From ce7da2db66476c8e71d1f227600b0fb02a036717 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Tue, 19 Sep 2023 16:10:30 +0800 Subject: [PATCH] Optimize chat reponse data (#322) --- client/public/locales/en/common.json | 11 +++- client/public/locales/zh/common.json | 11 +++- client/src/api/service/plugins.ts | 14 +++-- .../src/components/ChatBox/ResponseTags.tsx | 23 +++---- .../components/ChatBox/WholeResponseModal.tsx | 2 +- client/src/components/Icon/index.tsx | 2 +- client/src/constants/chat.ts | 8 --- client/src/constants/flow/ModuleTemplate.ts | 49 ++++----------- .../pages/api/openapi/v1/chat/completions.ts | 14 +++-- .../AdEdit/components/modules/NodeCard.tsx | 45 +++++++++---- .../app/detail/components/AdEdit/index.tsx | 63 +++++++------------ client/src/service/events/pushBill.ts | 2 +- client/src/service/models/chat.ts | 19 +----- .../moduleDispatch/agent/classifyQuestion.ts | 5 +- .../service/moduleDispatch/agent/extract.ts | 5 +- .../src/service/moduleDispatch/chat/oneapi.ts | 19 ++++-- .../src/service/moduleDispatch/kb/search.ts | 5 +- .../src/service/moduleDispatch/tools/http.ts | 7 ++- client/src/service/response.ts | 6 +- client/src/types/chat.d.ts | 8 ++- client/src/types/flow.d.ts | 6 +- client/src/utils/adapt.ts | 2 +- 22 files changed, 157 insertions(+), 169 deletions(-) diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json index 1e3ee46df..21da3b864 100644 --- a/client/public/locales/en/common.json +++ b/client/public/locales/en/common.json @@ -14,7 +14,6 @@ "UnKnow": "UnKnow", "Warning": "Warning", "app": { - "Quote Prompt Settings": "Quote Prompt Settings", "Advance App TestTip": "The current application is advanced editing mode \n. If you need to switch to [simple mode], please click the save button on the left", "App Detail": "App Detail", "Chat Logs Tips": "Logs record the app's online, shared, and API(chatId is existing) conversations", @@ -40,7 +39,14 @@ "My Apps": "My Apps", "Output Field Settings": "Output Field Settings", "Paste Config": "Paste Config", - "Variable Key Repeat Tip": "Variable Key Repeat" + "Quote Prompt Settings": "Quote Prompt Settings", + "Variable Key Repeat Tip": "Variable Key Repeat", + "module": { + "Custom Title Tip": "The title name is displayed during the conversation" + }, + "modules": { + "Title is required": "Title is required" + } }, "chat": { "Admin Mark Content": "Corrected response", @@ -81,6 +87,7 @@ "Copy": "Copy", "Copy Successful": "Copy Successful", "Course": "", + "Custom Title": "Custom Title", "Delete": "Delete", "Delete Failed": "Delete Failed", "Delete Success": "Delete Successful", diff --git a/client/public/locales/zh/common.json b/client/public/locales/zh/common.json index eb910068f..b8606e96e 100644 --- a/client/public/locales/zh/common.json +++ b/client/public/locales/zh/common.json @@ -14,7 +14,6 @@ "UnKnow": "未知", "Warning": "提示", "app": { - "Quote Prompt Settings": "引用提示词配置", "Advance App TestTip": "当前应用为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键", "App Detail": "应用详情", "Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录", @@ -40,7 +39,14 @@ "My Apps": "我的应用", "Output Field Settings": "输出字段编辑", "Paste Config": "粘贴配置", - "Variable Key Repeat Tip": "变量 key 重复" + "Quote Prompt Settings": "引用提示词配置", + "Variable Key Repeat Tip": "变量 key 重复", + "module": { + "Custom Title Tip": "该标题名字会展示在对话过程中" + }, + "modules": { + "Title is required": "模块名不能为空" + } }, "chat": { "Admin Mark Content": "纠正后的回复", @@ -81,6 +87,7 @@ "Copy": "复制", "Copy Successful": "复制成功", "Course": "", + "Custom Title": "自定义标题", "Delete": "删除", "Delete Failed": "删除失败", "Delete Success": "删除成功", diff --git a/client/src/api/service/plugins.ts b/client/src/api/service/plugins.ts index aa62a31b8..1bae574ea 100644 --- a/client/src/api/service/plugins.ts +++ b/client/src/api/service/plugins.ts @@ -1,8 +1,12 @@ import { GET, POST } from './request'; export const textCensor = (data: { text: string }) => - POST<{ code?: number; message: string }>('/plugins/censor/text_baidu', data).then((res) => { - if (res?.code === 5000) { - return Promise.reject(res.message); - } - }); + POST<{ code?: number; message: string }>('/plugins/censor/text_baidu', data) + .then((res) => { + if (res?.code === 5000) { + return Promise.reject(res.message); + } + }) + .catch((err) => { + return Promise.resolve(''); + }); diff --git a/client/src/components/ChatBox/ResponseTags.tsx b/client/src/components/ChatBox/ResponseTags.tsx index 0f68360eb..c61ddc568 100644 --- a/client/src/components/ChatBox/ResponseTags.tsx +++ b/client/src/components/ChatBox/ResponseTags.tsx @@ -1,5 +1,4 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { ChatModuleEnum } from '@/constants/chat'; import { ChatHistoryItemResType, ChatItemType, QuoteItemType } from '@/types/chat'; import { Flex, BoxProps, useDisclosure } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; @@ -7,6 +6,8 @@ import { useGlobalStore } from '@/store/global'; import dynamic from 'next/dynamic'; import Tag from '../Tag'; import MyTooltip from '../MyTooltip'; +import { FlowModuleTypeEnum } from '@/constants/flow'; + const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false }); const ContextModal = dynamic(() => import('./ContextModal'), { ssr: false }); const WholeResponseModal = dynamic(() => import('./WholeResponseModal'), { ssr: false }); @@ -32,15 +33,15 @@ const ResponseTags = ({ const { quoteList = [], - completeMessages = [], - tokens = 0 + historyPreview = [], + runningTime = 0 } = useMemo(() => { - const chatData = responseData.find((item) => item.moduleName === ChatModuleEnum.AIChat); + const chatData = responseData.find((item) => item.moduleType === FlowModuleTypeEnum.chatNode); if (!chatData) return {}; return { quoteList: chatData.quoteList, - completeMessages: chatData.completeMessages, - tokens: responseData.reduce((sum, item) => sum + (item.tokens || 0), 0) + historyPreview: chatData.historyPreview, + runningTime: responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0) }; }, [responseData]); @@ -65,21 +66,21 @@ const ResponseTags = ({ )} - {completeMessages.length > 0 && ( + {historyPreview.length > 0 && ( setContextModalData(completeMessages)} + onClick={() => setContextModalData(historyPreview)} > - {completeMessages.length}条上下文 + {historyPreview.length}条上下文 )} - {isPc && tokens > 0 && ( + {isPc && runningTime > 0 && ( - {tokens}Tokens + {runningTime}s )} diff --git a/client/src/components/ChatBox/WholeResponseModal.tsx b/client/src/components/ChatBox/WholeResponseModal.tsx index e16e0aadd..7c12eff6b 100644 --- a/client/src/components/ChatBox/WholeResponseModal.tsx +++ b/client/src/components/ChatBox/WholeResponseModal.tsx @@ -21,7 +21,7 @@ const ResponseModal = ({ () => response.map((item) => { const copy = { ...item }; - delete copy.completeMessages; + delete copy.historyPreview; delete copy.quoteList; return copy; }), diff --git a/client/src/components/Icon/index.tsx b/client/src/components/Icon/index.tsx index cab8ec076..9fac92523 100644 --- a/client/src/components/Icon/index.tsx +++ b/client/src/components/Icon/index.tsx @@ -94,7 +94,7 @@ const MyIcon = ({ name, w = 'auto', h = 'auto', ...props }: { name: IconName } & const [IconComponent, setIconComponent] = useState(null); useEffect(() => { - iconPaths[name]() + iconPaths[name]?.() .then((icon) => { setIconComponent({ as: icon.default }); }) diff --git a/client/src/constants/chat.ts b/client/src/constants/chat.ts index 507e36698..473873978 100644 --- a/client/src/constants/chat.ts +++ b/client/src/constants/chat.ts @@ -52,14 +52,6 @@ export const ChatSourceMap = { } }; -export enum ChatModuleEnum { - 'AIChat' = 'AI Chat', - 'KBSearch' = 'KB Search', - 'CQ' = 'Classify Question', - 'Extract' = 'Content Extract', - 'Http' = 'Http' -} - export enum OutLinkTypeEnum { 'share' = 'share', 'iframe' = 'iframe' diff --git a/client/src/constants/flow/ModuleTemplate.ts b/client/src/constants/flow/ModuleTemplate.ts index 991534070..f62f3434c 100644 --- a/client/src/constants/flow/ModuleTemplate.ts +++ b/client/src/constants/flow/ModuleTemplate.ts @@ -26,12 +26,12 @@ export const welcomeTextTip = '每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]: 用户点击后可以直接发送该问题'; export const VariableModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.variable, logo: '/imgs/module/variable.png', name: '全局变量', intro: '可以在对话开始前,要求用户填写一些内容作为本轮对话的变量。该模块位于开场引导之后。', description: '全局变量可以通过 {{变量key}} 的形式注入到其他模块 string 类型的输入中,例如:提示词、限定词等', - flowType: FlowModuleTypeEnum.variable, inputs: [ { key: SystemInputEnum.variables, @@ -43,10 +43,10 @@ export const VariableModule: FlowModuleTemplateType = { outputs: [] }; export const UserGuideModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.userGuide, logo: '/imgs/module/userGuide.png', name: '用户引导', intro: userGuideTip, - flowType: FlowModuleTypeEnum.userGuide, inputs: [ { key: SystemInputEnum.welcomeText, @@ -57,10 +57,10 @@ export const UserGuideModule: FlowModuleTemplateType = { outputs: [] }; export const UserInputModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.questionInput, logo: '/imgs/module/userChatInput.png', name: '用户问题(对话入口)', intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', - flowType: FlowModuleTypeEnum.questionInput, inputs: [ { key: SystemInputEnum.userChatInput, @@ -79,10 +79,10 @@ export const UserInputModule: FlowModuleTemplateType = { ] }; export const HistoryModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.historyNode, logo: '/imgs/module/history.png', name: '聊天记录', intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', - flowType: FlowModuleTypeEnum.historyNode, inputs: [ { key: 'maxContext', @@ -110,10 +110,10 @@ export const HistoryModule: FlowModuleTemplateType = { }; export const ChatModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.chatNode, logo: '/imgs/module/AI.png', name: 'AI 对话', intro: 'AI 大模型对话', - flowType: FlowModuleTypeEnum.chatNode, showStatus: true, inputs: [ { @@ -209,10 +209,10 @@ export const ChatModule: FlowModuleTemplateType = { }; export const KBSearchModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.kbSearchNode, logo: '/imgs/module/db.png', name: '知识库搜索', intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。', - flowType: FlowModuleTypeEnum.kbSearchNode, showStatus: true, inputs: [ { @@ -280,11 +280,11 @@ export const KBSearchModule: FlowModuleTemplateType = { }; export const AnswerModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.answerNode, logo: '/imgs/module/reply.png', name: '指定回复', intro: '该模块可以直接回复一段指定的内容。常用于引导、提示', description: '该模块可以直接回复一段指定的内容。常用于引导、提示', - flowType: FlowModuleTypeEnum.answerNode, inputs: [ Input_Template_TFSwitch, { @@ -308,40 +308,13 @@ export const AnswerModule: FlowModuleTemplateType = { } ] }; -export const TFSwitchModule: FlowModuleTemplateType = { - logo: '', - name: 'TF开关', - intro: '可以判断输入的内容为 True 或者 False,从而执行不同操作。', - flowType: FlowModuleTypeEnum.tfSwitchNode, - inputs: [ - { - key: SystemInputEnum.switch, - type: FlowInputItemTypeEnum.target, - label: '输入' - } - ], - outputs: [ - { - key: 'true', - label: 'True', - type: FlowOutputItemTypeEnum.source, - targets: [] - }, - { - key: 'false', - label: 'False', - type: FlowOutputItemTypeEnum.source, - targets: [] - } - ] -}; export const ClassifyQuestionModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.classifyQuestion, logo: '/imgs/module/cq.png', name: '问题分类', intro: '可以判断用户问题属于哪方面问题,从而执行不同的操作。', description: '根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:\n类型1: 打招呼\n类型2: 关于 laf 通用问题\n类型3: 关于 laf 代码问题\n类型4: 其他问题', - flowType: FlowModuleTypeEnum.classifyQuestion, showStatus: true, inputs: [ { @@ -398,11 +371,11 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = { ] }; export const ContextExtractModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.contentExtract, logo: '/imgs/module/extract.png', name: '文本内容提取', intro: '从文本中提取出指定格式的数据', description: '可从文本中提取指定的数据,例如:sql语句、搜索关键词、代码等', - flowType: FlowModuleTypeEnum.contentExtract, showStatus: true, inputs: [ Input_Template_TFSwitch, @@ -458,11 +431,11 @@ export const ContextExtractModule: FlowModuleTemplateType = { ] }; export const HttpModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.httpRequest, logo: '/imgs/module/http.png', name: 'HTTP模块', intro: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)', description: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)', - flowType: FlowModuleTypeEnum.httpRequest, showStatus: true, inputs: [ { @@ -487,11 +460,11 @@ export const HttpModule: FlowModuleTemplateType = { ] }; export const EmptyModule: FlowModuleTemplateType = { + flowType: FlowModuleTypeEnum.empty, logo: '/imgs/module/cq.png', name: '该模块已被移除', intro: '', description: '', - flowType: FlowModuleTypeEnum.empty, inputs: [], outputs: [] }; diff --git a/client/src/pages/api/openapi/v1/chat/completions.ts b/client/src/pages/api/openapi/v1/chat/completions.ts index eeb84f0cf..f9553a276 100644 --- a/client/src/pages/api/openapi/v1/chat/completions.ts +++ b/client/src/pages/api/openapi/v1/chat/completions.ts @@ -154,11 +154,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex stream, detail }); - // console.log(responseData, '===', answerText); - - // if (!answerText) { - // throw new Error('回复内容为空,可能模块编排出现问题'); - // } // save chat if (chatId) { @@ -284,6 +279,7 @@ export async function dispatchModules({ // let storeData: Record = {}; // after module used let chatResponse: ChatHistoryItemResType[] = []; // response request and save to database let chatAnswerText = ''; // AI answer + let runningTime = Date.now(); function pushStore({ answerText = '', @@ -292,7 +288,13 @@ export async function dispatchModules({ answerText?: string; responseData?: ChatHistoryItemResType; }) { - responseData && chatResponse.push(responseData); + const time = Date.now(); + responseData && + chatResponse.push({ + ...responseData, + runningTime: +((time - runningTime) / 1000).toFixed(2) + }); + runningTime = time; chatAnswerText += answerText; } function moduleInput( diff --git a/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx b/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx index 685135785..cd1d69975 100644 --- a/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx +++ b/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx @@ -6,7 +6,8 @@ import type { FlowModuleItemType } from '@/types/flow'; import MyTooltip from '@/components/MyTooltip'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { useTranslation } from 'react-i18next'; -import { useCopyData } from '@/utils/tools'; +import { useEditTitle } from '@/hooks/useEditTitle'; +import { useToast } from '@/hooks/useToast'; type Props = FlowModuleItemType & { children?: React.ReactNode | React.ReactNode[] | string; @@ -22,29 +23,48 @@ const NodeCard = (props: Props) => { minW = '300px', onCopyNode, onDelNode, + onChangeNode, moduleId } = props; - const { copyData } = useCopyData(); const { t } = useTranslation(); const theme = useTheme(); + const { toast } = useToast(); + + // custom title edit + const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({ + title: t('common.Custom Title'), + placeholder: t('app.module.Custom Title Tip') || '' + }); const menuList = useMemo( () => [ + { + icon: 'edit', + label: t('common.Rename'), + onClick: () => + onOpenModal({ + defaultVal: name, + onSuccess: (e) => { + if (!e) { + return toast({ + title: t('app.modules.Title is required'), + status: 'warning' + }); + } + onChangeNode({ + moduleId, + type: 'attr', + key: 'name', + value: e + }); + } + }) + }, { icon: 'copy', label: t('common.Copy'), onClick: () => onCopyNode(moduleId) }, - // { - // icon: 'settingLight', - // label: t('app.Copy Module Config'), - // onClick: () => { - // const copyProps = { ...props }; - // delete copyProps.children; - // delete copyProps.children; - // console.log(copyProps); - // } - // }, { icon: 'delete', label: t('common.Delete'), @@ -100,6 +120,7 @@ const NodeCard = (props: Props) => { {children} + ); }; diff --git a/client/src/pages/app/detail/components/AdEdit/index.tsx b/client/src/pages/app/detail/components/AdEdit/index.tsx index b52bbc542..0af69c5c5 100644 --- a/client/src/pages/app/detail/components/AdEdit/index.tsx +++ b/client/src/pages/app/detail/components/AdEdit/index.tsx @@ -276,62 +276,43 @@ const AppEdit = ({ app, onCloseSettings }: Props) => { setNodes((nodes) => nodes.map((node) => { if (node.id !== moduleId) return node; + + const updateObj: Record = {}; + if (type === 'inputs') { - return { - ...node, - data: { - ...node.data, - inputs: node.data.inputs.map((item) => (item.key === key ? value : item)) - } - }; - } - if (type === 'addInput') { + updateObj.inputs = node.data.inputs.map((item) => (item.key === key ? value : item)); + } else if (type === 'addInput') { const input = node.data.inputs.find((input) => input.key === value.key); if (input) { toast({ status: 'warning', title: 'key 重复' }); - return { - ...node, - data: { - ...node.data, - inputs: node.data.inputs - } - }; + updateObj.inputs = node.data.inputs; + } else { + updateObj.inputs = node.data.inputs.concat(value); } - return { - ...node, - data: { - ...node.data, - inputs: node.data.inputs.concat(value) - } - }; - } - if (type === 'delInput') { + } else if (type === 'delInput') { onDelEdge({ moduleId, targetHandle: key }); - return { - ...node, - data: { - ...node.data, - inputs: node.data.inputs.filter((item) => item.key !== key) - } - }; + updateObj.inputs = node.data.inputs.filter((item) => item.key !== key); + } else if (type === 'attr') { + updateObj[key] = value; + } else if (type === 'outputs') { + // del output connect + const delOutputs = node.data.outputs.filter( + (item) => !value.find((output: FlowOutputTargetItemType) => output.key === item.key) + ); + delOutputs.forEach((output) => { + onDelEdge({ moduleId, sourceHandle: output.key }); + }); + updateObj.outputs = value; } - // del output connect - const delOutputs = node.data.outputs.filter( - (item) => !value.find((output: FlowOutputTargetItemType) => output.key === item.key) - ); - delOutputs.forEach((output) => { - onDelEdge({ moduleId, sourceHandle: output.key }); - }); - return { ...node, data: { ...node.data, - outputs: value + ...updateObj } }; }) diff --git a/client/src/service/events/pushBill.ts b/client/src/service/events/pushBill.ts index 853c3b895..6f1d7f9de 100644 --- a/client/src/service/events/pushBill.ts +++ b/client/src/service/events/pushBill.ts @@ -30,7 +30,7 @@ export const pushTaskBill = async ({ total, source, list: response.map((item) => ({ - moduleName: item.moduleName, + moduleType: item.moduleType, amount: item.price || 0, model: item.model, tokenLen: item.tokens diff --git a/client/src/service/models/chat.ts b/client/src/service/models/chat.ts index 57c713385..220e344a0 100644 --- a/client/src/service/models/chat.ts +++ b/client/src/service/models/chat.ts @@ -62,24 +62,7 @@ const ChatSchema = new Schema({ default: '' }, [TaskResponseKeyEnum.responseData]: { - type: [ - { - moduleName: String, - price: String, - model: String, - tokens: Number, - question: String, - answer: String, - temperature: Number, - maxToken: Number, - quoteList: Array, - completeMessages: Array, - similarity: Number, - limit: Number, - cqList: Array, - cqResult: String - } - ], + type: Array, default: [] } } diff --git a/client/src/service/moduleDispatch/agent/classifyQuestion.ts b/client/src/service/moduleDispatch/agent/classifyQuestion.ts index 96958e392..00d4712bc 100644 --- a/client/src/service/moduleDispatch/agent/classifyQuestion.ts +++ b/client/src/service/moduleDispatch/agent/classifyQuestion.ts @@ -1,7 +1,7 @@ import { adaptChat2GptMessages } from '@/utils/common/adapt/message'; import { ChatContextFilter } from '@/service/common/tiktoken'; import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat'; -import { ChatModuleEnum, ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat'; +import { ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat'; import { getAIChatApi, axiosConfig } from '@/service/lib/openai'; import type { ClassifyQuestionAgentItemType } from '@/types/app'; import { countModelPrice } from '@/service/events/pushBill'; @@ -9,6 +9,7 @@ import { UserModelSchema } from '@/types/mongoSchema'; import { getModel } from '@/service/utils/data'; import { SystemInputEnum } from '@/constants/app'; import { SpecialInputKeyEnum } from '@/constants/flow'; +import { FlowModuleTypeEnum } from '@/constants/flow'; export type CQProps = { systemPrompt?: string; @@ -95,7 +96,7 @@ export const dispatchClassifyQuestion = async (props: Record): Prom return { [result.key]: 1, [TaskResponseKeyEnum.responseData]: { - moduleName: ChatModuleEnum.CQ, + moduleType: FlowModuleTypeEnum.classifyQuestion, price: userOpenaiAccount?.key ? 0 : countModelPrice({ model: agentModel, tokens }), model: getModel(agentModel)?.name || agentModel, tokens, diff --git a/client/src/service/moduleDispatch/agent/extract.ts b/client/src/service/moduleDispatch/agent/extract.ts index d422eaa80..703b23469 100644 --- a/client/src/service/moduleDispatch/agent/extract.ts +++ b/client/src/service/moduleDispatch/agent/extract.ts @@ -1,13 +1,14 @@ import { adaptChat2GptMessages } from '@/utils/common/adapt/message'; import { ChatContextFilter } from '@/service/common/tiktoken'; import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat'; -import { ChatModuleEnum, ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat'; +import { ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat'; import { getAIChatApi, axiosConfig } from '@/service/lib/openai'; import type { ContextExtractAgentItemType } from '@/types/app'; import { ContextExtractEnum } from '@/constants/flow/flowField'; import { countModelPrice } from '@/service/events/pushBill'; import { UserModelSchema } from '@/types/mongoSchema'; import { getModel } from '@/service/utils/data'; +import { FlowModuleTypeEnum } from '@/constants/flow'; export type Props = { userOpenaiAccount: UserModelSchema['openaiAccount']; @@ -118,7 +119,7 @@ export async function dispatchContentExtract({ [ContextExtractEnum.fields]: JSON.stringify(arg), ...arg, [TaskResponseKeyEnum.responseData]: { - moduleName: ChatModuleEnum.Extract, + moduleType: FlowModuleTypeEnum.contentExtract, price: userOpenaiAccount?.key ? 0 : countModelPrice({ model: agentModel, tokens }), model: getModel(agentModel)?.name || agentModel, tokens, diff --git a/client/src/service/moduleDispatch/chat/oneapi.ts b/client/src/service/moduleDispatch/chat/oneapi.ts index 273a578f6..d3d831226 100644 --- a/client/src/service/moduleDispatch/chat/oneapi.ts +++ b/client/src/service/moduleDispatch/chat/oneapi.ts @@ -3,7 +3,7 @@ import { sseResponse } from '@/service/utils/tools'; import { ChatContextFilter } from '@/service/common/tiktoken'; import type { ChatItemType, QuoteItemType } from '@/types/chat'; import type { ChatHistoryItemResType } from '@/types/chat'; -import { ChatModuleEnum, ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat'; +import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat'; import { SSEParseData, parseStreamChunk } from '@/utils/sse'; import { textAdaptGptResponse } from '@/utils/adapt'; import { getAIChatApi, axiosConfig } from '@/service/lib/openai'; @@ -20,6 +20,7 @@ import { adaptChat2GptMessages } from '@/utils/common/adapt/message'; import { defaultQuotePrompt, defaultQuoteTemplate } from '@/prompts/core/AIChat'; import type { AIChatProps } from '@/types/core/aiChat'; import { replaceVariable } from '@/utils/common/tools/text'; +import { FlowModuleTypeEnum } from '@/constants/flow'; export type ChatProps = AIChatProps & { res: NextApiResponse; @@ -175,15 +176,14 @@ export const dispatchChatCompletion = async (props: Record): Promis return { [TaskResponseKeyEnum.answerText]: answerText, [TaskResponseKeyEnum.responseData]: { - moduleName: ChatModuleEnum.AIChat, + moduleType: FlowModuleTypeEnum.chatNode, price: userOpenaiAccount?.key ? 0 : countModelPrice({ model, tokens: totalTokens }), model: modelConstantsData.name, tokens: totalTokens, question: userChatInput, - answer: answerText, maxToken: max_tokens, quoteList: filterQuoteQA, - completeMessages + historyPreview: getHistoryPreview(completeMessages) }, finish: true }; @@ -371,3 +371,14 @@ async function streamResponse({ answer }; } + +function getHistoryPreview(completeMessages: ChatItemType[]) { + return completeMessages.map((item, i) => { + if (item.obj === ChatRoleEnum.System) return item; + if (i >= completeMessages.length - 2) return item; + return { + ...item, + value: item.value.length > 15 ? `${item.value.slice(0, 15)}...` : item.value + }; + }); +} diff --git a/client/src/service/moduleDispatch/kb/search.ts b/client/src/service/moduleDispatch/kb/search.ts index 6813846f6..d384031c9 100644 --- a/client/src/service/moduleDispatch/kb/search.ts +++ b/client/src/service/moduleDispatch/kb/search.ts @@ -1,11 +1,12 @@ import { PgClient } from '@/service/pg'; import type { ChatHistoryItemResType } from '@/types/chat'; -import { ChatModuleEnum, TaskResponseKeyEnum } from '@/constants/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'; import { PgDatasetTableName } from '@/constants/plugin'; +import { FlowModuleTypeEnum } from '@/constants/flow'; type KBSearchProps = { kbList: SelectedKbType; @@ -57,7 +58,7 @@ export async function dispatchKBSearch(props: Record): Promise 0 ? true : undefined, quoteQA: searchRes, responseData: { - moduleName: ChatModuleEnum.KBSearch, + moduleType: FlowModuleTypeEnum.kbSearchNode, price: countModelPrice({ model: vectorModel.model, tokens: tokenLen }), model: vectorModel.name, tokens: tokenLen, diff --git a/client/src/service/moduleDispatch/tools/http.ts b/client/src/service/moduleDispatch/tools/http.ts index fb0d927f0..8b645829b 100644 --- a/client/src/service/moduleDispatch/tools/http.ts +++ b/client/src/service/moduleDispatch/tools/http.ts @@ -1,7 +1,8 @@ -import { ChatModuleEnum, TaskResponseKeyEnum } from '@/constants/chat'; +import { TaskResponseKeyEnum } from '@/constants/chat'; import { HttpPropsEnum } from '@/constants/flow/flowField'; import { ChatHistoryItemResType } from '@/types/chat'; import type { NextApiResponse } from 'next'; +import { FlowModuleTypeEnum } from '@/constants/flow'; export type HttpRequestProps = { res: NextApiResponse; @@ -26,7 +27,7 @@ export const dispatchHttpRequest = async (props: Record): Promise): Promise( } // another error - let msg = error?.response?.statusText || error?.message || '请求错误'; + let msg = ''; if ((code < 200 || code >= 400) && !message) { - msg = error?.message || '请求错误'; + msg = error?.response?.statusText || error?.message || '请求错误'; if (typeof error === 'string') { msg = error; } else if (proxyError[error?.code]) { @@ -59,7 +59,7 @@ export const jsonRes = ( res.status(code).json({ code, statusText: '', - message: msg, + message: message || msg, data: data !== undefined ? data : null }); }; diff --git a/client/src/types/chat.d.ts b/client/src/types/chat.d.ts index 86802b887..485e56bf6 100644 --- a/client/src/types/chat.d.ts +++ b/client/src/types/chat.d.ts @@ -4,6 +4,7 @@ import { TaskResponseKeyEnum } from '@/constants/chat'; import { ClassifyQuestionAgentItemType } from './app'; import { ChatItemSchema } from './mongoSchema'; import { KbDataItemType } from './plugin'; +import { FlowModuleTypeEnum } from '@/constants/flow'; export type ExportChatType = 'md' | 'pdf' | 'html'; @@ -46,19 +47,20 @@ export type QuoteItemType = KbDataItemType & { kb_id: string; }; +// response data export type ChatHistoryItemResType = { - moduleName: string; + moduleType: `${FlowModuleTypeEnum}`; price: number; + runningTime?: number; model?: string; tokens?: number; // chat - answer?: string; question?: string; temperature?: number; maxToken?: number; quoteList?: QuoteItemType[]; - completeMessages?: ChatItemType[]; + historyPreview?: ChatItemType[]; // completion context array. history will slice // kb search similarity?: number; diff --git a/client/src/types/flow.d.ts b/client/src/types/flow.d.ts index 69eb31bf7..2d4086ecb 100644 --- a/client/src/types/flow.d.ts +++ b/client/src/types/flow.d.ts @@ -10,7 +10,7 @@ import { FlowModuleTypeEnum } from '@/constants/flow'; export type FlowModuleItemChangeProps = { moduleId: string; - type: 'inputs' | 'outputs' | 'addInput' | 'delInput'; + type: 'attr' | 'inputs' | 'outputs' | 'addInput' | 'delInput'; key: string; value: any; }; @@ -48,12 +48,12 @@ export type FlowOutputItemType = { }; export type FlowModuleTemplateType = { + flowType: `${FlowModuleTypeEnum}`; // unique logo: string; name: string; description?: string; intro: string; - flowType: `${FlowModuleTypeEnum}`; - showStatus?: boolean; + showStatus?: boolean; // chatting response step status inputs: FlowInputItemType[]; outputs: FlowOutputItemType[]; }; diff --git a/client/src/utils/adapt.ts b/client/src/utils/adapt.ts index a581e276d..3dd231c1d 100644 --- a/client/src/utils/adapt.ts +++ b/client/src/utils/adapt.ts @@ -92,8 +92,8 @@ export const appModule2FlowNode = ({ // replace item data const moduleItem: FlowModuleItemType = { - ...item, ...template, + ...item, inputs: concatInputs.map((templateInput) => { // use latest inputs const itemInput = item.inputs.find((item) => item.key === templateInput.key) || templateInput;