// Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@/service/response'; import { authUser } from '@/service/utils/auth'; import { connectToDatabase, App } from '@/service/mongo'; import { rawSearchKey } from '@/constants/chat'; const chatTemplate = ({ model, temperature, maxToken, systemPrompt, limitPrompt }: { model: string; temperature: number; maxToken: number; systemPrompt: string; limitPrompt: string; }) => { return [ { logo: '/imgs/module/userChatInput.png', name: '用户问题', intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', type: 'initInput', flowType: 'questionInput', url: '/app/modules/init/userChatInput', inputs: [ { key: 'userChatInput', type: 'systemInput', label: '用户问题', connected: false } ], outputs: [ { key: 'userChatInput', label: '用户问题', type: 'source', targets: [ { moduleId: '7pacf0', key: 'userChatInput' } ] } ], position: { x: 477.9074315528994, y: 1604.2106242223683 }, moduleId: '7z5g5h' }, { logo: '/imgs/module/AI.png', name: 'AI 对话', intro: 'AI 大模型对话', flowType: 'chatNode', type: 'http', url: '/app/modules/chat/gpt', inputs: [ { key: 'model', type: 'custom', label: '对话模型', value: model, list: [ { label: 'FastAI-4k', value: 'gpt-3.5-turbo' }, { label: 'FastAI-16k', value: 'gpt-3.5-turbo-16k' }, { label: 'FastAI-Plus', value: 'gpt-4' } ], connected: false }, { key: 'temperature', type: 'custom', label: '温度', value: temperature, min: 0, max: 10, step: 1, markList: [ { label: '严谨', value: 0 }, { label: '发散', value: 10 } ], connected: false }, { key: 'maxToken', type: 'custom', label: '回复上限', value: maxToken, min: 100, max: 16000, step: 50, markList: [ { label: '0', value: 0 }, { label: '16000', value: 16000 } ], connected: false }, { key: 'systemPrompt', type: 'textarea', label: '系统提示词', description: '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', placeholder: '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', value: systemPrompt, connected: false }, { key: 'limitPrompt', type: 'textarea', label: '限定词', description: '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', placeholder: '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', value: limitPrompt, connected: false }, { key: 'switch', type: 'target', label: '触发器', connected: false }, { key: 'quotePrompt', type: 'target', label: '引用内容', connected: false }, { key: 'history', type: 'target', label: '聊天记录', connected: true }, { key: 'userChatInput', type: 'target', label: '用户问题', connected: true } ], outputs: [ { key: 'answerText', label: '模型回复', description: '直接响应,无需配置', type: 'hidden', targets: [] } ], position: { x: 981.9682828103937, y: 890.014595014464 }, moduleId: '7pacf0' }, { logo: '/imgs/module/history.png', name: '聊天记录', intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', type: 'initInput', flowType: 'historyNode', url: '/app/modules/init/history', inputs: [ { key: 'maxContext', type: 'numberInput', label: '最长记录数', value: 4, min: 0, max: 50, connected: false }, { key: 'history', type: 'hidden', label: '聊天记录', connected: false } ], outputs: [ { key: 'history', label: '聊天记录', type: 'source', targets: [ { moduleId: '7pacf0', key: 'history' } ] } ], position: { x: 452.5466249541586, y: 1276.3930310334215 }, moduleId: 'xj0c9p' } ]; }; const kbTemplate = ({ model, temperature, maxToken, systemPrompt, limitPrompt, kbs = [], searchSimilarity, searchLimit, searchEmptyText }: { model: string; temperature: number; maxToken: number; systemPrompt: string; limitPrompt: string; kbs: string[]; searchSimilarity: number; searchLimit: number; searchEmptyText: string; }) => { return [ { logo: '/imgs/module/userChatInput.png', name: '用户问题', intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', type: 'initInput', flowType: 'questionInput', url: '/app/modules/init/userChatInput', inputs: [ { key: 'userChatInput', type: 'systemInput', label: '用户问题', connected: false } ], outputs: [ { key: 'userChatInput', label: '用户问题', type: 'source', targets: [ { moduleId: 'q9v14m', key: 'userChatInput' }, { moduleId: 'qbf8td', key: 'userChatInput' } ] } ], position: { x: -210.24817109253843, y: 665.7922967022607 }, moduleId: 'v0nc1s' }, { logo: '/imgs/module/history.png', name: '聊天记录', intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', type: 'initInput', flowType: 'historyNode', url: '/app/modules/init/history', inputs: [ { key: 'maxContext', type: 'numberInput', label: '最长记录数', value: 4, min: 0, max: 50, connected: false }, { key: 'history', type: 'hidden', label: '聊天记录', connected: false } ], outputs: [ { key: 'history', label: '聊天记录', type: 'source', targets: [ { moduleId: 'qbf8td', key: 'history' } ] } ], position: { x: -196.84632684738483, y: 797.3401378431948 }, moduleId: 'k9y3jm' }, { logo: '/imgs/module/AI.png', name: 'AI 对话', intro: 'AI 大模型对话', flowType: 'chatNode', type: 'http', url: '/app/modules/chat/gpt', inputs: [ { key: 'model', type: 'custom', label: '对话模型', value: model, list: [ { label: 'FastAI-4k', value: 'gpt-3.5-turbo' }, { label: 'FastAI-16k', value: 'gpt-3.5-turbo-16k' }, { label: 'FastAI-Plus', value: 'gpt-4' } ], connected: false }, { key: 'temperature', type: 'custom', label: '温度', value: temperature, min: 0, max: 10, step: 1, markList: [ { label: '严谨', value: 0 }, { label: '发散', value: 10 } ], connected: false }, { key: 'maxToken', type: 'custom', label: '回复上限', value: maxToken, min: 100, max: 16000, step: 50, markList: [ { label: '0', value: 0 }, { label: '16000', value: 16000 } ], connected: false }, { key: 'systemPrompt', type: 'textarea', label: '系统提示词', description: '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', placeholder: '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', value: systemPrompt, connected: false }, { key: 'limitPrompt', type: 'textarea', label: '限定词', description: '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', placeholder: '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', value: limitPrompt, connected: false }, { key: 'switch', type: 'target', label: '触发器', connected: true }, { key: 'quotePrompt', type: 'target', label: '引用内容', connected: true }, { key: 'history', type: 'target', label: '聊天记录', connected: true }, { key: 'userChatInput', type: 'target', label: '用户问题', connected: true } ], outputs: [ { key: 'answerText', label: '模型回复', description: '直接响应,无需配置', type: 'hidden', targets: [] } ], position: { x: 745.484449528062, y: 259.9361900288137 }, moduleId: 'qbf8td' }, { logo: '/imgs/module/db.png', name: '知识库搜索', intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。', flowType: 'kbSearchNode', type: 'http', url: '/app/modules/kb/search', inputs: [ { key: 'kb_ids', type: 'custom', label: '关联的知识库', value: kbs, list: [], connected: false }, { key: 'similarity', type: 'custom', label: '相似度', value: searchSimilarity, min: 0, max: 1, step: 0.01, markList: [ { label: '0', value: 0 }, { label: '1', value: 1 } ], connected: false }, { key: 'limit', type: 'custom', label: '单次搜索上限', description: '最多取 n 条记录作为本次问题引用', value: searchLimit, min: 1, max: 20, step: 1, markList: [ { label: '1', value: 1 }, { label: '20', value: 20 } ], connected: false }, { key: 'switch', type: 'target', label: '触发器', connected: true }, { key: 'userChatInput', type: 'target', label: '用户问题', connected: true } ], outputs: [ { key: rawSearchKey, label: '源搜索数据', type: 'hidden', response: true, targets: [] }, { key: 'isEmpty', label: '搜索结果为空', type: 'source', targets: [ ...(searchEmptyText ? [ { moduleId: 'w8av9y', key: 'switch' } ] : []) ] }, { key: 'quotePrompt', label: '引用内容', description: '搜索结果为空时不返回', type: 'source', targets: [ { moduleId: 'qbf8td', key: 'quotePrompt' } ] } ], position: { x: 101.2612930583856, y: -31.342317423453437 }, moduleId: 'q9v14m' }, ...(searchEmptyText ? [ { logo: '/imgs/module/reply.png', name: '指定回复', intro: '该模块可以直接回复一段指定的内容。常用于引导、提示。', type: 'answer', flowType: 'answerNode', inputs: [ { key: 'switch', type: 'target', label: '触发器', connected: true }, { key: 'answerText', value: searchEmptyText, type: 'input', label: '回复的内容', connected: false } ], outputs: [], position: { x: 673.6108151684664, y: -84.13355134221933 }, moduleId: 'w8av9y' } ] : []) ]; }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await authUser({ req, authRoot: true }); await connectToDatabase(); // 遍历所有的 app const apps = await App.find( { chat: { $ne: null }, modules: { $exists: false } // userId: '63f9a14228d2a688d8dc9e1b' }, '_id chat' ); await Promise.all( apps.map(async (app) => { if (!app.chat) return app; const modules = (() => { if (app.chat.relatedKbs.length === 0) { return chatTemplate({ model: app.chat.chatModel, temperature: app.chat.temperature, maxToken: app.chat.maxToken, systemPrompt: app.chat.systemPrompt, limitPrompt: app.chat.limitPrompt }); } else { return kbTemplate({ model: app.chat.chatModel, temperature: app.chat.temperature, maxToken: app.chat.maxToken, systemPrompt: app.chat.systemPrompt, limitPrompt: app.chat.limitPrompt, kbs: app.chat.relatedKbs, searchEmptyText: app.chat.searchEmptyText, searchLimit: app.chat.searchLimit, searchSimilarity: app.chat.searchSimilarity }); } })(); await App.findByIdAndUpdate(app.id, { modules }); return modules; }) ); jsonRes(res, { data: apps.length }); } catch (error) { jsonRes(res, { code: 500, error }); } }