Commit 6027a966 by archer

perf: completion dispatch

parent 8151350d
...@@ -4,7 +4,7 @@ import type { InitChatResponse, InitShareChatResponse } from './response/chat'; ...@@ -4,7 +4,7 @@ import type { InitChatResponse, InitShareChatResponse } from './response/chat';
import { RequestPaging } from '../types/index'; import { RequestPaging } from '../types/index';
import type { ShareChatSchema } from '@/types/mongoSchema'; import type { ShareChatSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/app'; import type { ShareChatEditType } from '@/types/app';
import type { QuoteItemType } from '@/pages/api/app/modules/kb/search'; import type { QuoteItemType } from '@/types/chat';
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory'; import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';
/** /**
......
import { sseResponseEventEnum } from '@/constants/chat'; import { sseResponseEventEnum } from '@/constants/chat';
import { getErrText } from '@/utils/tools'; import { getErrText } from '@/utils/tools';
import { parseStreamChunk } from '@/utils/adapt'; import { parseStreamChunk } from '@/utils/adapt';
import { QuoteItemType } from '@/pages/api/app/modules/kb/search'; import { QuoteItemType } from '@/types/chat';
import { rawSearchKey } from '@/constants/chat';
interface StreamFetchProps { interface StreamFetchProps {
url?: string; url?: string;
...@@ -20,7 +19,6 @@ export const streamFetch = ({ ...@@ -20,7 +19,6 @@ export const streamFetch = ({
responseText: string; responseText: string;
errMsg: string; errMsg: string;
newChatId: string | null; newChatId: string | null;
[rawSearchKey]: QuoteItemType[];
}>(async (resolve, reject) => { }>(async (resolve, reject) => {
try { try {
const response = await window.fetch(url, { const response = await window.fetch(url, {
...@@ -43,7 +41,6 @@ export const streamFetch = ({ ...@@ -43,7 +41,6 @@ export const streamFetch = ({
// response data // response data
let responseText = ''; let responseText = '';
let rawSearch: QuoteItemType[] = [];
let errMsg = ''; let errMsg = '';
const newChatId = response.headers.get('newChatId'); const newChatId = response.headers.get('newChatId');
...@@ -55,8 +52,7 @@ export const streamFetch = ({ ...@@ -55,8 +52,7 @@ export const streamFetch = ({
return resolve({ return resolve({
responseText, responseText,
errMsg, errMsg,
newChatId, newChatId
rawSearch
}); });
} else { } else {
return reject({ return reject({
...@@ -82,7 +78,6 @@ export const streamFetch = ({ ...@@ -82,7 +78,6 @@ export const streamFetch = ({
onMessage(answer); onMessage(answer);
responseText += answer; responseText += answer;
} else if (item.event === sseResponseEventEnum.appStreamResponse) { } else if (item.event === sseResponseEventEnum.appStreamResponse) {
rawSearch = data?.[rawSearchKey] ? data[rawSearchKey] : rawSearch;
} else if (item.event === sseResponseEventEnum.error) { } else if (item.event === sseResponseEventEnum.error) {
errMsg = getErrText(data, '流响应错误'); errMsg = getErrText(data, '流响应错误');
} }
...@@ -93,8 +88,7 @@ export const streamFetch = ({ ...@@ -93,8 +88,7 @@ export const streamFetch = ({
return resolve({ return resolve({
responseText, responseText,
errMsg, errMsg,
newChatId, newChatId
rawSearch
}); });
} }
reject(getErrText(err, '请求异常')); reject(getErrText(err, '请求异常'));
......
...@@ -17,7 +17,7 @@ import { useQuery } from '@tanstack/react-query'; ...@@ -17,7 +17,7 @@ import { useQuery } from '@tanstack/react-query';
import { getHistoryQuote, updateHistoryQuote } from '@/api/chat'; import { getHistoryQuote, updateHistoryQuote } from '@/api/chat';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { getErrText } from '@/utils/tools'; import { getErrText } from '@/utils/tools';
import { QuoteItemType } from '@/pages/api/app/modules/kb/search'; import { QuoteItemType } from '@/types/chat';
const QuoteModal = ({ const QuoteModal = ({
chatId, chatId,
......
...@@ -22,7 +22,6 @@ import { Box, Card, Flex, Input, Textarea, Button, useTheme } from '@chakra-ui/r ...@@ -22,7 +22,6 @@ import { Box, Card, Flex, Input, Textarea, Button, useTheme } from '@chakra-ui/r
import { useUserStore } from '@/store/user'; import { useUserStore } from '@/store/user';
import { feConfigs } from '@/store/static'; import { feConfigs } from '@/store/static';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { HUMAN_ICON, quoteLenKey, rawSearchKey } from '@/constants/chat';
import { EventNameEnum } from '../Markdown/constant'; import { EventNameEnum } from '../Markdown/constant';
import { adaptChatItem_openAI } from '@/utils/plugin/openai'; import { adaptChatItem_openAI } from '@/utils/plugin/openai';
...@@ -35,7 +34,7 @@ import { fileDownload } from '@/utils/file'; ...@@ -35,7 +34,7 @@ import { fileDownload } from '@/utils/file';
import { htmlTemplate } from '@/constants/common'; import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
import { QuoteItemType } from '@/pages/api/app/modules/kb/search'; import { QuoteItemType } from '@/types/chat';
import { FlowModuleTypeEnum } from '@/constants/flow'; import { FlowModuleTypeEnum } from '@/constants/flow';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
...@@ -598,7 +597,7 @@ const ChatBox = ( ...@@ -598,7 +597,7 @@ const ChatBox = (
source={item.value} source={item.value}
isChatting={index === chatHistory.length - 1 && isChatting} isChatting={index === chatHistory.length - 1 && isChatting}
/> />
{(!!item[quoteLenKey] || !!item[rawSearchKey]?.length) && ( {/* {(!!item[quoteLenKey] || !!item[rawSearchKey]?.length) && (
<Button <Button
size={'xs'} size={'xs'}
variant={'base'} variant={'base'}
...@@ -613,7 +612,7 @@ const ChatBox = ( ...@@ -613,7 +612,7 @@ const ChatBox = (
> >
{item[quoteLenKey] || item[rawSearchKey]?.length}条引用 {item[quoteLenKey] || item[rawSearchKey]?.length}条引用
</Button> </Button>
)} )} */}
</Card> </Card>
<Flex {...controlContainerStyle}> <Flex {...controlContainerStyle}>
......
/* app */ /* app */
export enum AppModuleItemTypeEnum {
'variable' = 'variable',
'userGuide' = 'userGuide',
'initInput' = 'initInput',
'http' = 'http', // send a http request
'switch' = 'switch', // one input and two outputs
'answer' = 'answer' // redirect response
}
export enum SystemInputEnum { export enum SystemInputEnum {
'welcomeText' = 'welcomeText', 'welcomeText' = 'welcomeText',
'variables' = 'variables', 'variables' = 'variables',
...@@ -14,10 +6,7 @@ export enum SystemInputEnum { ...@@ -14,10 +6,7 @@ export enum SystemInputEnum {
'history' = 'history', 'history' = 'history',
'userChatInput' = 'userChatInput' 'userChatInput' = 'userChatInput'
} }
export enum TaskResponseKeyEnum {
'answerText' = 'answerText', // answer module text key
'responseData' = 'responseData'
}
export enum VariableInputEnum { export enum VariableInputEnum {
input = 'input', input = 'input',
select = 'select' select = 'select'
......
...@@ -12,6 +12,11 @@ export enum ChatRoleEnum { ...@@ -12,6 +12,11 @@ export enum ChatRoleEnum {
AI = 'AI' AI = 'AI'
} }
export enum TaskResponseKeyEnum {
'answerText' = 'answerText', // answer module text key
'responseData' = 'responseData'
}
export const ChatRoleMap = { export const ChatRoleMap = {
[ChatRoleEnum.System]: { [ChatRoleEnum.System]: {
name: '系统提示词' name: '系统提示词'
...@@ -46,10 +51,5 @@ export const ChatSourceMap = { ...@@ -46,10 +51,5 @@ export const ChatSourceMap = {
} }
}; };
export const responseDataKey = 'responseData';
export const rawSearchKey = 'rawSearch';
export const quoteLenKey = 'quoteLen';
export const HUMAN_ICON = `https://fastgpt.run/icon/human.png`; export const HUMAN_ICON = `https://fastgpt.run/icon/human.png`;
export const LOGO_ICON = `https://fastgpt.run/icon/logo.png`; export const LOGO_ICON = `https://fastgpt.run/icon/logo.png`;
import { AppModuleItemTypeEnum, SystemInputEnum, TaskResponseKeyEnum } from '../app'; import { SystemInputEnum } from '../app';
import { TaskResponseKeyEnum } from '../chat';
import { FlowModuleTypeEnum, FlowInputItemTypeEnum, FlowOutputItemTypeEnum } from './index'; import { FlowModuleTypeEnum, FlowInputItemTypeEnum, FlowOutputItemTypeEnum } from './index';
import type { AppItemType, AppModuleTemplateItemType } from '@/types/app'; import type { AppItemType } from '@/types/app';
import type { FlowModuleTemplateType } from '@/types/flow';
import { chatModelList } from '@/store/static'; import { chatModelList } from '@/store/static';
import { import {
Input_Template_History, Input_Template_History,
Input_Template_TFSwitch, Input_Template_TFSwitch,
Input_Template_UserChatInput Input_Template_UserChatInput
} from './inputTemplate'; } from './inputTemplate';
import { rawSearchKey } from '../chat';
export const ChatModelSystemTip = export const ChatModelSystemTip =
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}'; '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}';
...@@ -17,13 +18,12 @@ export const userGuideTip = '可以添加特殊的对话前后引导模块,更 ...@@ -17,13 +18,12 @@ export const userGuideTip = '可以添加特殊的对话前后引导模块,更
export const welcomeTextTip = export const welcomeTextTip =
'每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题'; '每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
export const VariableModule: AppModuleTemplateItemType = { export const VariableModule: FlowModuleTemplateType = {
logo: '/imgs/module/variable.png', logo: '/imgs/module/variable.png',
name: '全局变量', name: '全局变量',
intro: '可以在对话开始前,要求用户填写一些内容作为本轮对话的变量。该模块位于开场引导之后。', intro: '可以在对话开始前,要求用户填写一些内容作为本轮对话的变量。该模块位于开场引导之后。',
description: description:
'全局变量可以通过 {{变量key}} 的形式注入到其他模块的文本中。目前支持:提示词、限定词。', '全局变量可以通过 {{变量key}} 的形式注入到其他模块的文本中。目前支持:提示词、限定词。',
type: AppModuleItemTypeEnum.variable,
flowType: FlowModuleTypeEnum.variable, flowType: FlowModuleTypeEnum.variable,
inputs: [ inputs: [
{ {
...@@ -35,11 +35,10 @@ export const VariableModule: AppModuleTemplateItemType = { ...@@ -35,11 +35,10 @@ export const VariableModule: AppModuleTemplateItemType = {
], ],
outputs: [] outputs: []
}; };
export const UserGuideModule: AppModuleTemplateItemType = { export const UserGuideModule: FlowModuleTemplateType = {
logo: '/imgs/module/userGuide.png', logo: '/imgs/module/userGuide.png',
name: '用户引导', name: '用户引导',
intro: userGuideTip, intro: userGuideTip,
type: AppModuleItemTypeEnum.userGuide,
flowType: FlowModuleTypeEnum.userGuide, flowType: FlowModuleTypeEnum.userGuide,
inputs: [ inputs: [
{ {
...@@ -50,12 +49,11 @@ export const UserGuideModule: AppModuleTemplateItemType = { ...@@ -50,12 +49,11 @@ export const UserGuideModule: AppModuleTemplateItemType = {
], ],
outputs: [] outputs: []
}; };
export const UserInputModule: AppModuleTemplateItemType = { export const UserInputModule: FlowModuleTemplateType = {
logo: '/imgs/module/userChatInput.png', logo: '/imgs/module/userChatInput.png',
name: '用户问题', name: '用户问题',
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
type: AppModuleItemTypeEnum.initInput, flowType: FlowModuleTypeEnum.questionInput,
flowType: FlowModuleTypeEnum.questionInputNode,
url: '/app/modules/init/userChatInput', url: '/app/modules/init/userChatInput',
inputs: [ inputs: [
{ {
...@@ -73,11 +71,10 @@ export const UserInputModule: AppModuleTemplateItemType = { ...@@ -73,11 +71,10 @@ export const UserInputModule: AppModuleTemplateItemType = {
} }
] ]
}; };
export const HistoryModule: AppModuleTemplateItemType = { export const HistoryModule: FlowModuleTemplateType = {
logo: '/imgs/module/history.png', logo: '/imgs/module/history.png',
name: '聊天记录', name: '聊天记录',
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
type: AppModuleItemTypeEnum.initInput,
flowType: FlowModuleTypeEnum.historyNode, flowType: FlowModuleTypeEnum.historyNode,
url: '/app/modules/init/history', url: '/app/modules/init/history',
inputs: [ inputs: [
...@@ -106,12 +103,11 @@ export const HistoryModule: AppModuleTemplateItemType = { ...@@ -106,12 +103,11 @@ export const HistoryModule: AppModuleTemplateItemType = {
}; };
const defaultModel = chatModelList[0]; const defaultModel = chatModelList[0];
export const ChatModule: AppModuleTemplateItemType = { export const ChatModule: FlowModuleTemplateType = {
logo: '/imgs/module/AI.png', logo: '/imgs/module/AI.png',
name: 'AI 对话', name: 'AI 对话',
intro: 'AI 大模型对话', intro: 'AI 大模型对话',
flowType: FlowModuleTypeEnum.chatNode, flowType: FlowModuleTypeEnum.chatNode,
type: AppModuleItemTypeEnum.http,
url: '/app/modules/chat/gpt', url: '/app/modules/chat/gpt',
inputs: [ inputs: [
{ {
...@@ -166,9 +162,9 @@ export const ChatModule: AppModuleTemplateItemType = { ...@@ -166,9 +162,9 @@ export const ChatModule: AppModuleTemplateItemType = {
placeholder: ChatModelLimitTip, placeholder: ChatModelLimitTip,
value: '' value: ''
}, },
// Input_Template_TFSwitch, Input_Template_TFSwitch,
{ {
key: 'quotePrompt', key: 'quoteQA',
type: FlowInputItemTypeEnum.target, type: FlowInputItemTypeEnum.target,
label: '引用内容' label: '引用内容'
}, },
...@@ -186,12 +182,11 @@ export const ChatModule: AppModuleTemplateItemType = { ...@@ -186,12 +182,11 @@ export const ChatModule: AppModuleTemplateItemType = {
] ]
}; };
export const KBSearchModule: AppModuleTemplateItemType = { export const KBSearchModule: FlowModuleTemplateType = {
logo: '/imgs/module/db.png', logo: '/imgs/module/db.png',
name: '知识库搜索', name: '知识库搜索',
intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。', intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。',
flowType: FlowModuleTypeEnum.kbSearchNode, flowType: FlowModuleTypeEnum.kbSearchNode,
type: AppModuleItemTypeEnum.http,
url: '/app/modules/kb/search', url: '/app/modules/kb/search',
inputs: [ inputs: [
{ {
...@@ -233,20 +228,19 @@ export const KBSearchModule: AppModuleTemplateItemType = { ...@@ -233,20 +228,19 @@ export const KBSearchModule: AppModuleTemplateItemType = {
], ],
outputs: [ outputs: [
{ {
key: rawSearchKey, key: 'isEmpty',
label: '源搜索数据', label: '搜索结果为空',
type: FlowOutputItemTypeEnum.hidden, type: FlowOutputItemTypeEnum.source,
response: true,
targets: [] targets: []
}, },
{ {
key: 'isEmpty', key: 'unEmpty',
label: '搜索结果为空', label: '搜索结果不为空',
type: FlowOutputItemTypeEnum.source, type: FlowOutputItemTypeEnum.source,
targets: [] targets: []
}, },
{ {
key: 'quotePrompt', key: 'quoteQA',
label: '引用内容', label: '引用内容',
description: '搜索结果为空时不返回', description: '搜索结果为空时不返回',
type: FlowOutputItemTypeEnum.source, type: FlowOutputItemTypeEnum.source,
...@@ -255,16 +249,15 @@ export const KBSearchModule: AppModuleTemplateItemType = { ...@@ -255,16 +249,15 @@ export const KBSearchModule: AppModuleTemplateItemType = {
] ]
}; };
export const AnswerModule: AppModuleTemplateItemType = { export const AnswerModule: FlowModuleTemplateType = {
logo: '/imgs/module/reply.png', logo: '/imgs/module/reply.png',
name: '指定回复', name: '指定回复',
intro: '该模块可以直接回复一段指定的内容。常用于引导、提示。', intro: '该模块可以直接回复一段指定的内容。常用于引导、提示。',
type: AppModuleItemTypeEnum.answer,
flowType: FlowModuleTypeEnum.answerNode, flowType: FlowModuleTypeEnum.answerNode,
inputs: [ inputs: [
Input_Template_TFSwitch, Input_Template_TFSwitch,
{ {
key: TaskResponseKeyEnum.answerText, key: 'text',
value: '', value: '',
type: FlowInputItemTypeEnum.textarea, type: FlowInputItemTypeEnum.textarea,
label: '回复的内容' label: '回复的内容'
...@@ -272,11 +265,10 @@ export const AnswerModule: AppModuleTemplateItemType = { ...@@ -272,11 +265,10 @@ export const AnswerModule: AppModuleTemplateItemType = {
], ],
outputs: [] outputs: []
}; };
export const TFSwitchModule: AppModuleTemplateItemType = { export const TFSwitchModule: FlowModuleTemplateType = {
logo: '', logo: '',
name: 'TF开关', name: 'TF开关',
intro: '可以判断输入的内容为 True 或者 False,从而执行不同操作。', intro: '可以判断输入的内容为 True 或者 False,从而执行不同操作。',
type: AppModuleItemTypeEnum.switch,
flowType: FlowModuleTypeEnum.tfSwitchNode, flowType: FlowModuleTypeEnum.tfSwitchNode,
inputs: [ inputs: [
{ {
...@@ -300,13 +292,12 @@ export const TFSwitchModule: AppModuleTemplateItemType = { ...@@ -300,13 +292,12 @@ export const TFSwitchModule: AppModuleTemplateItemType = {
} }
] ]
}; };
export const ClassifyQuestionModule: AppModuleTemplateItemType = { export const ClassifyQuestionModule: FlowModuleTemplateType = {
logo: '/imgs/module/cq.png', logo: '/imgs/module/cq.png',
name: '问题分类', name: '问题分类',
intro: '可以判断用户问题属于哪方面问题,从而执行不同的操作。', intro: '可以判断用户问题属于哪方面问题,从而执行不同的操作。',
description: description:
'根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:\n类型1: 打招呼\n类型2: 关于 laf 通用问题\n类型3: 关于 laf 代码问题\n类型4: 其他问题', '根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:\n类型1: 打招呼\n类型2: 关于 laf 通用问题\n类型3: 关于 laf 代码问题\n类型4: 其他问题',
type: AppModuleItemTypeEnum.http,
url: '/app/modules/agent/classifyQuestion', url: '/app/modules/agent/classifyQuestion',
flowType: FlowModuleTypeEnum.classifyQuestion, flowType: FlowModuleTypeEnum.classifyQuestion,
inputs: [ inputs: [
...@@ -362,6 +353,15 @@ export const ClassifyQuestionModule: AppModuleTemplateItemType = { ...@@ -362,6 +353,15 @@ export const ClassifyQuestionModule: AppModuleTemplateItemType = {
} }
] ]
}; };
export const EmptyModule: FlowModuleTemplateType = {
logo: '/imgs/module/cq.png',
name: '该模块已被移除',
intro: '',
description: '',
flowType: FlowModuleTypeEnum.empty,
inputs: [],
outputs: []
};
export const ModuleTemplates = [ export const ModuleTemplates = [
{ {
...@@ -381,1302 +381,1301 @@ export const ModuleTemplates = [ ...@@ -381,1302 +381,1301 @@ export const ModuleTemplates = [
list: [ClassifyQuestionModule] list: [ClassifyQuestionModule]
} }
]; ];
export const ModuleTemplatesFlat = ModuleTemplates.map((templates) => templates.list)?.flat();
// template // template
export const appTemplates: (AppItemType & { avatar: string; intro: string })[] = [ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] = [
{ // {
id: 'simpleChat', // id: 'simpleChat',
avatar: '/imgs/module/AI.png', // avatar: '/imgs/module/AI.png',
name: '简单的对话', // name: '简单的对话',
intro: '一个极其简单的 AI 对话应用', // intro: '一个极其简单的 AI 对话应用',
modules: [ // modules: [
{ // {
...UserInputModule, // ...UserInputModule,
inputs: [ // inputs: [
{ // {
key: 'userChatInput', // key: 'userChatInput',
type: 'systemInput', // type: 'systemInput',
label: '用户问题', // label: '用户问题',
connected: false // connected: false
} // }
], // ],
outputs: [ // outputs: [
{ // {
key: 'userChatInput', // key: 'userChatInput',
label: '用户问题', // label: '用户问题',
type: 'source', // type: 'source',
targets: [ // targets: [
{ // {
moduleId: '7pacf0', // moduleId: '7pacf0',
key: 'userChatInput' // key: 'userChatInput'
} // }
] // ]
} // }
], // ],
position: { // position: {
x: 477.9074315528994, // x: 477.9074315528994,
y: 1604.2106242223683 // y: 1604.2106242223683
}, // },
moduleId: '7z5g5h' // moduleId: '7z5g5h'
}, // },
{ // {
...ChatModule, // ...ChatModule,
inputs: [ // inputs: [
{ // {
key: 'model', // key: 'model',
type: 'custom', // type: 'custom',
label: '对话模型', // label: '对话模型',
value: 'gpt-3.5-turbo-16k', // value: 'gpt-3.5-turbo-16k',
list: [ // list: [
{ // {
label: 'FastAI-4k', // label: 'FastAI-4k',
value: 'gpt-3.5-turbo' // value: 'gpt-3.5-turbo'
}, // },
{ // {
label: 'FastAI-16k', // label: 'FastAI-16k',
value: 'gpt-3.5-turbo-16k' // value: 'gpt-3.5-turbo-16k'
}, // },
{ // {
label: 'FastAI-Plus', // label: 'FastAI-Plus',
value: 'gpt-4' // value: 'gpt-4'
} // }
], // ],
connected: false // connected: false
}, // },
{ // {
key: 'temperature', // key: 'temperature',
type: 'custom', // type: 'custom',
label: '温度', // label: '温度',
value: 0, // value: 0,
min: 0, // min: 0,
max: 10, // max: 10,
step: 1, // step: 1,
markList: [ // markList: [
{ // {
label: '严谨', // label: '严谨',
value: 0 // value: 0
}, // },
{ // {
label: '发散', // label: '发散',
value: 10 // value: 10
} // }
], // ],
connected: false // connected: false
}, // },
{ // {
key: 'maxToken', // key: 'maxToken',
type: 'custom', // type: 'custom',
label: '回复上限', // label: '回复上限',
value: 8000, // value: 8000,
min: 100, // min: 100,
max: 16000, // max: 16000,
step: 50, // step: 50,
markList: [ // markList: [
{ // {
label: '0', // label: '0',
value: 0 // value: 0
}, // },
{ // {
label: '16000', // label: '16000',
value: 16000 // value: 16000
} // }
], // ],
connected: false // connected: false
}, // },
{ // {
key: 'systemPrompt', // key: 'systemPrompt',
type: 'textarea', // type: 'textarea',
label: '系统提示词', // label: '系统提示词',
description: // description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', // '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
placeholder: // placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', // '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
value: '', // value: '',
connected: false // connected: false
}, // },
{ // {
key: 'limitPrompt', // key: 'limitPrompt',
type: 'textarea', // type: 'textarea',
label: '限定词', // label: '限定词',
description: // description:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', // '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
placeholder: // placeholder:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', // '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
value: '', // value: '',
connected: false // connected: false
}, // },
{ // {
key: 'switch', // key: 'switch',
type: 'target', // type: 'target',
label: '触发器', // label: '触发器',
connected: false // connected: false
}, // },
{ // {
key: 'quotePrompt', // key: 'quoteQA',
type: 'target', // type: 'target',
label: '引用内容', // label: '引用内容',
connected: false // connected: false
}, // },
{ // {
key: 'history', // key: 'history',
type: 'target', // type: 'target',
label: '聊天记录', // label: '聊天记录',
connected: true // connected: true
}, // },
{ // {
key: 'userChatInput', // key: 'userChatInput',
type: 'target', // type: 'target',
label: '用户问题', // label: '用户问题',
connected: true // connected: true
} // }
], // ],
outputs: [ // outputs: [
{ // {
key: 'answerText', // key: 'answerText',
label: '模型回复', // label: '模型回复',
description: '直接响应,无需配置', // description: '直接响应,无需配置',
type: 'hidden', // type: 'hidden',
targets: [] // targets: []
} // }
], // ],
position: { // position: {
x: 981.9682828103937, // x: 981.9682828103937,
y: 890.014595014464 // y: 890.014595014464
}, // },
moduleId: '7pacf0' // moduleId: '7pacf0'
}, // },
{ // {
...HistoryModule, // ...HistoryModule,
inputs: [ // inputs: [
{ // {
key: 'maxContext', // key: 'maxContext',
type: 'numberInput', // type: 'numberInput',
label: '最长记录数', // label: '最长记录数',
value: 10, // value: 10,
min: 0, // min: 0,
max: 50, // max: 50,
connected: false // connected: false
}, // },
{ // {
key: 'history', // key: 'history',
type: 'hidden', // type: 'hidden',
label: '聊天记录', // label: '聊天记录',
connected: false // connected: false
} // }
], // ],
outputs: [ // outputs: [
{ // {
key: 'history', // key: 'history',
label: '聊天记录', // label: '聊天记录',
type: 'source', // type: 'source',
targets: [ // targets: [
{ // {
moduleId: '7pacf0', // moduleId: '7pacf0',
key: 'history' // key: 'history'
} // }
] // ]
} // }
], // ],
position: { // position: {
x: 452.5466249541586, // x: 452.5466249541586,
y: 1276.3930310334215 // y: 1276.3930310334215
}, // },
moduleId: 'xj0c9p' // moduleId: 'xj0c9p'
} // }
] // ]
}, // },
{ // {
id: 'simpleKbChat', // id: 'simpleKbChat',
avatar: '/imgs/module/db.png', // avatar: '/imgs/module/db.png',
name: '知识库 + 对话引导', // name: '知识库 + 对话引导',
intro: '每次提问时进行一次知识库搜索,将搜索结果注入 LLM 模型进行参考回答', // intro: '每次提问时进行一次知识库搜索,将搜索结果注入 LLM 模型进行参考回答',
modules: [ // modules: [
{ // {
...UserInputModule, // ...UserInputModule,
inputs: [ // inputs: [
{ // {
key: 'userChatInput', // key: 'userChatInput',
type: 'systemInput', // type: 'systemInput',
label: '用户问题', // label: '用户问题',
connected: false // connected: false
} // }
], // ],
outputs: [ // outputs: [
{ // {
key: 'userChatInput', // key: 'userChatInput',
label: '用户问题', // label: '用户问题',
type: 'source', // type: 'source',
targets: [ // targets: [
{ // {
moduleId: 'q9v14m', // moduleId: 'q9v14m',
key: 'userChatInput' // key: 'userChatInput'
}, // },
{ // {
moduleId: 'qbf8td', // moduleId: 'qbf8td',
key: 'userChatInput' // key: 'userChatInput'
} // }
] // ]
} // }
], // ],
position: { // position: {
x: -196.84632684738483, // x: -196.84632684738483,
y: 797.3401378431948 // y: 797.3401378431948
}, // },
moduleId: 'v0nc1s' // moduleId: 'v0nc1s'
}, // },
{ // {
...HistoryModule, // ...HistoryModule,
inputs: [ // inputs: [
{ // {
key: 'maxContext', // key: 'maxContext',
type: 'numberInput', // type: 'numberInput',
label: '最长记录数', // label: '最长记录数',
value: 10, // value: 10,
min: 0, // min: 0,
max: 50, // max: 50,
connected: false // connected: false
}, // },
{ // {
key: 'history', // key: 'history',
type: 'hidden', // type: 'hidden',
label: '聊天记录', // label: '聊天记录',
connected: false // connected: false
} // }
], // ],
outputs: [ // outputs: [
{ // {
key: 'history', // key: 'history',
label: '聊天记录', // label: '聊天记录',
type: 'source', // type: 'source',
targets: [ // targets: [
{ // {
moduleId: 'qbf8td', // moduleId: 'qbf8td',
key: 'history' // key: 'history'
} // }
] // ]
} // }
], // ],
position: { // position: {
x: 211.58250540918442, // x: 211.58250540918442,
y: 611.8700401034965 // y: 611.8700401034965
}, // },
moduleId: 'k9y3jm' // moduleId: 'k9y3jm'
}, // },
{ // {
...ChatModule, // ...ChatModule,
inputs: [ // inputs: [
{ // {
key: 'model', // key: 'model',
type: 'custom', // type: 'custom',
label: '对话模型', // label: '对话模型',
value: 'gpt-3.5-turbo-16k', // value: 'gpt-3.5-turbo-16k',
list: [ // list: [
{ // {
label: 'FastAI-4k', // label: 'FastAI-4k',
value: 'gpt-3.5-turbo' // value: 'gpt-3.5-turbo'
}, // },
{ // {
label: 'FastAI-16k', // label: 'FastAI-16k',
value: 'gpt-3.5-turbo-16k' // value: 'gpt-3.5-turbo-16k'
}, // },
{ // {
label: 'FastAI-Plus', // label: 'FastAI-Plus',
value: 'gpt-4' // value: 'gpt-4'
} // }
], // ],
connected: false // connected: false
}, // },
{ // {
key: 'temperature', // key: 'temperature',
type: 'custom', // type: 'custom',
label: '温度', // label: '温度',
value: 0, // value: 0,
min: 0, // min: 0,
max: 10, // max: 10,
step: 1, // step: 1,
markList: [ // markList: [
{ // {
label: '严谨', // label: '严谨',
value: 0 // value: 0
}, // },
{ // {
label: '发散', // label: '发散',
value: 10 // value: 10
} // }
], // ],
connected: false // connected: false
}, // },
{ // {
key: 'maxToken', // key: 'maxToken',
type: 'custom', // type: 'custom',
label: '回复上限', // label: '回复上限',
value: 8000, // value: 8000,
min: 100, // min: 100,
max: 16000, // max: 16000,
step: 50, // step: 50,
markList: [ // markList: [
{ // {
label: '0', // label: '0',
value: 0 // value: 0
}, // },
{ // {
label: '16000', // label: '16000',
value: 16000 // value: 16000
} // }
], // ],
connected: false // connected: false
}, // },
{ // {
key: 'systemPrompt', // key: 'systemPrompt',
type: 'textarea', // type: 'textarea',
label: '系统提示词', // label: '系统提示词',
description: // description:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', // '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
placeholder: // placeholder:
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', // '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
value: '', // value: '',
connected: false // connected: false
}, // },
{ // {
key: 'limitPrompt', // key: 'limitPrompt',
type: 'textarea', // type: 'textarea',
label: '限定词', // label: '限定词',
description: // description:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', // '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
placeholder: // placeholder:
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', // '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
value: '知识库是关于 Laf 的内容,参考知识库回答我的问题。', // value: '知识库是关于 Laf 的内容,参考知识库回答我的问题。',
connected: false // connected: false
}, // },
{ // {
key: 'switch', // key: 'switch',
type: 'target', // type: 'target',
label: '触发器', // label: '触发器',
connected: true // connected: true
}, // },
{ // {
key: 'quotePrompt', // key: 'quoteQA',
type: 'target', // type: 'target',
label: '引用内容', // label: '引用内容',
connected: true // connected: true
}, // },
{ // {
key: 'history', // key: 'history',
type: 'target', // type: 'target',
label: '聊天记录', // label: '聊天记录',
connected: true // connected: true
}, // },
{ // {
key: 'userChatInput', // key: 'userChatInput',
type: 'target', // type: 'target',
label: '用户问题', // label: '用户问题',
connected: true // connected: true
} // }
], // ],
outputs: [ // outputs: [
{ // {
key: 'answerText', // key: 'answerText',
label: '模型回复', // label: '模型回复',
description: '直接响应,无需配置', // description: '直接响应,无需配置',
type: 'hidden', // type: 'hidden',
targets: [] // targets: []
} // }
], // ],
position: { // position: {
x: 745.484449528062, // x: 745.484449528062,
y: 259.9361900288137 // y: 259.9361900288137
}, // },
moduleId: 'qbf8td' // moduleId: 'qbf8td'
}, // },
{ // {
...KBSearchModule, // ...KBSearchModule,
inputs: [ // inputs: [
{ // {
key: 'kbList', // key: 'kbList',
type: 'custom', // type: 'custom',
label: '关联的知识库', // label: '关联的知识库',
value: [], // value: [],
list: [], // list: [],
connected: false // connected: false
}, // },
{ // {
key: 'similarity', // key: 'similarity',
type: 'custom', // type: 'custom',
label: '相似度', // label: '相似度',
value: 0.8, // value: 0.8,
min: 0, // min: 0,
max: 1, // max: 1,
step: 0.01, // step: 0.01,
markList: [ // markList: [
{ // {
label: '0', // label: '0',
value: 0 // value: 0
}, // },
{ // {
label: '1', // label: '1',
value: 1 // value: 1
} // }
], // ],
connected: false // connected: false
}, // },
{ // {
key: 'limit', // key: 'limit',
type: 'custom', // type: 'custom',
label: '单次搜索上限', // label: '单次搜索上限',
description: '最多取 n 条记录作为本次问题引用', // description: '最多取 n 条记录作为本次问题引用',
value: 5, // value: 5,
min: 1, // min: 1,
max: 20, // max: 20,
step: 1, // step: 1,
markList: [ // markList: [
{ // {
label: '1', // label: '1',
value: 1 // value: 1
}, // },
{ // {
label: '20', // label: '20',
value: 20 // value: 20
} // }
], // ],
connected: false // connected: false
}, // },
{ // {
key: 'switch', // key: 'switch',
type: 'target', // type: 'target',
label: '触发器', // label: '触发器',
connected: true // connected: true
}, // },
{ // {
key: 'userChatInput', // key: 'userChatInput',
type: 'target', // type: 'target',
label: '用户问题', // label: '用户问题',
connected: true // connected: true
} // }
], // ],
outputs: [ // outputs: [
{ // {
key: rawSearchKey, // label: '源搜索数据',
label: '源搜索数据', // type: 'hidden',
type: 'hidden', // response: true,
response: true, // targets: []
targets: [] // },
}, // {
{ // key: 'isEmpty',
key: 'isEmpty', // label: '搜索结果为空',
label: '搜索结果为空', // type: 'source',
type: 'source', // targets: [
targets: [ // {
{ // moduleId: 'w8av9y',
moduleId: 'w8av9y', // key: 'switch'
key: 'switch' // }
} // ]
] // },
}, // {
{ // key: 'quoteQA',
key: 'quotePrompt', // label: '引用内容',
label: '引用内容', // description: '搜索结果为空时不返回',
description: '搜索结果为空时不返回', // type: 'source',
type: 'source', // targets: [
targets: [ // {
{ // moduleId: 'qbf8td',
moduleId: 'qbf8td', // key: 'quoteQA'
key: 'quotePrompt' // }
} // ]
] // }
} // ],
], // position: {
position: { // x: 101.2612930583856,
x: 101.2612930583856, // y: -31.342317423453437
y: -31.342317423453437 // },
}, // moduleId: 'q9v14m'
moduleId: 'q9v14m' // },
}, // {
{ // ...AnswerModule,
...AnswerModule, // inputs: [
inputs: [ // {
{ // key: 'switch',
key: 'switch', // type: 'target',
type: 'target', // label: '触发器',
label: '触发器', // connected: true
connected: true // },
}, // {
{ // key: 'answerText',
key: 'answerText', // value: '对不起,我没有找到你的问题',
value: '对不起,我没有找到你的问题', // type: 'input',
type: 'input', // label: '回复的内容',
label: '回复的内容', // connected: false
connected: false // }
} // ],
], // outputs: [],
outputs: [], // position: {
position: { // x: 673.6108151684664,
x: 673.6108151684664, // y: -84.13355134221933
y: -84.13355134221933 // },
}, // moduleId: 'w8av9y'
moduleId: 'w8av9y' // },
}, // {
{ // ...UserGuideModule,
...UserGuideModule, // inputs: [
inputs: [ // {
{ // key: 'welcomeText',
key: 'welcomeText', // type: 'input',
type: 'input', // label: '开场白',
label: '开场白', // value:
value: // '你好,我是 Laf 助手,请问有什么可以帮助你的么?\n[laf 是什么?]\n[官网是多少?]',
'你好,我是 Laf 助手,请问有什么可以帮助你的么?\n[laf 是什么?]\n[官网是多少?]', // connected: false
connected: false // }
} // ],
], // outputs: [],
outputs: [], // position: {
position: { // x: -338.02984747117785,
x: -338.02984747117785, // y: 203.21398144017178
y: 203.21398144017178 // },
}, // moduleId: 'v7lq0x'
moduleId: 'v7lq0x' // }
} // ]
] // },
}, // {
{ // id: 'chatGuide',
id: 'chatGuide', // avatar: '/imgs/module/userGuide.png',
avatar: '/imgs/module/userGuide.png', // name: '对话引导 + 变量',
name: '对话引导 + 变量', // intro: '可以在对话开始发送一段提示,或者让用户填写一些内容,作为本次对话的变量',
intro: '可以在对话开始发送一段提示,或者让用户填写一些内容,作为本次对话的变量', // modules: [
modules: [ // {
{ // ...UserInputModule,
...UserInputModule, // inputs: [
inputs: [ // {
{ // key: 'userChatInput',
key: 'userChatInput', // type: 'systemInput',
type: 'systemInput', // label: '用户问题',
label: '用户问题', // connected: false
connected: false // }
} // ],
], // outputs: [
outputs: [ // {
{ // key: 'userChatInput',
key: 'userChatInput', // label: '用户问题',
label: '用户问题', // type: 'source',
type: 'source', // targets: [
targets: [ // {
{ // moduleId: '7pacf0',
moduleId: '7pacf0', // key: 'userChatInput'
key: 'userChatInput' // }
} // ]
] // }
} // ],
], // position: {
position: { // x: 485.8457451202796,
x: 485.8457451202796, // y: 1601.0352987954163
y: 1601.0352987954163 // },
}, // moduleId: '7z5g5h'
moduleId: '7z5g5h' // },
}, // {
{ // ...ChatModule,
...ChatModule, // inputs: [
inputs: [ // {
{ // key: 'model',
key: 'model', // type: 'custom',
type: 'custom', // label: '对话模型',
label: '对话模型', // value: 'gpt-3.5-turbo-16k',
value: 'gpt-3.5-turbo-16k', // list: [
list: [ // {
{ // label: 'FastAI-4k',
label: 'FastAI-4k', // value: 'gpt-3.5-turbo'
value: 'gpt-3.5-turbo' // },
}, // {
{ // label: 'FastAI-16k',
label: 'FastAI-16k', // value: 'gpt-3.5-turbo-16k'
value: 'gpt-3.5-turbo-16k' // },
}, // {
{ // label: 'FastAI-Plus',
label: 'FastAI-Plus', // value: 'gpt-4'
value: 'gpt-4' // }
} // ],
], // connected: false
connected: false // },
}, // {
{ // key: 'temperature',
key: 'temperature', // type: 'custom',
type: 'custom', // label: '温度',
label: '温度', // value: 0,
value: 0, // min: 0,
min: 0, // max: 10,
max: 10, // step: 1,
step: 1, // markList: [
markList: [ // {
{ // label: '严谨',
label: '严谨', // value: 0
value: 0 // },
}, // {
{ // label: '发散',
label: '发散', // value: 10
value: 10 // }
} // ],
], // connected: false
connected: false // },
}, // {
{ // key: 'maxToken',
key: 'maxToken', // type: 'custom',
type: 'custom', // label: '回复上限',
label: '回复上限', // value: 8000,
value: 8000, // min: 100,
min: 100, // max: 16000,
max: 16000, // step: 50,
step: 50, // markList: [
markList: [ // {
{ // label: '0',
label: '0', // value: 0
value: 0 // },
}, // {
{ // label: '16000',
label: '16000', // value: 16000
value: 16000 // }
} // ],
], // connected: false
connected: false // },
}, // {
{ // key: 'systemPrompt',
key: 'systemPrompt', // type: 'textarea',
type: 'textarea', // label: '系统提示词',
label: '系统提示词', // description:
description: // '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', // placeholder:
placeholder: // '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', // value: '',
value: '', // connected: false
connected: false // },
}, // {
{ // key: 'limitPrompt',
key: 'limitPrompt', // type: 'textarea',
type: 'textarea', // label: '限定词',
label: '限定词', // description:
description: // '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', // placeholder:
placeholder: // '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', // value: '将我发送的任何内容,直接翻译成{{language}}',
value: '将我发送的任何内容,直接翻译成{{language}}', // connected: false
connected: false // },
}, // {
{ // key: 'switch',
key: 'switch', // type: 'target',
type: 'target', // label: '触发器',
label: '触发器', // connected: false
connected: false // },
}, // {
{ // key: 'quoteQA',
key: 'quotePrompt', // type: 'target',
type: 'target', // label: '引用内容',
label: '引用内容', // connected: false
connected: false // },
}, // {
{ // key: 'history',
key: 'history', // type: 'target',
type: 'target', // label: '聊天记录',
label: '聊天记录', // connected: true
connected: true // },
}, // {
{ // key: 'userChatInput',
key: 'userChatInput', // type: 'target',
type: 'target', // label: '用户问题',
label: '用户问题', // connected: true
connected: true // }
} // ],
], // outputs: [
outputs: [ // {
{ // key: 'answerText',
key: 'answerText', // label: '模型回复',
label: '模型回复', // description: '直接响应,无需配置',
description: '直接响应,无需配置', // type: 'hidden',
type: 'hidden', // targets: []
targets: [] // }
} // ],
], // position: {
position: { // x: 981.9682828103937,
x: 981.9682828103937, // y: 890.014595014464
y: 890.014595014464 // },
}, // moduleId: '7pacf0'
moduleId: '7pacf0' // },
}, // {
{ // ...HistoryModule,
...HistoryModule, // inputs: [
inputs: [ // {
{ // key: 'maxContext',
key: 'maxContext', // type: 'numberInput',
type: 'numberInput', // label: '最长记录数',
label: '最长记录数', // value: 10,
value: 10, // min: 0,
min: 0, // max: 50,
max: 50, // connected: false
connected: false // },
}, // {
{ // key: 'history',
key: 'history', // type: 'hidden',
type: 'hidden', // label: '聊天记录',
label: '聊天记录', // connected: false
connected: false // }
} // ],
], // outputs: [
outputs: [ // {
{ // key: 'history',
key: 'history', // label: '聊天记录',
label: '聊天记录', // type: 'source',
type: 'source', // targets: [
targets: [ // {
{ // moduleId: '7pacf0',
moduleId: '7pacf0', // key: 'history'
key: 'history' // }
} // ]
] // }
} // ],
], // position: {
position: { // x: 446.2698477029736,
x: 446.2698477029736, // y: 1281.1006139718102
y: 1281.1006139718102 // },
}, // moduleId: 'xj0c9p'
moduleId: 'xj0c9p' // },
}, // {
{ // ...VariableModule,
...VariableModule, // inputs: [
inputs: [ // {
{ // key: 'variables',
key: 'variables', // type: 'systemInput',
type: 'systemInput', // label: '变量输入',
label: '变量输入', // value: [
value: [ // {
{ // id: 'z3bs2f',
id: 'z3bs2f', // key: 'language',
key: 'language', // label: '目标语言',
label: '目标语言', // type: 'select',
type: 'select', // required: true,
required: true, // maxLen: 50,
maxLen: 50, // enums: [
enums: [ // {
{ // value: '英语'
value: '英语' // },
}, // {
{ // value: '法语'
value: '法语' // },
}, // {
{ // value: '日语'
value: '日语' // }
} // ]
] // }
} // ],
], // connected: false
connected: false // }
} // ],
], // outputs: [],
outputs: [], // position: {
position: { // x: 513.9049244392417,
x: 513.9049244392417, // y: 996.8739106932076
y: 996.8739106932076 // },
}, // moduleId: '7blchb'
moduleId: '7blchb' // },
}, // {
{ // ...UserGuideModule,
...UserGuideModule, // inputs: [
inputs: [ // {
{ // key: 'welcomeText',
key: 'welcomeText', // type: 'input',
type: 'input', // label: '开场白',
label: '开场白', // value: '你好,我是翻译助手,可以帮你翻译任何语言。请告诉我,你需要翻译成什么语言?',
value: '你好,我是翻译助手,可以帮你翻译任何语言。请告诉我,你需要翻译成什么语言?', // connected: false
connected: false // }
} // ],
], // outputs: [],
outputs: [], // position: {
position: { // x: 173.17995039750167,
x: 173.17995039750167, // y: 982.945778706804
y: 982.945778706804 // },
}, // moduleId: 'w35iml'
moduleId: 'w35iml' // }
} // ]
] // },
}, // {
{ // id: 'CQ',
id: 'CQ', // avatar: '/imgs/module/cq.png',
avatar: '/imgs/module/cq.png', // name: '问题分类 + 知识库',
name: '问题分类 + 知识库', // intro: '先对用户的问题进行分类,再根据不同类型问题,执行不同的操作',
intro: '先对用户的问题进行分类,再根据不同类型问题,执行不同的操作', // modules: [
modules: [ // {
{ // ...UserInputModule,
...UserInputModule, // inputs: [
inputs: [ // {
{ // key: 'userChatInput',
key: 'userChatInput', // type: 'systemInput',
type: 'systemInput', // label: '用户问题',
label: '用户问题', // connected: false
connected: false // }
} // ],
], // outputs: [
outputs: [ // {
{ // key: 'userChatInput',
key: 'userChatInput', // label: '用户问题',
label: '用户问题', // type: 'source',
type: 'source', // targets: [
targets: [ // {
{ // moduleId: '3n49vn',
moduleId: '3n49vn', // key: 'userChatInput'
key: 'userChatInput' // },
}, // {
{ // moduleId: 's7qnhf',
moduleId: 's7qnhf', // key: 'userChatInput'
key: 'userChatInput' // },
}, // {
{ // moduleId: '15c9bv',
moduleId: '15c9bv', // key: 'userChatInput'
key: 'userChatInput' // }
} // ]
] // }
} // ],
], // position: {
position: { // x: -216.08819066976912,
x: -216.08819066976912, // y: 585.9302721518841
y: 585.9302721518841 // },
}, // moduleId: 'xzj0oo'
moduleId: 'xzj0oo' // },
}, // {
{ // ...HistoryModule,
...HistoryModule, // inputs: [
inputs: [ // {
{ // key: 'maxContext',
key: 'maxContext', // type: 'numberInput',
type: 'numberInput', // label: '最长记录数',
label: '最长记录数', // value: 10,
value: 10, // min: 0,
min: 0, // max: 50,
max: 50, // connected: false
connected: false // },
}, // {
{ // key: 'history',
key: 'history', // type: 'hidden',
type: 'hidden', // label: '聊天记录',
label: '聊天记录', // connected: false
connected: false // }
} // ],
], // outputs: [
outputs: [ // {
{ // key: 'history',
key: 'history', // label: '聊天记录',
label: '聊天记录', // type: 'source',
type: 'source', // targets: [
targets: [ // {
{ // moduleId: '3n49vn',
moduleId: '3n49vn', // key: 'history'
key: 'history' // }
} // ]
] // }
} // ],
], // position: {
position: { // x: 1146.0216647621794,
x: 1146.0216647621794, // y: 236.92269104756855
y: 236.92269104756855 // },
}, // moduleId: 'hh6of9'
moduleId: 'hh6of9' // },
}, // {
{ // ...ChatModule,
...ChatModule, // inputs: [
inputs: [ // {
{ // key: 'model',
key: 'model', // type: 'select',
type: 'select', // label: '对话模型',
label: '对话模型', // value: 'gpt-3.5-turbo',
value: 'gpt-3.5-turbo', // list: [
list: [ // {
{ // label: 'FastAI-16k',
label: 'FastAI-16k', // value: 'gpt-3.5-turbo-16k'
value: 'gpt-3.5-turbo-16k' // },
}, // {
{ // label: 'FastAI-4k',
label: 'FastAI-4k', // value: 'gpt-3.5-turbo'
value: 'gpt-3.5-turbo' // },
}, // {
{ // label: 'FastAI-Plus-8k',
label: 'FastAI-Plus-8k', // value: 'gpt-4'
value: 'gpt-4' // }
} // ],
], // connected: false
connected: false // },
}, // {
{ // key: 'temperature',
key: 'temperature', // type: 'custom',
type: 'custom', // label: '温度',
label: '温度', // value: 0,
value: 0, // min: 0,
min: 0, // max: 10,
max: 10, // step: 1,
step: 1, // markList: [
markList: [ // {
{ // label: '严谨',
label: '严谨', // value: 0
value: 0 // },
}, // {
{ // label: '发散',
label: '发散', // value: 10
value: 10 // }
} // ],
], // connected: false
connected: false // },
}, // {
{ // key: 'maxToken',
key: 'maxToken', // type: 'custom',
type: 'custom', // label: '回复上限',
label: '回复上限', // value: 3000,
value: 3000, // min: 100,
min: 100, // max: 4000,
max: 4000, // step: 50,
step: 50, // markList: [
markList: [ // {
{ // label: '0',
label: '0', // value: 0
value: 0 // },
}, // {
{ // label: '4000',
label: '4000', // value: 4000
value: 4000 // }
} // ],
], // connected: false
connected: false // },
}, // {
{ // key: 'systemPrompt',
key: 'systemPrompt', // type: 'textarea',
type: 'textarea', // label: '系统提示词',
label: '系统提示词', // description:
description: // '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', // placeholder:
placeholder: // '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。',
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', // value: '你是 Laf 助手,可以回答 Laf 相关问题。',
value: '你是 Laf 助手,可以回答 Laf 相关问题。', // connected: false
connected: false // },
}, // {
{ // key: 'limitPrompt',
key: 'limitPrompt', // type: 'textarea',
type: 'textarea', // label: '限定词',
label: '限定词', // description:
description: // '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', // placeholder:
placeholder: // '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"',
'限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', // value: '知识库是 Laf 的内容,参考知识库回答问题。',
value: '知识库是 Laf 的内容,参考知识库回答问题。', // connected: false
connected: false // },
}, // {
{ // key: 'switch',
key: 'switch', // type: 'target',
type: 'target', // label: '触发器',
label: '触发器', // connected: false
connected: false // },
}, // {
{ // key: 'quoteQA',
key: 'quotePrompt', // type: 'target',
type: 'target', // label: '引用内容',
label: '引用内容', // connected: true
connected: true // },
}, // {
{ // key: 'history',
key: 'history', // type: 'target',
type: 'target', // label: '聊天记录',
label: '聊天记录', // connected: true
connected: true // },
}, // {
{ // key: 'userChatInput',
key: 'userChatInput', // type: 'target',
type: 'target', // label: '用户问题',
label: '用户问题', // connected: true
connected: true // }
} // ],
], // outputs: [
outputs: [ // {
{ // key: 'answerText',
key: 'answerText', // label: '模型回复',
label: '模型回复', // description: '直接响应,无需配置',
description: '直接响应,无需配置', // type: 'hidden',
type: 'hidden', // targets: []
targets: [] // }
} // ],
], // position: {
position: { // x: 1494.4843114348841,
x: 1494.4843114348841, // y: -13.57201521210618
y: -13.57201521210618 // },
}, // moduleId: '3n49vn'
moduleId: '3n49vn' // },
}, // {
{ // ...KBSearchModule,
...KBSearchModule, // inputs: [
inputs: [ // {
{ // key: 'kbList',
key: 'kbList', // type: 'custom',
type: 'custom', // label: '关联的知识库',
label: '关联的知识库', // value: [],
value: [], // list: [],
list: [], // connected: false
connected: false // },
}, // {
{ // key: 'similarity',
key: 'similarity', // type: 'custom',
type: 'custom', // label: '相似度',
label: '相似度', // value: 0.8,
value: 0.8, // min: 0,
min: 0, // max: 1,
max: 1, // step: 0.01,
step: 0.01, // markList: [
markList: [ // {
{ // label: '0',
label: '0', // value: 0
value: 0 // },
}, // {
{ // label: '1',
label: '1', // value: 1
value: 1 // }
} // ],
], // connected: false
connected: false // },
}, // {
{ // key: 'limit',
key: 'limit', // type: 'custom',
type: 'custom', // label: '单次搜索上限',
label: '单次搜索上限', // description: '最多取 n 条记录作为本次问题引用',
description: '最多取 n 条记录作为本次问题引用', // value: 5,
value: 5, // min: 1,
min: 1, // max: 20,
max: 20, // step: 1,
step: 1, // markList: [
markList: [ // {
{ // label: '1',
label: '1', // value: 1
value: 1 // },
}, // {
{ // label: '20',
label: '20', // value: 20
value: 20 // }
} // ],
], // connected: false
connected: false // },
}, // {
{ // key: 'switch',
key: 'switch', // type: 'target',
type: 'target', // label: '触发器',
label: '触发器', // connected: true
connected: true // },
}, // {
{ // key: 'userChatInput',
key: 'userChatInput', // type: 'target',
type: 'target', // label: '用户问题',
label: '用户问题', // connected: true
connected: true // }
} // ],
], // outputs: [
outputs: [ // {
{ // label: '源搜索数据',
key: rawSearchKey, // type: 'hidden',
label: '源搜索数据', // response: true,
type: 'hidden', // targets: []
response: true, // },
targets: [] // {
}, // key: 'isEmpty',
{ // label: '搜索结果为空',
key: 'isEmpty', // type: 'source',
label: '搜索结果为空', // targets: [
type: 'source', // {
targets: [ // moduleId: 'phwr0u',
{ // key: 'switch'
moduleId: 'phwr0u', // }
key: 'switch' // ]
} // },
] // {
}, // key: 'quoteQA',
{ // label: '引用内容',
key: 'quotePrompt', // description: '搜索结果为空时不返回',
label: '引用内容', // type: 'source',
description: '搜索结果为空时不返回', // targets: [
type: 'source', // {
targets: [ // moduleId: '3n49vn',
{ // key: 'quoteQA'
moduleId: '3n49vn', // }
key: 'quotePrompt' // ]
} // }
] // ],
} // position: {
], // x: 690.1930900957847,
position: { // y: 102.10119978743109
x: 690.1930900957847, // },
y: 102.10119978743109 // moduleId: 's7qnhf'
}, // },
moduleId: 's7qnhf' // {
}, // ...HistoryModule,
{ // inputs: [
...HistoryModule, // {
inputs: [ // key: 'maxContext',
{ // type: 'numberInput',
key: 'maxContext', // label: '最长记录数',
type: 'numberInput', // value: 2,
label: '最长记录数', // min: 0,
value: 2, // max: 50,
min: 0, // connected: false
max: 50, // },
connected: false // {
}, // key: 'history',
{ // type: 'hidden',
key: 'history', // label: '聊天记录',
type: 'hidden', // connected: false
label: '聊天记录', // }
connected: false // ],
} // outputs: [
], // {
outputs: [ // key: 'history',
{ // label: '聊天记录',
key: 'history', // type: 'source',
label: '聊天记录', // targets: [
type: 'source', // {
targets: [ // moduleId: '15c9bv',
{ // key: 'history'
moduleId: '15c9bv', // }
key: 'history' // ]
} // }
] // ],
} // position: {
], // x: -274.2362185453961,
position: { // y: 152.19755525696058
x: -274.2362185453961, // },
y: 152.19755525696058 // moduleId: 'qiwrjt'
}, // },
moduleId: 'qiwrjt' // {
}, // ...AnswerModule,
{ // inputs: [
...AnswerModule, // {
inputs: [ // key: 'switch',
{ // type: 'target',
key: 'switch', // label: '触发器',
type: 'target', // connected: true
label: '触发器', // },
connected: true // {
}, // key: 'answerText',
{ // value: '你好,我是 Laf 助手,有什么可以帮助你的么?',
key: 'answerText', // type: 'input',
value: '你好,我是 Laf 助手,有什么可以帮助你的么?', // label: '回复的内容',
type: 'input', // connected: false
label: '回复的内容', // }
connected: false // ],
} // outputs: [],
], // position: {
outputs: [], // x: 686.1260929408212,
position: { // y: -142.16731465682332
x: 686.1260929408212, // },
y: -142.16731465682332 // moduleId: 'l4e36k'
}, // },
moduleId: 'l4e36k' // {
}, // ...AnswerModule,
{ // inputs: [
...AnswerModule, // {
inputs: [ // key: 'switch',
{ // type: 'target',
key: 'switch', // label: '触发器',
type: 'target', // connected: true
label: '触发器', // },
connected: true // {
}, // key: 'answerText',
{ // value: '对不起,我无法回答你的问题,请问有什么关于 Laf 的问题么?',
key: 'answerText', // type: 'input',
value: '对不起,我无法回答你的问题,请问有什么关于 Laf 的问题么?', // label: '回复的内容',
type: 'input', // connected: false
label: '回复的内容', // }
connected: false // ],
} // outputs: [],
], // position: {
outputs: [], // x: 1469.3636235179692,
position: { // y: 937.5555811306511
x: 1469.3636235179692, // },
y: 937.5555811306511 // moduleId: 'phwr0u'
}, // },
moduleId: 'phwr0u' // {
}, // ...ClassifyQuestionModule,
{ // inputs: [
...ClassifyQuestionModule, // {
inputs: [ // key: 'systemPrompt',
{ // type: 'textarea',
key: 'systemPrompt', // label: '系统提示词',
type: 'textarea', // description:
label: '系统提示词', // '你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。',
description: // placeholder: '例如: \n1. Laf 是一个云函数开发平台……\n2. Sealos 是一个集群操作系统',
'你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。', // value:
placeholder: '例如: \n1. Laf 是一个云函数开发平台……\n2. Sealos 是一个集群操作系统', // ' laf 是什么\nlaf 是云开发平台,可以快速的开发应用\nlaf 是一个开源的 BaaS 开发平台(Backend as a Service)\nlaf 是一个开箱即用的 serverless 开发平台\nlaf 是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台\nlaf 可以是开源版的腾讯云开发、开源版的 Google Firebase、开源版的 UniCloud\nlaf 让每个开发团队都可以随时拥有一个自己的云开发平台!',
value: // connected: false
' laf 是什么\nlaf 是云开发平台,可以快速的开发应用\nlaf 是一个开源的 BaaS 开发平台(Backend as a Service)\nlaf 是一个开箱即用的 serverless 开发平台\nlaf 是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台\nlaf 可以是开源版的腾讯云开发、开源版的 Google Firebase、开源版的 UniCloud\nlaf 让每个开发团队都可以随时拥有一个自己的云开发平台!', // },
connected: false // {
}, // key: 'history',
{ // type: 'target',
key: 'history', // label: '聊天记录',
type: 'target', // connected: true
label: '聊天记录', // },
connected: true // {
}, // key: 'userChatInput',
{ // type: 'target',
key: 'userChatInput', // label: '用户问题',
type: 'target', // connected: true
label: '用户问题', // },
connected: true // {
}, // key: 'agents',
{ // type: 'custom',
key: 'agents', // label: '',
type: 'custom', // value: [
label: '', // {
value: [ // value: '打招呼、问候等',
{ // key: 'fasw'
value: '打招呼、问候等', // },
key: 'fasw' // {
}, // value: '关于 laf 云函数的问题',
{ // key: 'fqsw'
value: '关于 laf 云函数的问题', // },
key: 'fqsw' // {
}, // value: '其他问题',
{ // key: 'q73b'
value: '其他问题', // }
key: 'q73b' // ],
} // connected: false
], // }
connected: false // ],
} // outputs: [
], // {
outputs: [ // key: 'fasw',
{ // label: '',
key: 'fasw', // type: 'hidden',
label: '', // targets: [
type: 'hidden', // {
targets: [ // moduleId: 'l4e36k',
{ // key: 'switch'
moduleId: 'l4e36k', // }
key: 'switch' // ]
} // },
] // {
}, // key: 'fqsw',
{ // label: '',
key: 'fqsw', // type: 'hidden',
label: '', // targets: [
type: 'hidden', // {
targets: [ // moduleId: 's7qnhf',
{ // key: 'switch'
moduleId: 's7qnhf', // }
key: 'switch' // ]
} // },
] // {
}, // key: 'q73b',
{ // label: '',
key: 'q73b', // type: 'hidden',
label: '', // targets: [
type: 'hidden', // {
targets: [ // moduleId: 'phwr0u',
{ // key: 'switch'
moduleId: 'phwr0u', // }
key: 'switch' // ]
} // }
] // ],
} // position: {
], // x: 154.9724540917009,
position: { // y: -37.48714632270105
x: 154.9724540917009, // },
y: -37.48714632270105 // moduleId: '15c9bv'
}, // }
moduleId: '15c9bv' // ]
} // }
]
}
]; ];
...@@ -19,9 +19,10 @@ export enum FlowOutputItemTypeEnum { ...@@ -19,9 +19,10 @@ export enum FlowOutputItemTypeEnum {
} }
export enum FlowModuleTypeEnum { export enum FlowModuleTypeEnum {
empty = 'empty',
variable = 'variable', variable = 'variable',
userGuide = 'userGuide', userGuide = 'userGuide',
questionInputNode = 'questionInput', questionInput = 'questionInput',
historyNode = 'historyNode', historyNode = 'historyNode',
chatNode = 'chatNode', chatNode = 'chatNode',
kbSearchNode = 'kbSearchNode', kbSearchNode = 'kbSearchNode',
...@@ -30,6 +31,11 @@ export enum FlowModuleTypeEnum { ...@@ -30,6 +31,11 @@ export enum FlowModuleTypeEnum {
classifyQuestion = 'classifyQuestion' classifyQuestion = 'classifyQuestion'
} }
export const initModuleType: Record<string, boolean> = {
[FlowModuleTypeEnum.historyNode]: true,
[FlowModuleTypeEnum.questionInput]: true
};
export const edgeOptions = { export const edgeOptions = {
style: { style: {
strokeWidth: 1, strokeWidth: 1,
......
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { SystemInputEnum } from '@/constants/app';
import { ChatItemType } from '@/types/chat';
export type Props = {
maxContext: number;
[SystemInputEnum.history]: ChatItemType[];
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { maxContext = 5, history } = req.body as Props;
jsonRes(res, {
data: {
history: history.slice(-maxContext)
}
});
}
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { SystemInputEnum } from '@/constants/app';
export type Props = {
[SystemInputEnum.userChatInput]: string;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { userChatInput } = req.body as Props;
jsonRes(res, {
data: {
userChatInput
}
});
}
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import type { ChatItemType } from '@/types/chat';
import { ChatRoleEnum, rawSearchKey, responseDataKey } from '@/constants/chat';
import { modelToolMap } from '@/utils/plugin';
import { getVector } from '@/pages/api/openapi/plugin/vector';
import { countModelPrice, pushTaskBillListItem } from '@/service/events/pushBill';
import { getModel } from '@/service/utils/data';
import { authUser } from '@/service/utils/auth';
import type { SelectedKbType } from '@/types/plugin';
export type QuoteItemType = {
kb_id: string;
id: string;
q: string;
a: string;
source?: string;
};
type Props = {
kbList: SelectedKbType;
history: ChatItemType[];
similarity: number;
limit: number;
maxToken: number;
userChatInput: string;
stream?: boolean;
billId?: string;
};
type Response = {
[responseDataKey]: {
[rawSearchKey]: QuoteItemType[];
};
isEmpty?: boolean;
quotePrompt?: string;
};
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await authUser({ req, authRoot: true });
const { kbList = [], userChatInput } = req.body as Props;
if (!userChatInput) {
throw new Error('用户输入为空');
}
if (!Array.isArray(kbList) || kbList.length === 0) {
throw new Error('没有选择知识库');
}
const result = await kbSearch({
...req.body,
kbList,
userChatInput
});
jsonRes<Response>(res, {
data: result
});
} catch (err) {
console.log(err);
jsonRes(res, {
code: 500,
error: err
});
}
});
export async function kbSearch({
kbList = [],
history = [],
similarity = 0.8,
limit = 5,
maxToken = 2500,
userChatInput,
billId
}: Props): Promise<Response> {
if (kbList.length === 0) {
return Promise.reject('没有选择知识库');
}
// get vector
const vectorModel = global.vectorModels[0].model;
const { vectors, tokenLen } = await getVector({
model: vectorModel,
input: [userChatInput]
});
// search kb
const [res]: any = await Promise.all([
PgClient.query(
`BEGIN;
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
select kb_id,id,q,a,source from modelData where kb_id IN (${kbList
.map((item) => `'${item.kbId}'`)
.join(',')}) AND vector <#> '[${vectors[0]}]' < -${similarity} order by vector <#> '[${
vectors[0]
}]' limit ${limit};
COMMIT;`
),
pushTaskBillListItem({
billId,
moduleName: 'Vector Generate',
amount: countModelPrice({ model: vectorModel, tokens: tokenLen }),
model: getModel(vectorModel)?.name,
tokenLen
})
]);
const searchRes: QuoteItemType[] = res?.[2]?.rows || [];
// filter part quote by maxToken
const sliceResult = modelToolMap
.tokenSlice({
maxToken,
messages: searchRes.map((item, i) => ({
obj: ChatRoleEnum.System,
value: `${i + 1}: [${item.q}\n${item.a}]`
}))
})
.map((item) => item.value)
.join('\n')
.trim();
// slice filterSearch
const rawSearch = searchRes.slice(0, sliceResult.length);
return {
isEmpty: rawSearch.length === 0 ? true : undefined,
quotePrompt: sliceResult ? `知识库:\n${sliceResult}` : undefined,
responseData: {
rawSearch
}
};
}
...@@ -8,7 +8,7 @@ import { type ChatCompletionRequestMessage } from 'openai'; ...@@ -8,7 +8,7 @@ import { type ChatCompletionRequestMessage } from 'openai';
import { AppModuleItemType } from '@/types/app'; import { AppModuleItemType } from '@/types/app';
import { dispatchModules } from '../openapi/v1/chat/completions'; import { dispatchModules } from '../openapi/v1/chat/completions';
import { gptMessage2ChatType } from '@/utils/adapt'; import { gptMessage2ChatType } from '@/utils/adapt';
import { createTaskBill, delTaskBill, finishTaskBill } from '@/service/events/pushBill'; import { pushTaskBill } from '@/service/events/pushBill';
import { BillSourceEnum } from '@/constants/user'; import { BillSourceEnum } from '@/constants/user';
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string }; export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
...@@ -31,7 +31,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -31,7 +31,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}); });
let { modules = [], history = [], prompt, variables = {}, appName, appId } = req.body as Props; let { modules = [], history = [], prompt, variables = {}, appName, appId } = req.body as Props;
let billId = '';
try { try {
if (!history || !modules || !prompt) { if (!history || !modules || !prompt) {
throw new Error('Prams Error'); throw new Error('Prams Error');
...@@ -45,13 +44,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -45,13 +44,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
/* user auth */ /* user auth */
const { userId } = await authUser({ req }); const { userId } = await authUser({ req });
billId = await createTaskBill({
userId,
appName,
appId,
source: BillSourceEnum.fastgpt
});
/* start process */ /* start process */
const { responseData } = await dispatchModules({ const { responseData } = await dispatchModules({
res, res,
...@@ -61,8 +53,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -61,8 +53,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
history: gptMessage2ChatType(history), history: gptMessage2ChatType(history),
userChatInput: prompt userChatInput: prompt
}, },
stream: true, stream: true
billId
}); });
sseResponse({ sseResponse({
...@@ -77,12 +68,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ...@@ -77,12 +68,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}); });
res.end(); res.end();
// bill pushTaskBill({
finishTaskBill({ appName,
billId appId,
userId,
source: BillSourceEnum.fastgpt,
response: responseData
}); });
} catch (err: any) { } catch (err: any) {
delTaskBill(billId);
res.status(500); res.status(500);
sseErrRes(res, err); sseErrRes(res, err);
res.end(); res.end();
......
...@@ -2,21 +2,29 @@ import type { NextApiRequest, NextApiResponse } from 'next'; ...@@ -2,21 +2,29 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authUser, authApp, authShareChat } from '@/service/utils/auth'; import { authUser, authApp, authShareChat } from '@/service/utils/auth';
import { sseErrRes, jsonRes } from '@/service/response'; import { sseErrRes, jsonRes } from '@/service/response';
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
import { withNextCors } from '@/service/utils/tools'; import { withNextCors } from '@/service/utils/tools';
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
import {
dispatchHistory,
dispatchChatInput,
dispatchChatCompletion,
dispatchKBSearch,
dispatchAnswer,
dispatchClassifyQuestion
} from '@/service/moduleDispatch';
import type { CreateChatCompletionRequest } from 'openai'; import type { CreateChatCompletionRequest } from 'openai';
import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt'; import { gptMessage2ChatType } from '@/utils/adapt';
import { getChatHistory } from './getHistory'; import { getChatHistory } from './getHistory';
import { saveChat } from '@/pages/api/chat/saveChat'; import { saveChat } from '@/pages/api/chat/saveChat';
import { sseResponse } from '@/service/utils/tools'; import { sseResponse } from '@/service/utils/tools';
import { type ChatCompletionRequestMessage } from 'openai'; import { type ChatCompletionRequestMessage } from 'openai';
import { TaskResponseKeyEnum, AppModuleItemTypeEnum } from '@/constants/app'; import { TaskResponseKeyEnum } from '@/constants/chat';
import { FlowModuleTypeEnum, initModuleType } from '@/constants/flow';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { moduleFetch } from '@/service/api/request';
import { AppModuleItemType, RunningModuleItemType } from '@/types/app'; import { AppModuleItemType, RunningModuleItemType } from '@/types/app';
import { FlowInputItemTypeEnum } from '@/constants/flow'; import { pushTaskBill } from '@/service/events/pushBill';
import { finishTaskBill, createTaskBill, delTaskBill } from '@/service/events/pushBill';
import { BillSourceEnum } from '@/constants/user'; import { BillSourceEnum } from '@/constants/user';
import { ChatHistoryItemResType } from '@/types/chat';
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string }; export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
type FastGptWebChatProps = { type FastGptWebChatProps = {
...@@ -49,8 +57,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -49,8 +57,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
let { chatId, appId, shareId, stream = false, messages = [], variables = {} } = req.body as Props; let { chatId, appId, shareId, stream = false, messages = [], variables = {} } = req.body as Props;
let billId = '';
try { try {
if (!messages) { if (!messages) {
throw new Error('Prams Error'); throw new Error('Prams Error');
...@@ -105,13 +111,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -105,13 +111,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
res.setHeader('newChatId', String(newChatId)); res.setHeader('newChatId', String(newChatId));
} }
billId = await createTaskBill({
userId,
appName: app.name,
appId,
source: authType === 'apikey' ? BillSourceEnum.api : BillSourceEnum.fastgpt
});
/* start process */ /* start process */
const { responseData, answerText } = await dispatchModules({ const { responseData, answerText } = await dispatchModules({
res, res,
...@@ -121,9 +120,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -121,9 +120,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
history: prompts, history: prompts,
userChatInput: prompt.value userChatInput: prompt.value
}, },
stream, stream
billId
}); });
console.log(responseData, '===', answerText);
if (!answerText) { if (!answerText) {
throw new Error('回复内容为空,可能模块编排出现问题'); throw new Error('回复内容为空,可能模块编排出现问题');
...@@ -169,10 +168,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -169,10 +168,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
res.end(); res.end();
} else { } else {
res.json({ res.json({
data: { responseData,
newChatId,
...responseData
},
id: chatId || '', id: chatId || '',
model: '', model: '',
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
...@@ -186,14 +182,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ...@@ -186,14 +182,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}); });
} }
// bill pushTaskBill({
finishTaskBill({ appName: app.name,
billId, appId,
shareId userId,
source: authType === 'apikey' ? BillSourceEnum.api : BillSourceEnum.fastgpt,
response: responseData
}); });
} catch (err: any) { } catch (err: any) {
delTaskBill(billId);
if (stream) { if (stream) {
sseErrRes(res, err); sseErrRes(res, err);
res.end(); res.end();
...@@ -211,35 +207,29 @@ export async function dispatchModules({ ...@@ -211,35 +207,29 @@ export async function dispatchModules({
modules, modules,
params = {}, params = {},
variables = {}, variables = {},
stream = false, stream = false
billId
}: { }: {
res: NextApiResponse; res: NextApiResponse;
modules: AppModuleItemType[]; modules: AppModuleItemType[];
params?: Record<string, any>; params?: Record<string, any>;
variables?: Record<string, any>; variables?: Record<string, any>;
billId: string;
stream?: boolean; stream?: boolean;
}) { }) {
const runningModules = loadModules(modules, variables); const runningModules = loadModules(modules, variables);
// let storeData: Record<string, any> = {}; // after module used // let storeData: Record<string, any> = {}; // after module used
let chatResponse: Record<string, any> = {}; // response request and save to database let chatResponse: ChatHistoryItemResType[] = []; // response request and save to database
let answerText = ''; // AI answer let chatAnswerText = ''; // AI answer
function pushStore({ function pushStore({
answer, answerText = '',
responseData = {} responseData
}: { }: {
answer?: string; answerText?: string;
responseData?: Record<string, any>; responseData?: ChatHistoryItemResType;
}) { }) {
chatResponse = { responseData && chatResponse.push(responseData);
...chatResponse, chatAnswerText += answerText;
...responseData
};
answerText += answer;
} }
function moduleInput( function moduleInput(
module: RunningModuleItemType, module: RunningModuleItemType,
...@@ -292,63 +282,45 @@ export async function dispatchModules({ ...@@ -292,63 +282,45 @@ export async function dispatchModules({
} }
async function moduleRun(module: RunningModuleItemType): Promise<any> { async function moduleRun(module: RunningModuleItemType): Promise<any> {
if (res.closed) return Promise.resolve(); if (res.closed) return Promise.resolve();
console.log('run=========', module.type, module.url); console.log('run=========', module.flowType);
// direct answer
if (module.type === AppModuleItemTypeEnum.answer) {
const text =
module.inputs.find((item) => item.key === TaskResponseKeyEnum.answerText)?.value || '';
pushStore({
answer: text
});
return StreamAnswer({
res,
stream,
text: text
});
}
if (module.type === AppModuleItemTypeEnum.switch) { // get fetch params
return moduleOutput(module, switchResponse(module)); const params: Record<string, any> = {};
} module.inputs.forEach((item: any) => {
params[item.key] = item.value;
});
const props: Record<string, any> = {
res,
stream,
...params
};
if ( const dispatchRes = await (async () => {
(module.type === AppModuleItemTypeEnum.http || const callbackMap: Record<string, Function> = {
module.type === AppModuleItemTypeEnum.initInput) && [FlowModuleTypeEnum.historyNode]: dispatchHistory,
module.url [FlowModuleTypeEnum.questionInput]: dispatchChatInput,
) { [FlowModuleTypeEnum.answerNode]: dispatchAnswer,
// get fetch params [FlowModuleTypeEnum.chatNode]: dispatchChatCompletion,
const params: Record<string, any> = {}; [FlowModuleTypeEnum.kbSearchNode]: dispatchKBSearch,
module.inputs.forEach((item: any) => { [FlowModuleTypeEnum.classifyQuestion]: dispatchClassifyQuestion
params[item.key] = item.value;
});
const data = {
stream,
billId,
...params
}; };
if (callbackMap[module.flowType]) {
return callbackMap[module.flowType](props);
}
return {};
})();
// response data return moduleOutput(module, dispatchRes);
const fetchRes = await moduleFetch({
res,
url: module.url,
data
});
return moduleOutput(module, fetchRes);
}
} }
// start process width initInput // start process width initInput
const initModules = runningModules.filter( const initModules = runningModules.filter((item) => initModuleType[item.flowType]);
(item) => item.type === AppModuleItemTypeEnum.initInput
);
await Promise.all(initModules.map((module) => moduleInput(module, params))); await Promise.all(initModules.map((module) => moduleInput(module, params)));
return { return {
responseData: chatResponse, [TaskResponseKeyEnum.answerText]: chatAnswerText,
answerText [TaskResponseKeyEnum.responseData]: chatResponse
}; };
} }
...@@ -359,10 +331,9 @@ function loadModules( ...@@ -359,10 +331,9 @@ function loadModules(
return modules.map((module) => { return modules.map((module) => {
return { return {
moduleId: module.moduleId, moduleId: module.moduleId,
type: module.type, flowType: module.flowType,
url: module.url,
inputs: module.inputs inputs: module.inputs
.filter((item) => item.type !== FlowInputItemTypeEnum.target || item.connected) // filter unconnected target input .filter((item) => item.connected) // filter unconnected target input
.map((item) => { .map((item) => {
if (typeof item.value !== 'string') { if (typeof item.value !== 'string') {
return { return {
...@@ -385,38 +356,9 @@ function loadModules( ...@@ -385,38 +356,9 @@ function loadModules(
outputs: module.outputs.map((item) => ({ outputs: module.outputs.map((item) => ({
key: item.key, key: item.key,
answer: item.key === TaskResponseKeyEnum.answerText, answer: item.key === TaskResponseKeyEnum.answerText,
response: item.response,
value: undefined, value: undefined,
targets: item.targets targets: item.targets
})) }))
}; };
}); });
} }
function StreamAnswer({
res,
stream = false,
text = ''
}: {
res: NextApiResponse;
stream?: boolean;
text?: string;
}) {
if (stream && text) {
return sseResponse({
res,
event: sseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: text.replace(/\\n/g, '\n')
})
});
}
return text;
}
function switchResponse(module: RunningModuleItemType) {
const val = module?.inputs?.[0]?.value;
if (val) {
return { true: 1 };
}
return { false: 1 };
}
...@@ -43,7 +43,7 @@ const NodeChat = ({ ...@@ -43,7 +43,7 @@ const NodeChat = ({
return ( return (
<MySelect <MySelect
width={'100%'} width={'100%'}
value={inputItem.value || chatModelList[0]?.model} value={inputItem.value}
list={list} list={list}
onchange={(e) => { onchange={(e) => {
onChangeNode({ onChangeNode({
......
import React from 'react';
import { NodeProps } from 'reactflow';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
const NodeAnswer = ({ data: { ...props } }: NodeProps<FlowModuleItemType>) => {
return <NodeCard {...props}></NodeCard>;
};
export default React.memo(NodeAnswer);
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { Box, Flex, useOutsideClick } from '@chakra-ui/react'; import { Box, Flex, useOutsideClick } from '@chakra-ui/react';
import { ModuleTemplates } from '@/constants/flow/ModuleTemplate'; import { ModuleTemplates } from '@/constants/flow/ModuleTemplate';
import type { AppModuleTemplateItemType } from '@/types/app'; import type { FlowModuleItemType } from '@/types/app';
import type { XYPosition } from 'reactflow'; import type { XYPosition } from 'reactflow';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
...@@ -12,7 +12,7 @@ const ModuleStoreList = ({ ...@@ -12,7 +12,7 @@ const ModuleStoreList = ({
onClose onClose
}: { }: {
isOpen: boolean; isOpen: boolean;
onAddNode: (e: { template: AppModuleTemplateItemType; position: XYPosition }) => void; onAddNode: (e: { template: FlowModuleItemType; position: XYPosition }) => void;
onClose: () => void; onClose: () => void;
}) => { }) => {
const { isPc } = useGlobalStore(); const { isPc } = useGlobalStore();
......
...@@ -7,7 +7,7 @@ import MyTooltip from '@/components/MyTooltip'; ...@@ -7,7 +7,7 @@ import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon } from '@chakra-ui/icons';
type Props = { type Props = {
children: React.ReactNode | React.ReactNode[] | string; children?: React.ReactNode | React.ReactNode[] | string;
logo: string; logo: string;
name: string; name: string;
description?: string; description?: string;
......
...@@ -12,13 +12,20 @@ import ReactFlow, { ...@@ -12,13 +12,20 @@ import ReactFlow, {
} from 'reactflow'; } from 'reactflow';
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react'; import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons'; import { SmallCloseIcon } from '@chakra-ui/icons';
import { edgeOptions, connectionLineStyle, FlowModuleTypeEnum } from '@/constants/flow'; import {
edgeOptions,
connectionLineStyle,
FlowModuleTypeEnum,
FlowInputItemTypeEnum
} from '@/constants/flow';
import { appModule2FlowNode, appModule2FlowEdge } from '@/utils/adapt'; import { appModule2FlowNode, appModule2FlowEdge } from '@/utils/adapt';
import { import {
FlowModuleItemType, FlowModuleItemType,
FlowModuleTemplateType,
FlowOutputTargetItemType, FlowOutputTargetItemType,
type FlowModuleItemChangeProps type FlowModuleItemChangeProps
} from '@/types/flow'; } from '@/types/flow';
import { AppModuleItemType } from '@/types/app';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
import { putAppById } from '@/api/app'; import { putAppById } from '@/api/app';
import { useRequest } from '@/hooks/useRequest'; import { useRequest } from '@/hooks/useRequest';
...@@ -61,20 +68,20 @@ const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'), ...@@ -61,20 +68,20 @@ const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'),
import 'reactflow/dist/style.css'; import 'reactflow/dist/style.css';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { AppModuleItemType, AppModuleTemplateItemType } from '@/types/app';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
const nodeTypes = { const nodeTypes = {
[FlowModuleTypeEnum.userGuide]: NodeUserGuide, [FlowModuleTypeEnum.userGuide]: NodeUserGuide,
[FlowModuleTypeEnum.variable]: NodeVariable, [FlowModuleTypeEnum.variable]: NodeVariable,
[FlowModuleTypeEnum.questionInputNode]: NodeQuestionInput, [FlowModuleTypeEnum.questionInput]: NodeQuestionInput,
[FlowModuleTypeEnum.historyNode]: NodeHistory, [FlowModuleTypeEnum.historyNode]: NodeHistory,
[FlowModuleTypeEnum.chatNode]: NodeChat, [FlowModuleTypeEnum.chatNode]: NodeChat,
[FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch, [FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch,
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch, [FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
[FlowModuleTypeEnum.answerNode]: NodeAnswer, [FlowModuleTypeEnum.answerNode]: NodeAnswer,
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode [FlowModuleTypeEnum.classifyQuestion]: NodeCQNode
// [FlowModuleTypeEnum.empty]: EmptyModule
}; };
const edgeTypes = { const edgeTypes = {
buttonedge: ButtonEdge buttonedge: ButtonEdge
...@@ -147,7 +154,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { ...@@ -147,7 +154,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
[setEdges, setNodes] [setEdges, setNodes]
); );
const onAddNode = useCallback( const onAddNode = useCallback(
({ template, position }: { template: AppModuleTemplateItemType; position: XYPosition }) => { ({ template, position }: { template: FlowModuleItemType; position: XYPosition }) => {
if (!reactFlowWrapper.current) return; if (!reactFlowWrapper.current) return;
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect(); const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100; const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
...@@ -158,8 +165,8 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { ...@@ -158,8 +165,8 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
appModule2FlowNode({ appModule2FlowNode({
item: { item: {
...template, ...template,
position: { x: mouseX, y: mouseY }, moduleId: nanoid(),
moduleId: nanoid() position: { x: mouseX, y: mouseY }
}, },
onChangeNode, onChangeNode,
onDelNode onDelNode
...@@ -169,14 +176,18 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { ...@@ -169,14 +176,18 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
}, },
[onChangeNode, onDelNode, setNodes, x, y, zoom] [onChangeNode, onDelNode, setNodes, x, y, zoom]
); );
const flow2Modules = useCallback(() => { const flow2AppModules = useCallback(() => {
const modules: AppModuleItemType[] = nodes.map((item) => ({ const modules: AppModuleItemType[] = nodes.map((item) => ({
...item.data, moduleId: item.data.moduleId,
position: item.position, position: item.position,
onChangeNode: undefined, flowType: item.data.flowType,
onDelNode: undefined, inputs: item.data.inputs.map((item) => ({
outputs: item.data.outputs.map((output) => ({ key: item.key,
...output, value: item.value,
connected: item.type !== FlowInputItemTypeEnum.target
})),
outputs: item.data.outputs.map((item) => ({
key: item.key,
targets: [] as FlowOutputTargetItemType[] targets: [] as FlowOutputTargetItemType[]
})) }))
})); }));
...@@ -184,9 +195,11 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { ...@@ -184,9 +195,11 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
// update inputs and outputs // update inputs and outputs
modules.forEach((module) => { modules.forEach((module) => {
module.inputs.forEach((input) => { module.inputs.forEach((input) => {
input.connected = !!edges.find( input.connected =
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key input.connected ||
); !!edges.find(
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key
);
}); });
module.outputs.forEach((output) => { module.outputs.forEach((output) => {
output.targets = edges output.targets = edges
...@@ -233,7 +246,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { ...@@ -233,7 +246,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
const { mutate: onclickSave, isLoading } = useRequest({ const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: () => { mutationFn: () => {
return putAppById(app._id, { return putAppById(app._id, {
modules: flow2Modules() modules: flow2AppModules()
}); });
}, },
successToast: '保存配置成功', successToast: '保存配置成功',
...@@ -270,6 +283,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { ...@@ -270,6 +283,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
useEffect(() => { useEffect(() => {
initData(JSON.parse(JSON.stringify(app))); initData(JSON.parse(JSON.stringify(app)));
}, [app, initData]); }, [app, initData]);
console.log(flow2AppModules());
return ( return (
<> <>
...@@ -340,7 +354,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { ...@@ -340,7 +354,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
aria-label={'save'} aria-label={'save'}
variant={'base'} variant={'base'}
onClick={() => { onClick={() => {
setTestModules(flow2Modules()); setTestModules(flow2AppModules());
}} }}
/> />
</MyTooltip> </MyTooltip>
......
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
interface ConfigType {
headers?: { [key: string]: string };
hold?: boolean;
timeout?: number;
}
interface ResponseDataType {
code: number;
message: string;
data: any;
}
/**
* 请求开始
*/
function requestStart(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig {
if (config.headers) {
// config.headers.Authorization = getToken();
}
return config;
}
/**
* 请求成功,检查请求头
*/
function responseSuccess(response: AxiosResponse<ResponseDataType>) {
return response;
}
/**
* 响应数据检查
*/
function checkRes(data: ResponseDataType) {
if (data === undefined) {
console.log('error->', data, 'data is empty');
return Promise.reject('服务器异常');
} else if (data.code < 200 || data.code >= 400) {
return Promise.reject(data);
}
return data.data;
}
/**
* 响应错误
*/
function responseError(err: any) {
console.log('error->', '请求错误', err);
if (!err) {
return Promise.reject({ message: '未知错误' });
}
if (typeof err === 'string') {
return Promise.reject({ message: err });
}
return Promise.reject(err);
}
/* 创建请求实例 */
const instance = axios.create({
timeout: 60000, // 超时时间
headers: {
'content-type': 'application/json'
}
});
/* 请求拦截 */
instance.interceptors.request.use(requestStart, (err) => Promise.reject(err));
/* 响应拦截 */
instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err));
function request(url: string, data: any, config: ConfigType, method: Method): any {
/* 去空 */
for (const key in data) {
if (data[key] === null || data[key] === undefined) {
delete data[key];
}
}
return instance
.request({
baseURL: `http://localhost:${process.env.PORT || 3000}/api`,
url,
method,
data: ['POST', 'PUT'].includes(method) ? data : null,
params: !['POST', 'PUT'].includes(method) ? data : null,
...config // 用户自定义配置,可以覆盖前面的配置
})
.then((res) => checkRes(res.data))
.catch((err) => responseError(err));
}
/**
* api请求方式
* @param {String} url
* @param {Any} params
* @param {Object} config
* @returns
*/
export function GET<T>(url: string, params = {}, config: ConfigType = {}): Promise<T> {
return request(url, params, config, 'GET');
}
export function POST<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'POST');
}
export function PUT<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'PUT');
}
export function DELETE<T>(url: string, data = {}, config: ConfigType = {}): Promise<T> {
return request(url, data, config, 'DELETE');
}
import { sseResponseEventEnum } from '@/constants/chat';
import { getErrText } from '@/utils/tools';
import { parseStreamChunk } from '@/utils/adapt';
import { NextApiResponse } from 'next';
import { sseResponse } from '../utils/tools';
import { TaskResponseKeyEnum } from '@/constants/app';
interface Props {
res: NextApiResponse; // 用于流转发
url: string;
data: Record<string, any>;
}
export const moduleFetch = ({ url, data, res }: Props) =>
new Promise<Record<string, any>>(async (resolve, reject) => {
try {
const abortSignal = new AbortController();
const baseUrl = `http://localhost:${process.env.PORT || 3000}/api`;
const requestUrl = url.startsWith('/') ? `${baseUrl}${url}` : url;
const response = await fetch(requestUrl, {
method: 'POST',
// @ts-ignore
headers: {
'Content-Type': 'application/json',
rootkey: process.env.ROOT_KEY
},
body: JSON.stringify(data),
signal: abortSignal.signal
});
if (response.status >= 300 || response.status < 200) {
const err = await response.json();
return reject(err);
}
if (!response?.body) {
throw new Error('Request Error');
}
const responseType = response.headers.get('content-type');
if (responseType && responseType.includes('application/json')) {
const jsonResponse = await response.json();
return resolve(jsonResponse?.data || {});
}
const reader = response.body?.getReader();
let chatResponse: Record<string, any> = {
[TaskResponseKeyEnum.answerText]: ''
};
const read = async () => {
try {
const { done, value } = await reader.read();
if (done) {
return resolve(chatResponse);
} else if (res.closed) {
resolve(chatResponse);
abortSignal.abort();
return;
}
const chunkResponse = parseStreamChunk(value);
chunkResponse.forEach((item) => {
// parse json data
const data = (() => {
try {
return JSON.parse(item.data);
} catch (error) {
return {};
}
})();
if (!res.closed && item.event === sseResponseEventEnum.moduleFetchResponse) {
chatResponse = {
...chatResponse,
...data
};
} else if (
!res.closed &&
item.event === sseResponseEventEnum.answer &&
data?.choices?.[0]?.delta
) {
// save answer
const answer: string = data?.choices?.[0].delta.content || '';
if (answer) {
chatResponse = {
...chatResponse,
[TaskResponseKeyEnum.answerText]:
chatResponse[TaskResponseKeyEnum.answerText] + answer
};
}
sseResponse({
res,
event: sseResponseEventEnum.answer,
data: JSON.stringify(data)
});
} else if (item.event === sseResponseEventEnum.error) {
return reject(data);
}
});
read();
} catch (err: any) {
if (err?.message === 'The operation was aborted.') {
return;
}
reject(getErrText(err, '请求异常'));
}
};
read();
} catch (err: any) {
console.log(err);
reject(getErrText(err, '请求异常'));
}
});
import { connectToDatabase, Bill, User, ShareChat } from '../mongo'; import { connectToDatabase, Bill, User, ShareChat } from '../mongo';
import { BillSourceEnum } from '@/constants/user'; import { BillSourceEnum } from '@/constants/user';
import { getModel } from '../utils/data'; import { getModel } from '../utils/data';
import type { BillListItemType } from '@/types/mongoSchema'; import { ChatHistoryItemResType } from '@/types/chat';
import { formatPrice } from '@/utils/user'; import { formatPrice } from '@/utils/user';
export const createTaskBill = async ({ export const pushTaskBill = async ({
appName, appName,
appId, appId,
userId, userId,
source source,
shareId,
response
}: { }: {
appName: string; appName: string;
appId: string; appId: string;
userId: string; userId: string;
source: `${BillSourceEnum}`; source: `${BillSourceEnum}`;
shareId?: string;
response: ChatHistoryItemResType[];
}) => { }) => {
const res = await Bill.create({ const total = response.reduce((sum, item) => sum + item.price, 0);
userId,
appName,
appId,
total: 0,
source,
list: []
});
return String(res._id);
};
export const pushTaskBillListItem = async ({
billId,
moduleName,
amount,
model,
tokenLen
}: { billId?: string } & BillListItemType) => {
if (!billId) return;
try {
await Bill.findByIdAndUpdate(billId, {
$push: {
list: {
moduleName,
amount,
model,
tokenLen
}
}
});
} catch (error) {}
};
export const finishTaskBill = async ({ billId, shareId }: { billId: string; shareId?: string }) => {
try {
// update bill
const res = await Bill.findByIdAndUpdate(billId, [
{
$set: {
total: {
$sum: '$list.amount'
},
time: new Date()
}
}
]);
if (!res) return;
const total = res.list.reduce((sum, item) => sum + item.amount, 0) || 0;
if (shareId) { await Promise.allSettled([
updateShareChatBill({ Bill.create({
shareId, userId,
total appName,
}); appId,
} total,
source,
console.log('finish bill:', formatPrice(total)); list: response.map((item) => ({
moduleName: item.moduleName,
// 账号扣费 amount: item.price || 0,
await User.findByIdAndUpdate(res.userId, { model: item.model,
tokenLen: item.tokens
}))
}),
User.findByIdAndUpdate(userId, {
$inc: { balance: -total } $inc: { balance: -total }
}); }),
} catch (error) { ...(shareId
console.log('Finish bill failed:', error); ? [
billId && Bill.findByIdAndDelete(billId); updateShareChatBill({
} shareId,
}; total
})
export const delTaskBill = async (billId?: string) => { ]
if (!billId) return; : [])
]);
try {
await Bill.findByIdAndRemove(billId); console.log('finish bill:', formatPrice(total));
} catch (error) {}
}; };
export const updateShareChatBill = async ({ export const updateShareChatBill = async ({
......
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { adaptChatItem_openAI } from '@/utils/plugin/openai'; import { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { ChatContextFilter } from '@/service/utils/chat/index'; import { ChatContextFilter } from '@/service/utils/chat/index';
import type { ChatItemType } from '@/types/chat'; import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import { ChatRoleEnum } from '@/constants/chat'; import { ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { getOpenAIApi, axiosConfig } from '@/service/ai/openai'; import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
import type { ClassifyQuestionAgentItemType } from '@/types/app'; import type { ClassifyQuestionAgentItemType } from '@/types/app';
import { countModelPrice, pushTaskBillListItem } from '@/service/events/pushBill'; import { countModelPrice } from '@/service/events/pushBill';
import { getModel } from '@/service/utils/data';
import { authUser } from '@/service/utils/auth';
export type Props = { export type CQProps = {
systemPrompt?: string; systemPrompt?: string;
history?: ChatItemType[]; history?: ChatItemType[];
userChatInput: string; userChatInput: string;
agents: ClassifyQuestionAgentItemType[]; agents: ClassifyQuestionAgentItemType[];
billId?: string;
}; };
export type Response = { history: ChatItemType[] }; export type CQResponse = {
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
[key: string]: any;
};
const moduleName = 'Classify Question';
const agentModel = 'gpt-3.5-turbo'; const agentModel = 'gpt-3.5-turbo';
const agentFunName = 'agent_user_question'; const agentFunName = 'agent_user_question';
const maxTokens = 2000;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await authUser({ req, authRoot: true });
let { userChatInput } = req.body as Props;
if (!userChatInput) {
throw new Error('userChatInput is empty');
}
const response = await classifyQuestion(req.body);
jsonRes(res, {
data: response
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
/* request openai chat */ /* request openai chat */
export async function classifyQuestion({ export const dispatchClassifyQuestion = async (props: Record<string, any>): Promise<CQResponse> => {
agents, const { agents, systemPrompt, history = [], userChatInput } = props as CQProps;
systemPrompt,
history = [],
userChatInput,
billId
}: Props) {
const messages: ChatItemType[] = [ const messages: ChatItemType[] = [
...(systemPrompt ...(systemPrompt
? [ ? [
...@@ -62,16 +35,16 @@ export async function classifyQuestion({ ...@@ -62,16 +35,16 @@ export async function classifyQuestion({
} }
] ]
: []), : []),
...history,
{ {
obj: ChatRoleEnum.Human, obj: ChatRoleEnum.Human,
value: userChatInput value: userChatInput
} }
]; ];
const filterMessages = ChatContextFilter({ const filterMessages = ChatContextFilter({
// @ts-ignore
model: agentModel, model: agentModel,
prompts: messages, prompts: messages,
maxTokens: 1500 maxTokens
}); });
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false }); const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
...@@ -112,27 +85,19 @@ export async function classifyQuestion({ ...@@ -112,27 +85,19 @@ export async function classifyQuestion({
throw new Error(''); throw new Error('');
} }
const totalTokens = response.data.usage?.total_tokens || 0; const tokens = response.data.usage?.total_tokens || 0;
await pushTaskBillListItem({ const result = agents.find((item) => item.key === arg.type) || agents[0];
billId,
moduleName: 'Classify Question',
amount: countModelPrice({ model: agentModel, tokens: totalTokens }),
model: getModel(agentModel)?.name,
tokenLen: totalTokens
});
console.log(agents.map((item) => `${item.value},返回: '${item.key}'`).join(';'), arg);
const result = agents.find((item) => item.key === arg.type);
if (result) {
return {
[arg.type]: 1
};
}
return { return {
[agents[0].key]: 1 [result.key]: 1,
[TaskResponseKeyEnum.responseData]: {
moduleName,
price: countModelPrice({ model: agentModel, tokens }),
model: agentModel,
tokens,
cqList: agents,
cqResult: result.value
}
}; };
} };
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction import type { NextApiResponse } from 'next';
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes, sseErrRes } from '@/service/response';
import { sseResponse } from '@/service/utils/tools'; import { sseResponse } from '@/service/utils/tools';
import { OpenAiChatEnum } from '@/constants/model'; import { OpenAiChatEnum } from '@/constants/model';
import { adaptChatItem_openAI, countOpenAIToken } from '@/utils/plugin/openai'; import { adaptChatItem_openAI, countOpenAIToken } from '@/utils/plugin/openai';
import { modelToolMap } from '@/utils/plugin'; import { modelToolMap } from '@/utils/plugin';
import { ChatContextFilter } from '@/service/utils/chat/index'; import { ChatContextFilter } from '@/service/utils/chat/index';
import type { ChatItemType } from '@/types/chat'; import type { ChatItemType, QuoteItemType } from '@/types/chat';
import type { ChatHistoryItemResType } from '@/types/chat';
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat'; import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
import { parseStreamChunk, textAdaptGptResponse } from '@/utils/adapt'; import { parseStreamChunk, textAdaptGptResponse } from '@/utils/adapt';
import { getOpenAIApi, axiosConfig } from '@/service/ai/openai'; import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
import { TaskResponseKeyEnum } from '@/constants/app'; import { TaskResponseKeyEnum } from '@/constants/chat';
import { getChatModel } from '@/service/utils/data'; import { getChatModel } from '@/service/utils/data';
import { countModelPrice, pushTaskBillListItem } from '@/service/events/pushBill'; import { countModelPrice } from '@/service/events/pushBill';
import { authUser } from '@/service/utils/auth';
export type Props = { export type ChatProps = {
res: NextApiResponse;
model: `${OpenAiChatEnum}`; model: `${OpenAiChatEnum}`;
temperature?: number; temperature?: number;
maxToken?: number; maxToken?: number;
history?: ChatItemType[]; history?: ChatItemType[];
userChatInput: string; userChatInput: string;
stream?: boolean; stream?: boolean;
quotePrompt?: string; quoteQA?: QuoteItemType[];
systemPrompt?: string; systemPrompt?: string;
limitPrompt?: string; limitPrompt?: string;
billId?: string;
}; };
export type Response = { [TaskResponseKeyEnum.answerText]: string; totalTokens: number }; export type ChatResponse = {
[TaskResponseKeyEnum.answerText]: string;
export default async function handler(req: NextApiRequest, res: NextApiResponse) { [TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
let { model, stream } = req.body as Props; };
try {
await authUser({ req, authRoot: true });
const response = await chatCompletion({
...req.body,
res,
model
});
if (stream) { const moduleName = 'AI Chat';
sseResponse({
res,
event: sseResponseEventEnum.moduleFetchResponse,
data: JSON.stringify(response)
});
res.end();
} else {
jsonRes(res, {
data: response
});
}
} catch (err) {
if (stream) {
sseErrRes(res, err);
res.end();
} else {
jsonRes(res, {
code: 500,
error: err
});
}
}
}
/* request openai chat */ /* request openai chat */
export async function chatCompletion({ export const dispatchChatCompletion = async (props: Record<string, any>): Promise<ChatResponse> => {
res, let {
model, res,
temperature = 0, model,
maxToken = 4000, temperature = 0,
stream = false, maxToken = 4000,
history = [], stream = false,
quotePrompt = '', history = [],
userChatInput, quoteQA = [],
systemPrompt = '', userChatInput,
limitPrompt = '', systemPrompt = '',
billId limitPrompt = ''
}: Props & { res: NextApiResponse }): Promise<Response> { } = props as ChatProps;
// temperature adapt // temperature adapt
const modelConstantsData = getChatModel(model); const modelConstantsData = getChatModel(model);
if (!modelConstantsData) { if (!modelConstantsData) {
return Promise.reject('The chat model is undefined'); return Promise.reject('The chat model is undefined, you need to select a chat model.');
} }
// FastGpt temperature range: 1~10 // FastGpt temperature range: 1~10
...@@ -91,12 +59,19 @@ export async function chatCompletion({ ...@@ -91,12 +59,19 @@ export async function chatCompletion({
const limitText = (() => { const limitText = (() => {
if (limitPrompt) return limitPrompt; if (limitPrompt) return limitPrompt;
if (quotePrompt && !limitPrompt) { if (quoteQA.length > 0 && !limitPrompt) {
return '根据知识库内容回答问题,仅回复知识库提供的内容。'; return '根据知识库内容回答问题,仅回复知识库提供的内容,不要对知识库内容做补充说明。';
} }
return ''; return '';
})(); })();
const quotePrompt =
quoteQA.length > 0
? `下面是知识库内容:
${quoteQA.map((item, i) => `${i + 1}. [${item.q}\n${item.a}]`).join('\n')}
`
: '';
const messages: ChatItemType[] = [ const messages: ChatItemType[] = [
...(quotePrompt ...(quotePrompt
? [ ? [
...@@ -138,6 +113,7 @@ export async function chatCompletion({ ...@@ -138,6 +113,7 @@ export async function chatCompletion({
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false }); const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
const chatAPI = getOpenAIApi(); const chatAPI = getOpenAIApi();
// console.log(adaptMessages);
/* count response max token */ /* count response max token */
const promptsToken = modelToolMap.countTokens({ const promptsToken = modelToolMap.countTokens({
...@@ -152,8 +128,8 @@ export async function chatCompletion({ ...@@ -152,8 +128,8 @@ export async function chatCompletion({
temperature: Number(temperature || 0), temperature: Number(temperature || 0),
max_tokens: maxToken, max_tokens: maxToken,
messages: adaptMessages, messages: adaptMessages,
// frequency_penalty: 0.5, // 越大,重复内容越少 frequency_penalty: 0.5, // 越大,重复内容越少
// presence_penalty: -0.5, // 越大,越容易出现新内容 presence_penalty: -0.5, // 越大,越容易出现新内容
stream stream
}, },
{ {
...@@ -163,7 +139,7 @@ export async function chatCompletion({ ...@@ -163,7 +139,7 @@ export async function chatCompletion({
} }
); );
const { answer, totalTokens } = await (async () => { const { answerText, totalTokens, finishMessages } = await (async () => {
if (stream) { if (stream) {
// sse response // sse response
const { answer } = await streamResponse({ res, response }); const { answer } = await streamResponse({ res, response });
...@@ -174,38 +150,45 @@ export async function chatCompletion({ ...@@ -174,38 +150,45 @@ export async function chatCompletion({
}); });
const totalTokens = countOpenAIToken({ const totalTokens = countOpenAIToken({
messages: finishMessages, messages: finishMessages
model: 'gpt-3.5-turbo-16k'
}); });
return { return {
answer, answerText: answer,
totalTokens totalTokens,
finishMessages
}; };
} else { } else {
const answer = stream ? '' : response.data.choices?.[0].message?.content || ''; const answer = stream ? '' : response.data.choices?.[0].message?.content || '';
const totalTokens = stream ? 0 : response.data.usage?.total_tokens || 0; const totalTokens = stream ? 0 : response.data.usage?.total_tokens || 0;
const finishMessages = filterMessages.concat({
obj: ChatRoleEnum.AI,
value: answer
});
return { return {
answer, answerText: answer,
totalTokens totalTokens,
finishMessages
}; };
} }
})(); })();
await pushTaskBillListItem({
billId,
moduleName: 'AI Chat',
amount: countModelPrice({ model, tokens: totalTokens }),
model: modelConstantsData.name,
tokenLen: totalTokens
});
return { return {
answerText: answer, [TaskResponseKeyEnum.answerText]: answerText,
totalTokens [TaskResponseKeyEnum.responseData]: {
moduleName,
price: countModelPrice({ model, tokens: totalTokens }),
model: modelConstantsData.name,
tokens: totalTokens,
question: userChatInput,
answer: answerText,
maxToken,
finishMessages
}
}; };
} };
async function streamResponse({ res, response }: { res: NextApiResponse; response: any }) { async function streamResponse({ res, response }: { res: NextApiResponse; response: any }) {
let answer = ''; let answer = '';
......
export * from './init/history';
export * from './init/userChatInput';
export * from './chat/oneapi';
export * from './kb/search';
export * from './tools/answer';
export * from './agent/classifyQuestion';
import { SystemInputEnum } from '@/constants/app';
import { ChatItemType } from '@/types/chat';
export type HistoryProps = {
maxContext: number;
[SystemInputEnum.history]: ChatItemType[];
};
export const dispatchHistory = (props: Record<string, any>) => {
const { maxContext = 5, history = [] } = props as HistoryProps;
return {
history: history.slice(-maxContext)
};
};
import { SystemInputEnum } from '@/constants/app';
export type UserChatInputProps = {
[SystemInputEnum.userChatInput]: string;
};
export const dispatchChatInput = (props: Record<string, any>) => {
const { userChatInput } = props as UserChatInputProps;
return {
userChatInput
};
};
import { PgClient } from '@/service/pg';
import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { getVector } from '@/pages/api/openapi/plugin/vector';
import { countModelPrice } from '@/service/events/pushBill';
import type { SelectedKbType } from '@/types/plugin';
import type { QuoteItemType } from '@/types/chat';
type KBSearchProps = {
kbList: SelectedKbType;
history: ChatItemType[];
similarity: number;
limit: number;
userChatInput: string;
};
export type KBSearchResponse = {
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
isEmpty?: boolean;
unEmpty?: boolean;
quoteQA: QuoteItemType[];
};
const moduleName = 'KB Search';
export async function dispatchKBSearch(props: Record<string, any>): Promise<KBSearchResponse> {
const {
kbList = [],
history = [],
similarity = 0.8,
limit = 5,
userChatInput
} = props as KBSearchProps;
if (kbList.length === 0) {
return Promise.reject("You didn't choose the knowledge base");
}
if (!userChatInput) {
return Promise.reject('Your input is empty');
}
// get vector
const vectorModel = global.vectorModels[0];
const { vectors, tokenLen } = await getVector({
model: vectorModel.model,
input: [userChatInput]
});
// search kb
const res: any = await PgClient.query(
`BEGIN;
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
select kb_id,id,q,a,source from modelData where kb_id IN (${kbList
.map((item) => `'${item.kbId}'`)
.join(',')}) AND vector <#> '[${vectors[0]}]' < -${similarity} order by vector <#> '[${
vectors[0]
}]' limit ${limit};
COMMIT;`
);
const searchRes: QuoteItemType[] = res?.[2]?.rows || [];
return {
isEmpty: searchRes.length === 0 ? true : undefined,
unEmpty: searchRes.length > 0 ? true : undefined,
quoteQA: searchRes,
responseData: {
moduleName,
price: countModelPrice({ model: vectorModel.model, tokens: tokenLen }),
model: vectorModel.name,
tokens: tokenLen,
similarity,
limit
}
};
}
import { sseResponseEventEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { sseResponse } from '@/service/utils/tools';
import { textAdaptGptResponse } from '@/utils/adapt';
import type { NextApiResponse } from 'next';
export type AnswerProps = {
res: NextApiResponse;
text: string;
stream: boolean;
};
export type AnswerResponse = {
[TaskResponseKeyEnum.answerText]: string;
};
export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
const { res, text = '', stream } = props as AnswerProps;
if (stream) {
sseResponse({
res,
event: sseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: text.replace(/\\n/g, '\n')
})
});
}
return {
[TaskResponseKeyEnum.answerText]: text
};
};
...@@ -5,7 +5,7 @@ import { ...@@ -5,7 +5,7 @@ import {
ModulesInputItemTypeEnum, ModulesInputItemTypeEnum,
VariableInputEnum VariableInputEnum
} from '../constants/app'; } from '../constants/app';
import type { FlowInputItemType, FlowOutputItemType } from './flow'; import type { FlowInputItemType, FlowOutputItemType, FlowOutputTargetItemType } from './flow';
import type { AppSchema, kbSchema } from './mongoSchema'; import type { AppSchema, kbSchema } from './mongoSchema';
import { ChatModelType } from '@/constants/model'; import { ChatModelType } from '@/constants/model';
...@@ -58,21 +58,12 @@ export type VariableItemType = { ...@@ -58,21 +58,12 @@ export type VariableItemType = {
}; };
/* app module */ /* app module */
export type AppModuleTemplateItemType = { export type AppModuleItemType = {
logo: string;
name: string;
description?: string;
intro: string;
flowType: `${FlowModuleTypeEnum}`;
type: `${AppModuleItemTypeEnum}`;
url?: string;
inputs: FlowInputItemType[];
outputs: FlowOutputItemType[];
};
export type AppModuleItemType = AppModuleTemplateItemType & {
moduleId: string; moduleId: string;
position?: XYPosition; position?: XYPosition;
flowType: `${FlowModuleTypeEnum}`;
inputs: { key: string; value?: any; connected?: boolean }[];
outputs: { key: string; targets: FlowOutputTargetItemType[] }[];
}; };
export type AppItemType = { export type AppItemType = {
...@@ -83,8 +74,7 @@ export type AppItemType = { ...@@ -83,8 +74,7 @@ export type AppItemType = {
export type RunningModuleItemType = { export type RunningModuleItemType = {
moduleId: string; moduleId: string;
type: `${AppModuleItemTypeEnum}`; flowType: `${FlowModuleTypeEnum}`;
url?: string;
inputs: { inputs: {
key: string; key: string;
value?: any; value?: any;
......
import { ChatRoleEnum, rawSearchKey } from '@/constants/chat'; import { ChatRoleEnum, rawSearchKey } from '@/constants/chat';
import type { InitChatResponse, InitShareChatResponse } from '@/api/response/chat'; import type { InitChatResponse, InitShareChatResponse } from '@/api/response/chat';
import { QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch'; import { TaskResponseKeyEnum } from '@/constants/chat';
import { ClassifyQuestionAgentItemType } from './app';
export type ExportChatType = 'md' | 'pdf' | 'html'; export type ExportChatType = 'md' | 'pdf' | 'html';
...@@ -37,3 +38,33 @@ export type ShareChatHistoryItemType = HistoryItemType & { ...@@ -37,3 +38,33 @@ export type ShareChatHistoryItemType = HistoryItemType & {
export type ShareChatType = InitShareChatResponse & { export type ShareChatType = InitShareChatResponse & {
history: ShareChatHistoryItemType; history: ShareChatHistoryItemType;
}; };
export type QuoteItemType = {
kb_id: string;
id: string;
q: string;
a: string;
source?: string;
};
export type ChatHistoryItemResType = {
moduleName: string;
price: number;
model?: string;
tokens?: number;
// chat
answer?: string;
question?: string;
temperature?: number;
maxToken?: number;
finishMessages?: ChatItemType[];
// kb search
similarity?: number;
limit?: number;
// cq
cqList?: ClassifyQuestionAgentItemType[];
cqResult?: string;
};
...@@ -5,6 +5,15 @@ import { ...@@ -5,6 +5,15 @@ import {
} from '@/constants/flow'; } from '@/constants/flow';
import { Connection } from 'reactflow'; import { Connection } from 'reactflow';
import type { AppModuleItemType } from './app'; import type { AppModuleItemType } from './app';
import { FlowModuleTypeEnum } from '@/constants/flow';
export type FlowModuleItemChangeProps = {
moduleId: string;
type?: 'inputs' | 'outputs';
key: string;
value: any;
valueKey?: keyof FlowInputItemType & keyof FlowBodyItemType;
};
export type FlowInputItemType = { export type FlowInputItemType = {
key: string; // 字段名 key: string; // 字段名
...@@ -31,19 +40,21 @@ export type FlowOutputItemType = { ...@@ -31,19 +40,21 @@ export type FlowOutputItemType = {
label: string; label: string;
description?: string; description?: string;
type: `${FlowOutputItemTypeEnum}`; type: `${FlowOutputItemTypeEnum}`;
response?: boolean;
targets: FlowOutputTargetItemType[]; targets: FlowOutputTargetItemType[];
}; };
export type FlowModuleItemChangeProps = { export type FlowModuleTemplateType = {
moduleId: string; logo: string;
type?: 'inputs' | 'outputs'; name: string;
key: string; description?: string;
value: any; intro: string;
valueKey?: keyof FlowInputItemType & keyof FlowBodyItemType; flowType: `${FlowModuleTypeEnum}`;
url?: string;
inputs: FlowInputItemType[];
outputs: FlowOutputItemType[];
}; };
export type FlowModuleItemType = FlowModuleTemplateType & {
export type FlowModuleItemType = AppModuleItemType & { moduleId: string;
onChangeNode: (e: FlowModuleItemChangeProps) => void; onChangeNode: (e: FlowModuleItemChangeProps) => void;
onDelNode: (id: string) => void; onDelNode: (id: string) => void;
}; };
...@@ -6,11 +6,11 @@ import { ChatCompletionRequestMessageRoleEnum } from 'openai'; ...@@ -6,11 +6,11 @@ import { ChatCompletionRequestMessageRoleEnum } from 'openai';
import { ChatRoleEnum } from '@/constants/chat'; import { ChatRoleEnum } from '@/constants/chat';
import type { MessageItemType } from '@/pages/api/openapi/v1/chat/completions'; import type { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
import type { AppModuleItemType } from '@/types/app'; import type { AppModuleItemType } from '@/types/app';
import type { FlowModuleItemType } from '@/types/flow'; import type { FlowModuleItemType, FlowModuleTemplateType } from '@/types/flow';
import type { Edge, Node } from 'reactflow'; import type { Edge, Node } from 'reactflow';
import { connectionLineStyle } from '@/constants/flow'; import { connectionLineStyle } from '@/constants/flow';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
import { ModuleTemplates } from '@/constants/flow/ModuleTemplate'; import { EmptyModule, ModuleTemplates, ModuleTemplatesFlat } from '@/constants/flow/ModuleTemplate';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
export const adaptBill = (bill: BillSchema): UserBillType => { export const adaptBill = (bill: BillSchema): UserBillType => {
...@@ -92,48 +92,41 @@ export const appModule2FlowNode = ({ ...@@ -92,48 +92,41 @@ export const appModule2FlowNode = ({
}): Node<FlowModuleItemType> => { }): Node<FlowModuleItemType> => {
// init some static data // init some static data
const template = const template =
ModuleTemplates.map((templates) => templates.list) ModuleTemplatesFlat.find((template) => template.flowType === item.flowType) || EmptyModule;
?.flat()
.find((template) => template.flowType === item.flowType) || item;
// replace item data // replace item data
const moduleItem = { const moduleItem: FlowModuleItemType = {
...item, ...item,
logo: template.logo, logo: template.logo,
name: template.name, name: template.name,
intro: template.intro, intro: template.intro,
type: template.type,
url: template.url, url: template.url,
inputs: template.inputs.map((templateInput) => { inputs: template.inputs.map((templateInput) => {
// use latest inputs // use latest inputs
const itemInput = item.inputs.find((item) => item.key === templateInput.key) || templateInput; const itemInput = item.inputs.find((item) => item.key === templateInput.key) || templateInput;
return { return {
...templateInput, ...templateInput,
key: itemInput.key,
value: itemInput.value value: itemInput.value
}; };
}), }),
outputs: item.outputs.map((itemOutput) => { outputs: template.outputs.map((templateOutput) => {
// unChange outputs // unChange outputs
const templateOutput = const itemOutput =
template.outputs.find((item) => item.key === itemOutput.key) || itemOutput; item.outputs.find((item) => item.key === templateOutput.key) || templateOutput;
return { return {
...templateOutput, ...templateOutput,
key: itemOutput.key,
targets: itemOutput.targets || [] targets: itemOutput.targets || []
}; };
}) }),
onChangeNode,
onDelNode
}; };
return { return {
id: item.moduleId, id: item.moduleId,
type: item.flowType, type: item.flowType,
data: { data: moduleItem,
...moduleItem,
onChangeNode,
onDelNode
},
position: item.position || { x: 0, y: 0 } position: item.position || { x: 0, y: 0 }
}; };
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment