From 92bdbb228a04ca3108f025a59c9700e90b37aca2 Mon Sep 17 00:00:00 2001 From: archer <545436317@qq.com> Date: Thu, 11 Sep 2025 10:12:14 +0800 Subject: [PATCH] feat: sub tool response --- packages/global/core/chat/adapt.ts | 336 ++++++++--------- packages/global/core/chat/constants.ts | 7 - packages/global/core/chat/type.d.ts | 34 +- packages/global/core/chat/utils.ts | 12 +- .../global/core/workflow/runtime/constants.ts | 1 + .../global/core/workflow/runtime/utils.ts | 8 +- packages/service/core/chat/pushChatLog.ts | 11 +- packages/service/core/chat/saveChat.ts | 12 +- packages/service/core/chat/utils.ts | 6 +- .../core/workflow/dispatch/ai/agent/index.ts | 98 ++--- .../dispatch/ai/agent/sub/app/index.ts | 2 - .../dispatch/ai/agent/sub/model/index.ts | 3 + .../dispatch/ai/agent/sub/plan/index.ts | 3 + .../workflow/dispatch/ai/classifyQuestion.ts | 4 +- .../core/workflow/dispatch/ai/extract.ts | 6 +- .../workflow/dispatch/ai/tool/toolCall.ts | 7 +- .../core/workflow/dispatch/ai/utils.ts | 10 +- .../service/core/workflow/dispatch/index.ts | 11 +- .../service/core/workflow/dispatch/type.d.ts | 1 + .../service/core/workflow/dispatch/utils.ts | 7 +- .../ChatBox/components/ChatAvatar.tsx | 3 +- .../ChatBox/components/ChatItem.tsx | 284 +++++++------- .../ChatBox/hooks/useChatBox.tsx | 7 +- .../core/chat/ChatContainer/ChatBox/index.tsx | 353 +++++++++++------- .../core/chat/ChatContainer/ChatBox/utils.ts | 13 +- .../ChatContainer/PluginRunBox/context.tsx | 14 +- .../core/chat/ChatContainer/type.d.ts | 2 + .../core/chat/components/AIResponseBox.tsx | 183 +++++---- .../app/detail/Edit/Agent/utils.ts | 2 +- .../render/RenderDebug/NodeDebugResponse.tsx | 4 +- .../app/src/pages/api/core/chat/chatTest.ts | 8 +- .../api/core/chat/getPaginationRecords.ts | 4 +- .../src/pages/api/core/chat/item/delete.ts | 4 +- projects/app/src/service/core/app/utils.ts | 7 +- projects/app/src/service/support/mcp/utils.ts | 7 +- projects/app/src/web/common/api/fetch.ts | 24 +- test/cases/global/core/chat/utils.test.ts | 22 +- .../core/app/workflow/dispatch/utils.test.ts | 9 +- .../app/workflow/workflowDispatch.test.ts | 2 - 39 files changed, 798 insertions(+), 733 deletions(-) diff --git a/packages/global/core/chat/adapt.ts b/packages/global/core/chat/adapt.ts index 47b13a259..25fc0a415 100644 --- a/packages/global/core/chat/adapt.ts +++ b/packages/global/core/chat/adapt.ts @@ -7,7 +7,7 @@ import type { UserChatItemType, UserChatItemValueItemType } from '../../core/chat/type.d'; -import { ChatFileTypeEnum, ChatItemValueTypeEnum, ChatRoleEnum } from '../../core/chat/constants'; +import { ChatFileTypeEnum, ChatRoleEnum } from '../../core/chat/constants'; import type { ChatCompletionContentPart, ChatCompletionFunctionMessageParam, @@ -62,13 +62,13 @@ export const chats2GPTMessages = ({ } else if (item.obj === ChatRoleEnum.Human) { const value = item.value .map((item) => { - if (item.type === ChatItemValueTypeEnum.text) { + if (item.text) { return { type: 'text', text: item.text?.content || '' }; } - if (item.type === ChatItemValueTypeEnum.file) { + if (item.file) { if (item.file?.type === ChatFileTypeEnum.image) { return { type: 'image_url', @@ -98,9 +98,9 @@ export const chats2GPTMessages = ({ } else { const aiResults: ChatCompletionMessageParam[] = []; - //AI + //AI: 只需要把根节点转化即可 item.value.forEach((value, i) => { - if (value.type === ChatItemValueTypeEnum.tool && value.tools && reserveTool) { + if (value.tools && reserveTool) { const tool_calls: ChatCompletionMessageToolCall[] = []; const toolResponse: ChatCompletionToolMessageParam[] = []; value.tools.forEach((tool) => { @@ -124,21 +124,14 @@ export const chats2GPTMessages = ({ tool_calls }); aiResults.push(...toolResponse); - } else if ( - value.type === ChatItemValueTypeEnum.text && - typeof value.text?.content === 'string' - ) { + } else if (typeof value.text?.content === 'string') { if (!value.text.content && item.value.length > 1) { return; } // Concat text const lastValue = item.value[i - 1]; const lastResult = aiResults[aiResults.length - 1]; - if ( - lastValue && - lastValue.type === ChatItemValueTypeEnum.text && - typeof lastResult?.content === 'string' - ) { + if (lastValue && typeof lastResult?.content === 'string') { lastResult.content += value.text.content; } else { aiResults.push({ @@ -147,7 +140,7 @@ export const chats2GPTMessages = ({ content: value.text.content }); } - } else if (value.type === ChatItemValueTypeEnum.interactive) { + } else if (value.interactive) { aiResults.push({ dataId, role: ChatCompletionRequestMessageRoleEnum.Assistant, @@ -187,180 +180,175 @@ export const GPTMessages2Chats = ({ .map((item) => { const obj = GPT2Chat[item.role]; - const value = (() => { - if ( - obj === ChatRoleEnum.System && - item.role === ChatCompletionRequestMessageRoleEnum.System - ) { - const value: SystemChatItemValueItemType[] = []; + if ( + obj === ChatRoleEnum.System && + item.role === ChatCompletionRequestMessageRoleEnum.System + ) { + const value: SystemChatItemValueItemType[] = []; - if (Array.isArray(item.content)) { - item.content.forEach((item) => [ + if (Array.isArray(item.content)) { + item.content.forEach((item) => [ + value.push({ + text: { + content: item.text + } + }) + ]); + } else { + value.push({ + text: { + content: item.content + } + }); + } + return { + dataId: item.dataId, + obj, + hideInUI: item.hideInUI, + value + }; + } else if ( + obj === ChatRoleEnum.Human && + item.role === ChatCompletionRequestMessageRoleEnum.User + ) { + const value: UserChatItemValueItemType[] = []; + + if (typeof item.content === 'string') { + value.push({ + text: { + content: item.content + } + }); + } else if (Array.isArray(item.content)) { + item.content.forEach((item) => { + if (item.type === 'text') { value.push({ - type: ChatItemValueTypeEnum.text, text: { content: item.text } - }) - ]); - } else { - value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.content - } - }); - } - return value; - } else if ( - obj === ChatRoleEnum.Human && - item.role === ChatCompletionRequestMessageRoleEnum.User - ) { - const value: UserChatItemValueItemType[] = []; - - if (typeof item.content === 'string') { - value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.content - } - }); - } else if (Array.isArray(item.content)) { - item.content.forEach((item) => { - if (item.type === 'text') { - value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.text - } - }); - } else if (item.type === 'image_url') { - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.file, - file: { - type: ChatFileTypeEnum.image, - name: '', - url: item.image_url.url, - key: item.key - } - }); - } else if (item.type === 'file_url') { - value.push({ - // @ts-ignore - type: ChatItemValueTypeEnum.file, - file: { - type: ChatFileTypeEnum.file, - name: item.name, - url: item.url, - key: item.key - } - }); - } - }); - } - return value; - } else if ( - obj === ChatRoleEnum.AI && - item.role === ChatCompletionRequestMessageRoleEnum.Assistant - ) { - const value: AIChatItemValueItemType[] = []; - - if (typeof item.reasoning_text === 'string' && item.reasoning_text) { - value.push({ - type: ChatItemValueTypeEnum.reasoning, - reasoning: { - content: item.reasoning_text - } - }); - } - if (item.tool_calls && reserveTool) { - // save tool calls - const toolCalls = item.tool_calls as ChatCompletionMessageToolCall[]; - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.tool, - tools: toolCalls.map((tool) => { - let toolResponse = - messages.find( - (msg) => - msg.role === ChatCompletionRequestMessageRoleEnum.Tool && - msg.tool_call_id === tool.id - )?.content || ''; - toolResponse = - typeof toolResponse === 'string' ? toolResponse : JSON.stringify(toolResponse); - - const toolInfo = getToolInfo?.(tool.function.name); - - return { - id: tool.id, - toolName: toolInfo?.name || '', - toolAvatar: toolInfo?.avatar || '', - functionName: tool.function.name, - params: tool.function.arguments, - response: toolResponse as string - }; - }) - }); - } - if (item.function_call && reserveTool) { - const functionCall = item.function_call as ChatCompletionMessageFunctionCall; - const functionResponse = messages.find( - (msg) => - msg.role === ChatCompletionRequestMessageRoleEnum.Function && - msg.name === item.function_call?.name - ) as ChatCompletionFunctionMessageParam; - - if (functionResponse) { - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.tool, - tools: [ - { - id: functionCall.id || '', - toolName: functionCall.toolName || '', - toolAvatar: functionCall.toolAvatar || '', - functionName: functionCall.name, - params: functionCall.arguments, - response: functionResponse.content || '' - } - ] }); - } - } - if (item.interactive) { - value.push({ - //@ts-ignore - type: ChatItemValueTypeEnum.interactive, - interactive: item.interactive - }); - } - if (typeof item.content === 'string' && item.content) { - const lastValue = value[value.length - 1]; - if (lastValue && lastValue.type === ChatItemValueTypeEnum.text && lastValue.text) { - lastValue.text.content += item.content; - } else { + } else if (item.type === 'image_url') { value.push({ - type: ChatItemValueTypeEnum.text, - text: { - content: item.content + file: { + type: ChatFileTypeEnum.image, + name: '', + url: item.image_url.url, + key: item.key + } + }); + } else if (item.type === 'file_url') { + value.push({ + file: { + type: ChatFileTypeEnum.file, + name: item.name, + url: item.url, + key: item.key } }); } - } + }); + } + return { + dataId: item.dataId, + obj, + hideInUI: item.hideInUI, + value + }; + } else if ( + obj === ChatRoleEnum.AI && + item.role === ChatCompletionRequestMessageRoleEnum.Assistant + ) { + const value: AIChatItemValueItemType[] = []; - return value; + if (typeof item.reasoning_text === 'string' && item.reasoning_text) { + value.push({ + reasoning: { + content: item.reasoning_text + } + }); + } + if (item.tool_calls && reserveTool) { + // save tool calls + const toolCalls = item.tool_calls as ChatCompletionMessageToolCall[]; + value.push({ + tools: toolCalls.map((tool) => { + let toolResponse = + messages.find( + (msg) => + msg.role === ChatCompletionRequestMessageRoleEnum.Tool && + msg.tool_call_id === tool.id + )?.content || ''; + toolResponse = + typeof toolResponse === 'string' ? toolResponse : JSON.stringify(toolResponse); + + const toolInfo = getToolInfo?.(tool.function.name); + + return { + id: tool.id, + toolName: toolInfo?.name || '', + toolAvatar: toolInfo?.avatar || '', + functionName: tool.function.name, + params: tool.function.arguments, + response: toolResponse as string + }; + }) + }); + } + if (item.function_call && reserveTool) { + const functionCall = item.function_call as ChatCompletionMessageFunctionCall; + const functionResponse = messages.find( + (msg) => + msg.role === ChatCompletionRequestMessageRoleEnum.Function && + msg.name === item.function_call?.name + ) as ChatCompletionFunctionMessageParam; + + if (functionResponse) { + value.push({ + tools: [ + { + id: functionCall.id || '', + toolName: functionCall.toolName || '', + toolAvatar: functionCall.toolAvatar || '', + functionName: functionCall.name, + params: functionCall.arguments, + response: functionResponse.content || '' + } + ] + }); + } + } + if (item.interactive) { + value.push({ + interactive: item.interactive + }); + } + if (typeof item.content === 'string' && item.content) { + const lastValue = value[value.length - 1]; + if (lastValue && lastValue.text) { + lastValue.text.content += item.content; + } else { + value.push({ + text: { + content: item.content + } + }); + } } - return []; - })(); + return { + dataId: item.dataId, + obj, + hideInUI: item.hideInUI, + value + }; + } return { dataId: item.dataId, obj, hideInUI: item.hideInUI, - value - } as ChatItemType; + value: [] + }; }) .filter((item) => item.value.length > 0); @@ -387,7 +375,7 @@ export const chatValue2RuntimePrompt = (value: ChatItemValueItemType[]): Runtime text: '' }; value.forEach((item) => { - if (item.type === 'file' && item.file) { + if ('file' in item && item.file) { prompt.files.push(item.file); } else if (item.text) { prompt.text += item.text.content; @@ -403,14 +391,12 @@ export const runtimePrompt2ChatsValue = ( if (prompt.files) { prompt.files.forEach((file) => { value.push({ - type: ChatItemValueTypeEnum.file, file }); }); } if (prompt.text) { value.push({ - type: ChatItemValueTypeEnum.text, text: { content: prompt.text } @@ -424,7 +410,7 @@ export const getSystemPrompt_ChatItemType = (prompt?: string): ChatItemType[] => return [ { obj: ChatRoleEnum.System, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: prompt } }] + value: [{ text: { content: prompt } }] } ]; }; diff --git a/packages/global/core/chat/constants.ts b/packages/global/core/chat/constants.ts index 424678f68..8eba2a4b3 100644 --- a/packages/global/core/chat/constants.ts +++ b/packages/global/core/chat/constants.ts @@ -21,13 +21,6 @@ export enum ChatFileTypeEnum { image = 'image', file = 'file' } -export enum ChatItemValueTypeEnum { - text = 'text', - file = 'file', - tool = 'tool', - interactive = 'interactive', - reasoning = 'reasoning' -} export enum ChatSourceEnum { test = 'test', diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts index 247e0adde..73c70bb6e 100644 --- a/packages/global/core/chat/type.d.ts +++ b/packages/global/core/chat/type.d.ts @@ -1,12 +1,6 @@ import { ClassifyQuestionAgentItemType } from '../workflow/template/system/classifyQuestion/type'; import type { SearchDataResponseItemType } from '../dataset/type'; -import type { - ChatFileTypeEnum, - ChatItemValueTypeEnum, - ChatRoleEnum, - ChatSourceEnum, - ChatStatusEnum -} from './constants'; +import type { ChatFileTypeEnum, ChatRoleEnum, ChatSourceEnum, ChatStatusEnum } from './constants'; import type { FlowNodeTypeEnum } from '../workflow/node/constant'; import type { NodeInputKeyEnum, NodeOutputKeyEnum } from '../workflow/constants'; import type { DispatchNodeResponseKeyEnum } from '../workflow/runtime/constants'; @@ -20,6 +14,7 @@ import type { WorkflowInteractiveResponseType } from '../workflow/template/syste import type { FlowNodeInputItemType } from '../workflow/type/io'; import type { FlowNodeTemplateType } from '../workflow/type/node.d'; import { ChatCompletionMessageParam } from '../ai/type'; +import type { RequireOnlyOne } from '../../common/type/utils'; /* --------- chat ---------- */ export type ChatSchemaType = { @@ -59,7 +54,6 @@ export type UserChatItemFileItemType = { url: string; }; export type UserChatItemValueItemType = { - type: ChatItemValueTypeEnum.text | ChatItemValueTypeEnum.file; text?: { content: string; }; @@ -72,7 +66,6 @@ export type UserChatItemType = { }; export type SystemChatItemValueItemType = { - type: ChatItemValueTypeEnum.text; text?: { content: string; }; @@ -83,12 +76,16 @@ export type SystemChatItemType = { }; export type AIChatItemValueItemType = { - id?: string; // Client concat stream response - type: - | ChatItemValueTypeEnum.text - | ChatItemValueTypeEnum.reasoning - | ChatItemValueTypeEnum.tool - | ChatItemValueTypeEnum.interactive; + id?: string; +} & RequireOnlyOne<{ + text: { + content: string; + }; + reasoning: { + content: string; + }; + tool: ToolModuleResponseItemType; + interactive: WorkflowInteractiveResponseType; text?: { content: string; @@ -96,12 +93,15 @@ export type AIChatItemValueItemType = { reasoning?: { content: string; }; - tools?: ToolModuleResponseItemType[]; interactive?: WorkflowInteractiveResponseType; -}; + + // Abandon + tools?: ToolModuleResponseItemType[]; +}>; export type AIChatItemType = { obj: ChatRoleEnum.AI; value: AIChatItemValueItemType[]; + subAppsValue?: Record; memories?: Record; userGoodFeedback?: string; userBadFeedback?: string; diff --git a/packages/global/core/chat/utils.ts b/packages/global/core/chat/utils.ts index 606e7bebc..e8d4d9e59 100644 --- a/packages/global/core/chat/utils.ts +++ b/packages/global/core/chat/utils.ts @@ -1,6 +1,6 @@ import { type DispatchNodeResponseType } from '../workflow/runtime/type'; import { FlowNodeTypeEnum } from '../workflow/node/constant'; -import { ChatItemValueTypeEnum, ChatRoleEnum, ChatSourceEnum } from './constants'; +import { ChatRoleEnum, ChatSourceEnum } from './constants'; import { type AIChatItemValueItemType, type ChatHistoryItemResType, @@ -24,7 +24,7 @@ export const concatHistories = (histories1: ChatItemType[], histories2: ChatItem export const getChatTitleFromChatMessage = (message?: ChatItemType, defaultValue = '新对话') => { // @ts-ignore - const textMsg = message?.value.find((item) => item.type === ChatItemValueTypeEnum.text); + const textMsg = message?.value.find((item) => 'text' in item && item.text); if (textMsg?.text?.content) { return textMsg.text.content.slice(0, 20); @@ -168,14 +168,14 @@ export const removeAIResponseCite = { return ( input?.filter((item) => { - if (item.type === ChatItemValueTypeEnum.text && !item.text?.content?.trim()) { - return false; + if (item.text?.content?.trim()) { + return true; } // type 为 'file' 时 key 和 url 不能同时为空 - if (item.type === ChatItemValueTypeEnum.file && !item.file?.key && !item.file?.url) { + if (!item.file?.key && !item.file?.url) { return false; } - return true; + return false; }) || [] ); }; diff --git a/packages/global/core/workflow/runtime/constants.ts b/packages/global/core/workflow/runtime/constants.ts index 2f7740501..2548c97bd 100644 --- a/packages/global/core/workflow/runtime/constants.ts +++ b/packages/global/core/workflow/runtime/constants.ts @@ -11,6 +11,7 @@ export enum SseResponseEventEnum { toolCall = 'toolCall', // tool start toolParams = 'toolParams', // tool params return toolResponse = 'toolResponse', // tool response return + flowResponses = 'flowResponses', // sse response request updateVariables = 'updateVariables', diff --git a/packages/global/core/workflow/runtime/utils.ts b/packages/global/core/workflow/runtime/utils.ts index f79770486..804246bd7 100644 --- a/packages/global/core/workflow/runtime/utils.ts +++ b/packages/global/core/workflow/runtime/utils.ts @@ -1,6 +1,6 @@ import json5 from 'json5'; import { replaceVariable, valToStr } from '../../../common/string/tools'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants'; +import { ChatRoleEnum } from '../../../core/chat/constants'; import type { ChatItemType, NodeOutputItemType } from '../../../core/chat/type'; import { ChatCompletionRequestMessageRoleEnum } from '../../ai/constants'; import { @@ -173,11 +173,7 @@ export const getLastInteractiveValue = ( if (lastAIMessage) { const lastValue = lastAIMessage.value[lastAIMessage.value.length - 1]; - if ( - !lastValue || - lastValue.type !== ChatItemValueTypeEnum.interactive || - !lastValue.interactive - ) { + if (!lastValue || !lastValue.interactive) { return; } diff --git a/packages/service/core/chat/pushChatLog.ts b/packages/service/core/chat/pushChatLog.ts index afc14ab3f..0749523a1 100644 --- a/packages/service/core/chat/pushChatLog.ts +++ b/packages/service/core/chat/pushChatLog.ts @@ -2,8 +2,11 @@ import { addLog } from '../../common/system/log'; import { MongoChatItem } from './chatItemSchema'; import { MongoChat } from './chatSchema'; import axios from 'axios'; -import { type AIChatItemType, type UserChatItemType } from '@fastgpt/global/core/chat/type'; -import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; +import { + type AIChatItemType, + type ChatItemType, + type UserChatItemType +} from '@fastgpt/global/core/chat/type'; export type Metadata = { [key: string]: { @@ -94,9 +97,9 @@ const pushChatLogInternal = async ({ // Pop last two items const question = chatItemHuman.value .map((item) => { - if (item.type === ChatItemValueTypeEnum.text) { + if (item.text) { return item.text?.content; - } else if (item.type === ChatItemValueTypeEnum.file) { + } else if (item.file) { if (item.file?.type === 'image') { return `![${item.file?.name}](${item.file?.url})`; } diff --git a/packages/service/core/chat/saveChat.ts b/packages/service/core/chat/saveChat.ts index ad5f35ca0..f29a8492d 100644 --- a/packages/service/core/chat/saveChat.ts +++ b/packages/service/core/chat/saveChat.ts @@ -1,7 +1,7 @@ import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type.d'; import { MongoApp } from '../app/schema'; import type { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { MongoChatItem } from './chatItemSchema'; import { MongoChat } from './chatSchema'; import { addLog } from '../../common/system/log'; @@ -44,7 +44,7 @@ type Props = { const beforProcess = (props: Props) => { // Remove url props.userContent.value.forEach((item) => { - if (item.type === ChatItemValueTypeEnum.file && item.file?.key) { + if (item.file?.key) { item.file.url = ''; } }); @@ -61,7 +61,7 @@ const afterProcess = async ({ .map((item) => { if (item.value && Array.isArray(item.value)) { return item.value.map((valueItem) => { - if (valueItem.type === ChatItemValueTypeEnum.file && valueItem.file?.key) { + if ('file' in valueItem && valueItem.file?.key) { return valueItem.file.key; } }); @@ -349,11 +349,7 @@ export const updateInteractiveChat = async (props: Props) => { // Update interactive value const interactiveValue = chatItem.value[chatItem.value.length - 1]; - if ( - !interactiveValue || - interactiveValue.type !== ChatItemValueTypeEnum.interactive || - !interactiveValue.interactive?.params - ) { + if (!interactiveValue || !interactiveValue.interactive?.params) { return; } diff --git a/packages/service/core/chat/utils.ts b/packages/service/core/chat/utils.ts index 1991e1163..201974894 100644 --- a/packages/service/core/chat/utils.ts +++ b/packages/service/core/chat/utils.ts @@ -1,4 +1,4 @@ -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import type { ChatItemType } from '@fastgpt/global/core/chat/type'; import { getS3ChatSource } from '../../common/s3/sources/chat'; import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; @@ -10,7 +10,7 @@ export const addPreviewUrlToChatItems = async ( ) => { async function addToChatflow(item: ChatItemType) { for await (const value of item.value) { - if (value.type === ChatItemValueTypeEnum.file && value.file && value.file.key) { + if ('file' in value && value.file?.key) { value.file.url = await s3ChatSource.createGetChatFileURL({ key: value.file.key, external: true @@ -23,7 +23,7 @@ export const addPreviewUrlToChatItems = async ( for (let j = 0; j < item.value.length; j++) { const value = item.value[j]; - if (value.type !== ChatItemValueTypeEnum.text) continue; + if (!('text' in value)) continue; const inputValueString = value.text?.content || ''; const parsedInputValue = JSON.parse(inputValueString) as FlowNodeInputItemType[]; diff --git a/packages/service/core/workflow/dispatch/ai/agent/index.ts b/packages/service/core/workflow/dispatch/ai/agent/index.ts index e8ec2a45c..90af79b7d 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/index.ts @@ -165,6 +165,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise return chats2GPTMessages({ messages: chatHistories, reserveId: false }); })(); + const userMessages = chats2GPTMessages({ messages: [ { @@ -224,12 +225,11 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise event: SseResponseEventEnum.toolCall, data: { tool: { - id: call.id, + id: `${nodeId}/${call.function.name}`, toolName: toolNode?.name || call.function.name, toolAvatar: toolNode?.avatar || '', functionName: call.function.name, - params: call.function.arguments ?? '', - response: '' + params: call.function.arguments ?? '' } } }); @@ -240,11 +240,7 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise event: SseResponseEventEnum.toolParams, data: { tool: { - id: call.id, - toolName: '', - toolAvatar: '', - params, - response: '' + params } } }); @@ -253,15 +249,31 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise handleToolResponse: async ({ call, messages }) => { const toolId = call.function.name; const childWorkflowStreamResponse = getWorkflowChildResponseWrite({ + subAppId: `${nodeId}/${toolId}`, id: call.id, fn: workflowStreamResponse }); + const onReasoning = ({ text }: { text: string }) => { + childWorkflowStreamResponse?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + reasoning_content: text + }) + }); + }; + const onStreaming = ({ text }: { text: string }) => { + childWorkflowStreamResponse?.({ + event: SseResponseEventEnum.answer, + data: textAdaptGptResponse({ + text + }) + }); + }; const { response, usages = [], - isEnd, - streamResponse = true + isEnd } = await (async () => { try { if (toolId === SubAppIds.stop) { @@ -278,28 +290,14 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise temperature, top_p: aiChatTopP, stream, - onStreaming({ text }) { - //TODO: 需要一个新的 plan sse event - childWorkflowStreamResponse?.({ - event: SseResponseEventEnum.toolResponse, - data: { - tool: { - id: call.id, - toolName: '', - toolAvatar: '', - params: '', - response: text - } - } - }); - } + onReasoning, + onStreaming }); return { response, usages, - isEnd: false, - streamResponse: false + isEnd: false }; } else if (toolId === SubAppIds.model) { const { systemPrompt, task } = parseToolArgs<{ @@ -314,26 +312,13 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise stream, systemPrompt, task, - onStreaming({ text }) { - childWorkflowStreamResponse?.({ - event: SseResponseEventEnum.toolResponse, - data: { - tool: { - id: call.id, - toolName: '', - toolAvatar: '', - params: '', - response: text - } - } - }); - } + onReasoning, + onStreaming }); return { response, usages, - isEnd: false, - streamResponse: false + isEnd: false }; } else if (toolId === SubAppIds.fileRead) { const { file_indexes } = parseToolArgs<{ @@ -434,12 +419,11 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise ) { const fn = node.flowNodeType === FlowNodeTypeEnum.appModule ? dispatchApp : dispatchPlugin; - console.log(requestParams, 22); + const { response, usages } = await fn({ ...props, node, - // stream: false, - workflowStreamResponse: undefined, + workflowStreamResponse: childWorkflowStreamResponse, callParams: { appId: node.pluginId, version: node.version, @@ -470,20 +454,16 @@ export const dispatchRunAgent = async (props: DispatchAgentModuleProps): Promise })(); // Push stream response - if (streamResponse) { - childWorkflowStreamResponse?.({ - event: SseResponseEventEnum.toolResponse, - data: { - tool: { - id: call.id, - toolName: '', - toolAvatar: '', - params: '', - response - } + workflowStreamResponse?.({ + id: call.id, + event: SseResponseEventEnum.toolResponse, + data: { + tool: { + id: call.id, + response } - }); - } + } + }); // TODO: 推送账单 diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts index eef1c67bf..85f5fe14c 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/app/index.ts @@ -15,7 +15,6 @@ import { } from '@fastgpt/global/core/workflow/runtime/utils'; import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; import { getUserChatInfoAndAuthTeamPoints } from '../../../../../../../support/permission/auth/team'; -import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import { getChildAppRuntimeById } from '../../../../../../app/plugin/controller'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils'; @@ -92,7 +91,6 @@ export const dispatchApp = async (props: Props): Promise variables: childrenRunVariables, query: [ { - type: ChatItemValueTypeEnum.text, text: { content: userChatInput } diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/model/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/model/index.ts index 6b5ca62ec..d269096cc 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/sub/model/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/model/index.ts @@ -14,6 +14,7 @@ type ModelAgentConfig = { type DispatchModelAgentProps = ModelAgentConfig & { systemPrompt: string; task: string; + onReasoning: ResponseEvents['onReasoning']; onStreaming: ResponseEvents['onStreaming']; }; @@ -29,6 +30,7 @@ export async function dispatchModelAgent({ stream, systemPrompt, task, + onReasoning, onStreaming }: DispatchModelAgentProps): Promise { const modelData = getLLMModel(model); @@ -56,6 +58,7 @@ export async function dispatchModelAgent({ top_p, stream }, + onReasoning, onStreaming }); diff --git a/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts index 3ea72139a..5fa9ed43b 100644 --- a/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts +++ b/packages/service/core/workflow/dispatch/ai/agent/sub/plan/index.ts @@ -20,6 +20,7 @@ type PlanAgentConfig = { type DispatchPlanAgentProps = PlanAgentConfig & { messages: ChatCompletionMessageParam[]; tools: ChatCompletionTool[]; + onReasoning: ResponseEvents['onReasoning']; onStreaming: ResponseEvents['onStreaming']; }; @@ -36,6 +37,7 @@ export const dispatchPlanAgent = async ({ temperature, top_p, stream, + onReasoning, onStreaming }: DispatchPlanAgentProps): Promise => { const modelData = getLLMModel(model); @@ -63,6 +65,7 @@ export const dispatchPlanAgent = async ({ toolCallMode: modelData.toolChoice ? 'toolChoice' : 'prompt', parallel_tool_calls: true }, + onReasoning, onStreaming }); diff --git a/packages/service/core/workflow/dispatch/ai/classifyQuestion.ts b/packages/service/core/workflow/dispatch/ai/classifyQuestion.ts index fb81df7fc..1e58ada8f 100644 --- a/packages/service/core/workflow/dispatch/ai/classifyQuestion.ts +++ b/packages/service/core/workflow/dispatch/ai/classifyQuestion.ts @@ -1,6 +1,6 @@ import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflow/template/system/classifyQuestion/type'; import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; @@ -114,7 +114,6 @@ const completions = async ({ obj: ChatRoleEnum.System, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: getCQSystemPrompt({ systemPrompt, @@ -132,7 +131,6 @@ const completions = async ({ obj: ChatRoleEnum.Human, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: userChatInput } diff --git a/packages/service/core/workflow/dispatch/ai/extract.ts b/packages/service/core/workflow/dispatch/ai/extract.ts index 35e9890e3..4a092f47e 100644 --- a/packages/service/core/workflow/dispatch/ai/extract.ts +++ b/packages/service/core/workflow/dispatch/ai/extract.ts @@ -1,7 +1,7 @@ import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt'; import { filterGPTMessageByMaxContext } from '../../../ai/llm/utils'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/template/system/contextExtract/type'; import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { @@ -196,7 +196,6 @@ const toolChoice = async (props: ActionProps) => { obj: ChatRoleEnum.System, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: getExtractJsonToolPrompt({ systemPrompt: description, @@ -211,7 +210,6 @@ const toolChoice = async (props: ActionProps) => { obj: ChatRoleEnum.Human, value: [ { - type: ChatItemValueTypeEnum.text, text: { content } @@ -300,7 +298,6 @@ const completions = async (props: ActionProps) => { obj: ChatRoleEnum.System, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: getExtractJsonPrompt({ systemPrompt: description, @@ -316,7 +313,6 @@ const completions = async (props: ActionProps) => { obj: ChatRoleEnum.Human, value: [ { - type: ChatItemValueTypeEnum.text, text: { content } diff --git a/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts b/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts index d2c746725..a9c5eb5a3 100644 --- a/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts +++ b/packages/service/core/workflow/dispatch/ai/tool/toolCall.ts @@ -17,7 +17,6 @@ import { formatToolResponse, initToolCallEdges, initToolNodes, parseToolArgs } f import { computedMaxToken } from '../../../../ai/utils'; import { sliceStrStartEnd } from '@fastgpt/global/common/string/tools'; import type { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; -import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { createLLMResponse } from '../../../../ai/llm/request'; import { toolValueTypeList, valueTypeJsonSchemaMap } from '@fastgpt/global/core/workflow/constants'; @@ -338,12 +337,12 @@ export const runToolCall = async ( } }); }, - onToolParam({ tool, params }) { + onToolParam({ call, params }) { workflowStreamResponse?.({ event: SseResponseEventEnum.toolParams, data: { tool: { - id: tool.id, + id: call.id, toolName: '', toolAvatar: '', params, @@ -474,7 +473,7 @@ export const runToolCall = async ( const toolChildAssistants = flatToolsResponseData .map((item) => item.assistantResponses) .flat() - .filter((item) => item.type !== ChatItemValueTypeEnum.interactive); // 交互节点留着下次记录 + .filter((item) => !item.interactive); // 交互节点留着下次记录 const concatAssistantResponses = [ ...assistantResponses, ...toolNodeAssistant.value, diff --git a/packages/service/core/workflow/dispatch/ai/utils.ts b/packages/service/core/workflow/dispatch/ai/utils.ts index 2386047ae..04e4ab3a0 100644 --- a/packages/service/core/workflow/dispatch/ai/utils.ts +++ b/packages/service/core/workflow/dispatch/ai/utils.ts @@ -1,5 +1,4 @@ import { replaceVariable, sliceStrStartEnd } from '@fastgpt/global/common/string/tools'; -import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import type { AIChatItemValueItemType, UserChatItemValueItemType @@ -18,7 +17,7 @@ import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/co // Assistant process export const filterToolResponseToPreview = (response: AIChatItemValueItemType[]) => { return response.map((item) => { - if (item.type === ChatItemValueTypeEnum.tool) { + if (item.tools) { const formatTools = item.tools?.map((tool) => { return { ...tool, @@ -106,15 +105,15 @@ export const toolCallMessagesAdapt = ({ if (skip) return userInput; - const files = userInput.filter((item) => item.type === 'file'); + const files = userInput.filter((item) => item.file); if (files.length > 0) { const filesCount = files.filter((file) => file.file?.type === 'file').length; const imgCount = files.filter((file) => file.file?.type === 'image').length; - if (userInput.some((item) => item.type === 'text')) { + if (userInput.some((item) => item.text)) { return userInput.map((item) => { - if (item.type === 'text') { + if (item.text) { const text = item.text?.content || ''; return { @@ -131,7 +130,6 @@ export const toolCallMessagesAdapt = ({ // Every input is a file return [ { - type: ChatItemValueTypeEnum.text, text: { content: getMultiplePrompt({ fileCount: filesCount, imgCount, question: '' }) } diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index 6ebbc666b..ee593621a 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -24,7 +24,6 @@ import type { } from '@fastgpt/global/core/workflow/runtime/type'; import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type.d'; import { getErrText } from '@fastgpt/global/common/error/utils'; -import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils'; import { checkNodeRunStatus, @@ -136,7 +135,7 @@ export async function dispatchWorkFlow({ // Add preview url to chat items await addPreviewUrlToChatItems(histories, 'chatFlow'); for (const item of query) { - if (item.type !== ChatItemValueTypeEnum.file || !item.file?.key) continue; + if (!item.file?.key) continue; item.file.url = await getS3ChatSource().createGetChatFileURL({ key: item.file.key, external: true @@ -703,7 +702,6 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise; }) => void; diff --git a/packages/service/core/workflow/dispatch/utils.ts b/packages/service/core/workflow/dispatch/utils.ts index 409b8317d..0b098315a 100644 --- a/packages/service/core/workflow/dispatch/utils.ts +++ b/packages/service/core/workflow/dispatch/utils.ts @@ -40,7 +40,7 @@ export const getWorkflowResponseWrite = ({ id?: string; showNodeStatus?: boolean; }) => { - const fn: WorkflowResponseType = ({ id, event, data }) => { + const fn: WorkflowResponseType = ({ id, subAppId, event, data }) => { if (!res || res.closed || !streamResponse) return; // Forbid show detail @@ -64,6 +64,7 @@ export const getWorkflowResponseWrite = ({ event: detail ? event : undefined, data: JSON.stringify({ ...data, + ...(subAppId && detail && { subAppId }), ...(id && detail && { responseValueId: id }) }) }); @@ -72,14 +73,16 @@ export const getWorkflowResponseWrite = ({ }; export const getWorkflowChildResponseWrite = ({ id, + subAppId, fn }: { id: string; + subAppId: string; fn?: WorkflowResponseType; }): WorkflowResponseType | undefined => { if (!fn) return; return (e: Parameters[0]) => { - return fn({ ...e, id }); + return fn({ ...e, id, subAppId }); }; }; diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatAvatar.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatAvatar.tsx index 56ba48acf..17c4b3e22 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatAvatar.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatAvatar.tsx @@ -2,8 +2,9 @@ import Avatar from '@fastgpt/web/components/common/Avatar'; import { Box } from '@chakra-ui/react'; import { useTheme } from '@chakra-ui/system'; import React from 'react'; +import type { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; -const ChatAvatar = ({ src, type }: { src?: string; type: 'Human' | 'AI' }) => { +const ChatAvatar = ({ src, type }: { src?: string; type: `${ChatRoleEnum}` }) => { const theme = useTheme(); return ( { return ( @@ -92,6 +86,7 @@ const HumanContentCard = React.memo( ); const AIContentCard = React.memo(function AIContentCard({ chatValue, + subAppsValue = {}, dataId, isLastChild, isChatting, @@ -100,6 +95,7 @@ const AIContentCard = React.memo(function AIContentCard({ }: { dataId: string; chatValue: AIChatItemValueItemType[]; + subAppsValue?: AIChatItemType['subAppsValue']; isLastChild: boolean; isChatting: boolean; questionGuides: string[]; @@ -115,6 +111,7 @@ const AIContentCard = React.memo(function AIContentCard({ chatItemDataId={dataId} key={key} value={value} + subAppValue={value.tool ? subAppsValue[value.tool.id] : undefined} isLastResponseValue={isLastChild && i === chatValue.length - 1} isChatting={isChatting} onOpenCiteModal={onOpenCiteModal} @@ -129,12 +126,12 @@ const AIContentCard = React.memo(function AIContentCard({ }); const ChatItem = (props: Props) => { - const { type, avatar, statusBoxData, children, isLastChild, questionGuides = [], chat } = props; + const { avatar, statusBoxData, children, isLastChild, questionGuides = [], chat } = props; const { isPc } = useSystem(); const styleMap: BoxProps = { - ...(type === ChatRoleEnum.Human + ...(chat.obj === ChatRoleEnum.Human ? { order: 0, borderRadius: '8px 0 8px 8px', @@ -183,57 +180,63 @@ const ChatItem = (props: Props) => { 2. Auto-complete the last textnode */ const splitAiResponseResults = useMemo(() => { - if (chat.obj !== ChatRoleEnum.AI) return [chat.value]; + if (chat.obj === ChatRoleEnum.Human) return [chat.value]; - // Remove empty text node - const filterList = chat.value.filter((item, i) => { - if (item.type === ChatItemValueTypeEnum.text && !item.text?.content?.trim()) { - return false; - } - return item; - }); - - const groupedValues: AIChatItemValueItemType[][] = []; - let currentGroup: AIChatItemValueItemType[] = []; - - filterList.forEach((value) => { - if (value.type === 'interactive') { - if (currentGroup.length > 0) { - groupedValues.push(currentGroup); - currentGroup = []; + if (chat.obj === ChatRoleEnum.AI) { + // Remove empty text node + const filterList = chat.value.filter((item, i) => { + if (item.text && !item.text.content?.trim()) { + return false; } + if (item.reasoning && !item.reasoning.content?.trim()) { + return false; + } + return item; + }); - groupedValues.push([value]); - } else { - currentGroup.push(value); - } - }); + const groupedValues: AIChatItemValueItemType[][] = []; + let currentGroup: AIChatItemValueItemType[] = []; - if (currentGroup.length > 0) { - groupedValues.push(currentGroup); - } - - // Check last group is interactive, Auto add a empty text node(animation) - const lastGroup = groupedValues[groupedValues.length - 1]; - if (isChatting || groupedValues.length === 0) { - if ( - (lastGroup && - lastGroup[lastGroup.length - 1] && - lastGroup[lastGroup.length - 1].type === ChatItemValueTypeEnum.interactive) || - groupedValues.length === 0 - ) { - groupedValues.push([ - { - type: ChatItemValueTypeEnum.text, - text: { - content: '' - } + filterList.forEach((value) => { + if (value.interactive) { + if (currentGroup.length > 0) { + groupedValues.push(currentGroup); + currentGroup = []; } - ]); + + groupedValues.push([value]); + } else { + currentGroup.push(value); + } + }); + + if (currentGroup.length > 0) { + groupedValues.push(currentGroup); } + + // Check last group is interactive, Auto add a empty text node(animation) + const lastGroup = groupedValues[groupedValues.length - 1]; + if (isChatting || groupedValues.length === 0) { + if ( + (lastGroup && + lastGroup[lastGroup.length - 1] && + lastGroup[lastGroup.length - 1].interactive) || + groupedValues.length === 0 + ) { + groupedValues.push([ + { + text: { + content: '' + } + } + ]); + } + } + + return groupedValues; } - return groupedValues; + return []; }, [chat.obj, chat.value, isChatting]); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData); @@ -278,6 +281,8 @@ const ChatItem = (props: Props) => { } ); + const aiSubApps = 'subApps' in chat ? chat.subApps : undefined; + return ( { > {/* control icon */} - {isChatting && type === ChatRoleEnum.AI && isLastChild ? null : ( + {isChatting && chat.obj === ChatRoleEnum.AI && isLastChild ? null : ( {chat.time && (isPc || isChatLog) && ( { )} - + {/* Workflow status */} {!!chatStatusMap && statusBoxData && isLastChild && showNodeStatus && ( @@ -334,88 +339,91 @@ const ChatItem = (props: Props) => { )} {/* content */} - {splitAiResponseResults.map((value, i) => ( - - { + return ( + - {type === ChatRoleEnum.Human && } - {type === ChatRoleEnum.AI && ( - <> - - {i === splitAiResponseResults.length - 1 && ( - + {chat.obj === ChatRoleEnum.Human && } + {chat.obj === ChatRoleEnum.AI && ( + <> + - )} - - )} - {/* Example: Response tags. A set of dialogs only needs to be displayed once*/} - {i === splitAiResponseResults.length - 1 && ( - <> - {/* error message */} - {!!chat.errorMsg && ( - - - - {chat.errorMsg} + {i === splitAiResponseResults.length - 1 && ( + + )} + + )} + {/* Example: Response tags. A set of dialogs only needs to be displayed once*/} + {i === splitAiResponseResults.length - 1 && ( + <> + {/* error message */} + {!!chat.errorMsg && ( + + + + {chat.errorMsg} + + )} + {children} + + )} + {/* 对话框底部的复制按钮 */} + {chat.obj == ChatRoleEnum.AI && + !('interactive' in value[0]) && + (!isChatting || (isChatting && !isLastChild)) && ( + + + copyData(formatChatValue2InputType(value).text ?? '')} + /> + )} - {children} - - )} - {/* 对话框底部的复制按钮 */} - {type == ChatRoleEnum.AI && - value[0]?.type !== 'interactive' && - (!isChatting || (isChatting && !isLastChild)) && ( - - - copyData(formatChatValue2InputType(value).text ?? '')} - /> - - - )} - - - ))} + + + ); + })} ); }; diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useChatBox.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useChatBox.tsx index 299e0f6f1..dd33cab9d 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useChatBox.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useChatBox.tsx @@ -3,7 +3,6 @@ import { type ChatItemType } from '@fastgpt/global/core/chat/type'; import { useCallback } from 'react'; import { htmlTemplate } from '@/web/core/chat/constants'; import { fileDownload } from '@/web/common/file/utils'; -import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import { useTranslation } from 'next-i18next'; export const useChatBox = () => { const { t } = useTranslation(); @@ -47,13 +46,13 @@ export const useChatBox = () => { .map((item) => { let result = `Role: ${item.obj}\n`; const content = item.value.map((item) => { - if (item.type === ChatItemValueTypeEnum.text) { + if (item.text) { return item.text?.content; - } else if (item.type === ChatItemValueTypeEnum.file) { + } else if ('file' in item && item.file) { return ` ![${item.file?.name}](${item.file?.url}) `; - } else if (item.type === ChatItemValueTypeEnum.tool) { + } else if ('tools' in item && item.tools) { return ` \`\`\`Tool ${JSON.stringify(item.tools, null, 2)} diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx index f3bbc6ce1..c8199bdb9 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx @@ -35,11 +35,7 @@ import ChatInput from './Input/ChatInput'; import ChatBoxDivider from '../../Divider'; import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; import { getNanoid } from '@fastgpt/global/common/string/tools'; -import { - ChatItemValueTypeEnum, - ChatRoleEnum, - ChatStatusEnum -} from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants'; import { getInteractiveByHistories, formatChatValue2InputType, @@ -239,159 +235,255 @@ const ChatBox = ({ status, name, tool, + subAppId, interactive, - autoTTSResponse, variables, nodeResponse, - durationSeconds + durationSeconds, + autoTTSResponse }: generatingMessageProps & { autoTTSResponse?: boolean }) => { setChatRecords((state) => state.map((item, index) => { if (index !== state.length - 1) return item; if (item.obj !== ChatRoleEnum.AI) return item; - autoTTSResponse && splitText2Audio(formatChatValue2InputType(item.value).text || ''); + if (subAppId) { + let subAppValue = cloneDeep(item.subAppsValue?.[subAppId]); + if (!subAppValue) { + console.log("Can't find the sub app"); + return item; + } - const updateIndex = (() => { - if (!responseValueId) return item.value.length - 1; - const index = item.value.findIndex((item) => item.id === responseValueId); - if (index !== -1) return index; - return item.value.length - 1; - })(); - const updateValue: AIChatItemValueItemType = cloneDeep(item.value[updateIndex]); + const updateIndex = (() => { + if (!responseValueId) return subAppValue.length - 1; + const index = subAppValue.findIndex((item) => item.id === responseValueId); + if (index !== -1) return index; + return subAppValue.length - 1; + })(); + const updateValue = subAppValue[updateIndex]; + + if ( + event === SseResponseEventEnum.answer || + event === SseResponseEventEnum.fastAnswer + ) { + if (reasoningText) { + if (updateValue?.reasoning) { + updateValue.reasoning.content += reasoningText; + } else { + const val: AIChatItemValueItemType = { + id: responseValueId, + reasoning: { + content: reasoningText + } + }; + subAppValue = [ + ...subAppValue.slice(0, updateIndex), + val, + ...subAppValue.slice(updateIndex + 1) + ]; + } + } + if (text) { + if (updateValue?.text) { + updateValue.text.content += text; + } else { + const val: AIChatItemValueItemType = { + id: responseValueId, + text: { + content: text + } + }; + subAppValue = [ + ...subAppValue.slice(0, updateIndex), + val, + ...subAppValue.slice(updateIndex + 1) + ]; + } + } + } + + if (event === SseResponseEventEnum.toolCall && tool) { + const val: AIChatItemValueItemType = { + id: responseValueId, + tool + }; + subAppValue = [ + ...subAppValue.slice(0, updateIndex), + val, + ...subAppValue.slice(updateIndex + 1) + ]; + } + if (event === SseResponseEventEnum.toolParams && tool && updateValue?.tool) { + if (tool.params) { + updateValue.tool.params += tool.params; + } + return item; + } + if (event === SseResponseEventEnum.toolResponse && tool && updateValue?.tool) { + if (tool.response) { + updateValue.tool.response += tool.response; + } + return item; + } - if (event === SseResponseEventEnum.flowNodeResponse && nodeResponse) { return { ...item, - responseData: item.responseData - ? [...item.responseData, nodeResponse] - : [nodeResponse] + subAppsValue: { + ...item.subAppsValue, + [subAppId]: subAppValue + } }; - } else if (event === SseResponseEventEnum.flowNodeStatus && status) { - return { - ...item, - status, - moduleName: name - }; - } else if (reasoningText) { - if (updateValue.type === ChatItemValueTypeEnum.reasoning && updateValue.reasoning) { - updateValue.reasoning.content += reasoningText; + } else { + autoTTSResponse && splitText2Audio(formatChatValue2InputType(item.value).text || ''); + + const updateIndex = (() => { + if (!responseValueId) return item.value.length - 1; + const index = item.value.findIndex((item) => item.id === responseValueId); + if (index !== -1) return index; + return item.value.length - 1; + })(); + const updateValue: AIChatItemValueItemType = cloneDeep(item.value[updateIndex]); + updateValue.id = responseValueId; + console.log(event, tool, updateValue); + if (event === SseResponseEventEnum.flowNodeResponse && nodeResponse) { return { ...item, - value: [ - ...item.value.slice(0, updateIndex), - updateValue, - ...item.value.slice(updateIndex + 1) - ] + responseData: item.responseData + ? [...item.responseData, nodeResponse] + : [nodeResponse] }; - } else { + } + if (event === SseResponseEventEnum.flowNodeStatus && status) { + return { + ...item, + status, + moduleName: name + }; + } + if ( + event === SseResponseEventEnum.answer || + event === SseResponseEventEnum.fastAnswer + ) { + if (reasoningText) { + if (updateValue?.reasoning) { + updateValue.reasoning.content += reasoningText; + return { + ...item, + value: [ + ...item.value.slice(0, updateIndex), + updateValue, + ...item.value.slice(updateIndex + 1) + ] + }; + } else { + const val: AIChatItemValueItemType = { + id: responseValueId, + reasoning: { + content: reasoningText + } + }; + return { + ...item, + value: [...item.value, val] + }; + } + } + if (text) { + if (updateValue?.text) { + updateValue.text.content += text; + return { + ...item, + value: [ + ...item.value.slice(0, updateIndex), + updateValue, + ...item.value.slice(updateIndex + 1) + ] + }; + } else { + const newValue: AIChatItemValueItemType = { + id: responseValueId, + text: { + content: text + } + }; + return { + ...item, + value: item.value.concat(newValue) + }; + } + } + } + + // Tool call + if (event === SseResponseEventEnum.toolCall && tool) { const val: AIChatItemValueItemType = { - type: ChatItemValueTypeEnum.reasoning, - reasoning: { - content: reasoningText + id: responseValueId, + tool: { + ...tool, + response: '' } }; return { ...item, + subAppsValue: { + ...item.subAppsValue, + [tool.id]: [] + }, value: [...item.value, val] }; } - } else if ( - (event === SseResponseEventEnum.answer || event === SseResponseEventEnum.fastAnswer) && - text - ) { - if (!updateValue || !updateValue.text) { - const newValue: AIChatItemValueItemType = { - type: ChatItemValueTypeEnum.text, - text: { - content: text - } + if (event === SseResponseEventEnum.toolParams && tool && updateValue?.tool) { + if (tool.params) { + updateValue.tool.params += tool.params; + return { + ...item, + value: [ + ...item.value.slice(0, updateIndex), + updateValue, + ...item.value.slice(updateIndex + 1) + ] + }; + } + return item; + } + if (event === SseResponseEventEnum.toolResponse && tool && updateValue?.tool) { + if (tool.response) { + // replace tool response + updateValue.tool.response += tool.response; + + return { + ...item, + value: [ + ...item.value.slice(0, updateIndex), + updateValue, + ...item.value.slice(updateIndex + 1) + ] + }; + } + return item; + } + + if (event === SseResponseEventEnum.updateVariables && variables) { + resetVariables({ variables }); + } + if (event === SseResponseEventEnum.interactive && interactive) { + const val: AIChatItemValueItemType = { + interactive }; + return { ...item, - value: item.value.concat(newValue) - }; - } else { - updateValue.text.content += text; - return { - ...item, - value: [ - ...item.value.slice(0, updateIndex), - updateValue, - ...item.value.slice(updateIndex + 1) - ] + value: item.value.concat(val) + }; + } + if (event === SseResponseEventEnum.workflowDuration && durationSeconds) { + return { + ...item, + durationSeconds: item.durationSeconds + ? +(item.durationSeconds + durationSeconds).toFixed(2) + : durationSeconds }; } - } else if (event === SseResponseEventEnum.toolCall && tool) { - const val: AIChatItemValueItemType = { - id: responseValueId, - type: ChatItemValueTypeEnum.tool, - tools: [tool] - }; - return { - ...item, - value: item.value.concat(val) - }; - } else if ( - event === SseResponseEventEnum.toolParams && - tool && - updateValue.type === ChatItemValueTypeEnum.tool && - updateValue?.tools - ) { - updateValue.tools = updateValue.tools.map((item) => { - if (item.id === tool.id) { - item.params += tool.params; - } - return item; - }); - return { - ...item, - value: [ - ...item.value.slice(0, updateIndex), - updateValue, - ...item.value.slice(updateIndex + 1) - ] - }; - } else if ( - event === SseResponseEventEnum.toolResponse && - tool && - updateValue.type === ChatItemValueTypeEnum.tool && - updateValue?.tools - ) { - // replace tool response - updateValue.tools = updateValue.tools.map((item) => { - if (item.id === tool.id) { - item.response = item.response ? item.response + tool.response : tool.response; - } - return item; - }); - return { - ...item, - value: [ - ...item.value.slice(0, updateIndex), - updateValue, - ...item.value.slice(updateIndex + 1) - ] - }; - } else if (event === SseResponseEventEnum.updateVariables && variables) { - resetVariables({ variables }); - } else if (event === SseResponseEventEnum.interactive) { - const val: AIChatItemValueItemType = { - type: ChatItemValueTypeEnum.interactive, - interactive - }; - - return { - ...item, - value: item.value.concat(val) - }; - } else if (event === SseResponseEventEnum.workflowDuration && durationSeconds) { - return { - ...item, - durationSeconds: item.durationSeconds - ? +(item.durationSeconds + durationSeconds).toFixed(2) - : durationSeconds - }; } return item; @@ -524,7 +616,6 @@ const ChatBox = ({ hideInUI, value: [ ...files.map((file) => ({ - type: ChatItemValueTypeEnum.file, file: { type: file.type, name: file.name, @@ -536,7 +627,6 @@ const ChatBox = ({ ...(text ? [ { - type: ChatItemValueTypeEnum.text, text: { content: text } @@ -551,7 +641,6 @@ const ChatBox = ({ obj: ChatRoleEnum.AI, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: '' } @@ -1045,7 +1134,6 @@ const ChatBox = ({ {item.obj === ChatRoleEnum.Human && !item.hideInUI && ( - item.type === 'file' && item.file + 'file' in item && item.file ? { id: item.file.url, type: item.file.type, @@ -57,7 +57,7 @@ export const getInteractiveByHistories = ( if ( lastMessageValue && - lastMessageValue.type === ChatItemValueTypeEnum.interactive && + 'interactive' in lastMessageValue && !!lastMessageValue?.interactive?.params ) { const finalInteractive = extractDeepestInteractive(lastMessageValue.interactive); @@ -88,13 +88,10 @@ export const setInteractiveResultToHistories = ( if (i !== histories.length - 1) return item; const value = item.value.map((val, i) => { - if ( - i !== item.value.length - 1 || - val.type !== ChatItemValueTypeEnum.interactive || - !val.interactive - ) { + if (i !== item.value.length - 1) { return val; } + if (!('interactive' in val) || !val.interactive) return val; const finalInteractive = extractDeepestInteractive(val.interactive); if (finalInteractive.type === 'userSelect') { diff --git a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/context.tsx b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/context.tsx index 717a5948a..d23c06d28 100644 --- a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/context.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/context.tsx @@ -9,7 +9,7 @@ import { type FieldValues } from 'react-hook-form'; import { PluginRunBoxTabEnum } from './constants'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { getNanoid } from '@fastgpt/global/common/string/tools'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { type generatingMessageProps } from '../type'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { useTranslation } from 'next-i18next'; @@ -99,7 +99,6 @@ const PluginRunContextProvider = ({ ) { if (!lastValue || !lastValue.text) { const newValue: AIChatItemValueItemType = { - type: ChatItemValueTypeEnum.text, text: { content: text } @@ -117,19 +116,13 @@ const PluginRunContextProvider = ({ } } else if (event === SseResponseEventEnum.toolCall && tool) { const val: AIChatItemValueItemType = { - type: ChatItemValueTypeEnum.tool, tools: [tool] }; return { ...item, value: item.value.concat(val) }; - } else if ( - event === SseResponseEventEnum.toolParams && - tool && - lastValue.type === ChatItemValueTypeEnum.tool && - lastValue?.tools - ) { + } else if (event === SseResponseEventEnum.toolParams && tool && lastValue?.tools) { lastValue.tools = lastValue.tools.map((item) => { if (item.id === tool.id) { item.params += tool.params; @@ -145,7 +138,7 @@ const PluginRunContextProvider = ({ return { ...item, value: item.value.map((val) => { - if (val.type === ChatItemValueTypeEnum.tool && val.tools) { + if (val.tools) { const tools = val.tools.map((item) => item.id === tool.id ? { ...item, response: tool.response } : item ); @@ -205,7 +198,6 @@ const PluginRunContextProvider = ({ obj: ChatRoleEnum.AI, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: '' } diff --git a/projects/app/src/components/core/chat/ChatContainer/type.d.ts b/projects/app/src/components/core/chat/ChatContainer/type.d.ts index a39d9b4e2..7a80ee6ff 100644 --- a/projects/app/src/components/core/chat/ChatContainer/type.d.ts +++ b/projects/app/src/components/core/chat/ChatContainer/type.d.ts @@ -2,6 +2,7 @@ import type { StreamResponseType } from '@/web/common/api/fetch'; import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type'; import type { ChatHistoryItemResType, + AIChatItemValueItemType, ToolModuleResponseItemType } from '@fastgpt/global/core/chat/type'; import { ChatSiteItemType } from '@fastgpt/global/core/chat/type'; @@ -11,6 +12,7 @@ import type { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workf export type generatingMessageProps = { event: SseResponseEventEnum; responseValueId?: string; + subAppId?: string; text?: string; reasoningText?: string; name?: string; diff --git a/projects/app/src/components/core/chat/components/AIResponseBox.tsx b/projects/app/src/components/core/chat/components/AIResponseBox.tsx index 322b069cb..8c29b8087 100644 --- a/projects/app/src/components/core/chat/components/AIResponseBox.tsx +++ b/projects/app/src/components/core/chat/components/AIResponseBox.tsx @@ -10,11 +10,9 @@ import { Flex, HStack } from '@chakra-ui/react'; -import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import type { AIChatItemValueItemType, - ToolModuleResponseItemType, - UserChatItemValueItemType + ToolModuleResponseItemType } from '@fastgpt/global/core/chat/type'; import React, { useState, useCallback, useMemo } from 'react'; import MyIcon from '@fastgpt/web/components/common/Icon'; @@ -128,75 +126,91 @@ const RenderText = React.memo(function RenderText({ const RenderTool = React.memo( function RenderTool({ showAnimation, - tools + tool, + subAppValue, + + chatItemDataId, + isChatting, + onOpenCiteModal }: { showAnimation: boolean; - tools: ToolModuleResponseItemType[]; + tool: ToolModuleResponseItemType; + subAppValue?: AIChatItemValueItemType[]; + + chatItemDataId: string; + isChatting: boolean; + onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void; }) { const { t } = useSafeTranslation(); const [userOnchange, setUserOnchange] = useState(false); + const formatJson = (string: string) => { + try { + return JSON.stringify(JSON.parse(string), null, 2); + } catch (error) { + return string; + } + }; + const params = formatJson(tool.params); + const response = formatJson(tool.response); return ( - - {tools.map((tool) => { - const formatJson = (string: string) => { - try { - return JSON.stringify(JSON.parse(string), null, 2); - } catch (error) { - return string; - } - }; - const toolParams = formatJson(tool.params); - const toolResponse = formatJson(tool.response); - - return ( - { - setUserOnchange(true); - }} - > - - - - - {t(tool.toolName)} - - {showAnimation && !tool.response && } - - - - {toolParams && toolParams !== '{}' && ( - - - - )} - {toolResponse && ( - - )} - - - - ); - })} - + { + setUserOnchange(true); + }} + > + + + + + {t(tool.toolName)} + + {showAnimation && !tool.response && } + + + + {params && params !== '{}' && ( + + + + )} + {response && ( + + )} + {subAppValue && ( + + {subAppValue.map((value, index) => ( + + ))} + + )} + + + ); }, (prevProps, nextProps) => isEqual(prevProps, nextProps) @@ -297,17 +311,19 @@ const RenderPaymentPauseInteractive = React.memo(function RenderPaymentPauseInte const AIResponseBox = ({ chatItemDataId, value, + subAppValue, isLastResponseValue, isChatting, onOpenCiteModal }: { chatItemDataId: string; - value: UserChatItemValueItemType | AIChatItemValueItemType; + value: AIChatItemValueItemType; + subAppValue?: AIChatItemValueItemType[]; isLastResponseValue: boolean; isChatting: boolean; onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void; }) => { - if (value.type === ChatItemValueTypeEnum.text && value.text) { + if ('text' in value && value.text) { return ( ); } - if (value.type === ChatItemValueTypeEnum.reasoning && value.reasoning) { + if ('reasoning' in value && value.reasoning) { return ( ); } - if (value.type === ChatItemValueTypeEnum.tool && value.tools) { - return ; + if ('tool' in value && value.tool) { + return ( + + ); } - if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) { + if ('interactive' in value && value.interactive) { const finalInteractive = extractDeepestInteractive(value.interactive); if (finalInteractive.type === 'userSelect') { return ; @@ -341,6 +366,22 @@ const AIResponseBox = ({ return ; } } + + // Abandon + if ('tools' in value && value.tools) { + return value.tools.map((tool) => ( + + + + )); + } return null; }; export default React.memo(AIResponseBox); diff --git a/projects/app/src/pageComponents/app/detail/Edit/Agent/utils.ts b/projects/app/src/pageComponents/app/detail/Edit/Agent/utils.ts index 5a69b1ccb..7fddaad97 100644 --- a/projects/app/src/pageComponents/app/detail/Edit/Agent/utils.ts +++ b/projects/app/src/pageComponents/app/detail/Edit/Agent/utils.ts @@ -50,7 +50,7 @@ export const appWorkflow2AgentForm = ({ defaultAppForm.aiSettings.aiChatTopP = inputMap.get(NodeInputKeyEnum.aiChatTopP); const subApps = inputMap.get(NodeInputKeyEnum.subApps) as FlowNodeTemplateType[]; - console.log(subApps); + if (subApps) { subApps.forEach((subApp) => { defaultAppForm.selectedTools.push(subApp); diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderDebug/NodeDebugResponse.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderDebug/NodeDebugResponse.tsx index 1ea956b5a..99bc6b53a 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderDebug/NodeDebugResponse.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderDebug/NodeDebugResponse.tsx @@ -12,7 +12,7 @@ import { } from '@/components/core/chat/components/Interactive/InteractiveComponents'; import { type UserInputInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import { type ChatItemType, type UserChatItemValueItemType } from '@fastgpt/global/core/chat/type'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm'; import { WorkflowActionsContext } from '../../../../context/workflowActionsContext'; import { WorkflowDebugContext } from '../../../../context/workflowDebugContext'; @@ -102,7 +102,6 @@ const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => { const updatedQuery: UserChatItemValueItemType[] = [ { - type: ChatItemValueTypeEnum.text, text: { content: userContent } } ]; @@ -112,7 +111,6 @@ const NodeDebugResponse = ({ nodeId, debugResult }: NodeDebugResponseProps) => { obj: ChatRoleEnum.AI, value: [ { - type: ChatItemValueTypeEnum.interactive, interactive: { ...interactive, entryNodeIds: workflowDebugData.entryNodeIds || [], diff --git a/projects/app/src/pages/api/core/chat/chatTest.ts b/projects/app/src/pages/api/core/chat/chatTest.ts index 40d5ff341..12123c86e 100644 --- a/projects/app/src/pages/api/core/chat/chatTest.ts +++ b/projects/app/src/pages/api/core/chat/chatTest.ts @@ -41,11 +41,8 @@ import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants import { getWorkflowToolInputsFromStoreNodes } from '@fastgpt/global/core/app/tool/workflowTool/utils'; import { getChatItems } from '@fastgpt/service/core/chat/controller'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; -import { - ChatItemValueTypeEnum, - ChatRoleEnum, - ChatSourceEnum -} from '@fastgpt/global/core/chat/constants'; + +import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import { saveChat, updateInteractiveChat } from '@fastgpt/service/core/chat/saveChat'; import { getLocale } from '@fastgpt/service/common/middle/i18n'; import { formatTime2YMDHM } from '@fastgpt/global/common/string/time'; @@ -108,7 +105,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { obj: ChatRoleEnum.Human, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: 'tool test' } } ] diff --git a/projects/app/src/pages/api/core/chat/getPaginationRecords.ts b/projects/app/src/pages/api/core/chat/getPaginationRecords.ts index 770e55cd8..ea95cf024 100644 --- a/projects/app/src/pages/api/core/chat/getPaginationRecords.ts +++ b/projects/app/src/pages/api/core/chat/getPaginationRecords.ts @@ -8,7 +8,7 @@ import { getChatItems } from '@fastgpt/service/core/chat/controller'; import { authChatCrud } from '@/service/support/permission/auth/chat'; import { MongoApp } from '@fastgpt/service/core/app/schema'; import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { filterPublicNodeResponseData, removeAIResponseCite @@ -85,7 +85,7 @@ async function handler( }); if (showNodeStatus === false) { - item.value = item.value.filter((v) => v.type !== ChatItemValueTypeEnum.tool); + item.value = item.value.filter((v) => v.tools); } } }); diff --git a/projects/app/src/pages/api/core/chat/item/delete.ts b/projects/app/src/pages/api/core/chat/item/delete.ts index e90f1d195..5bc1cc46f 100644 --- a/projects/app/src/pages/api/core/chat/item/delete.ts +++ b/projects/app/src/pages/api/core/chat/item/delete.ts @@ -6,7 +6,7 @@ import { NextAPI } from '@/service/middleware/entry'; import { type ApiRequestProps } from '@fastgpt/service/type/next'; import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; import { MongoChatItemResponse } from '@fastgpt/service/core/chat/chatItemResponseSchema'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat'; async function handler(req: ApiRequestProps, res: NextApiResponse) { @@ -39,7 +39,7 @@ async function handler(req: ApiRequestProps, res: NextApiRe if (item?.obj === ChatRoleEnum.Human && delFile) { const s3ChatSource = getS3ChatSource(); for (const value of item.value) { - if (value.type === ChatItemValueTypeEnum.file && value.file?.key) { + if (value.file?.key) { await s3ChatSource.deleteChatFileByKey(value.file.key); } } diff --git a/projects/app/src/service/core/app/utils.ts b/projects/app/src/service/core/app/utils.ts index 1583fdd0b..80e02a08d 100644 --- a/projects/app/src/service/core/app/utils.ts +++ b/projects/app/src/service/core/app/utils.ts @@ -2,11 +2,7 @@ import { getErrText } from '@fastgpt/global/common/error/utils'; import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { delay, retryFn } from '@fastgpt/global/common/system/utils'; -import { - ChatItemValueTypeEnum, - ChatRoleEnum, - ChatSourceEnum -} from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import { type UserChatItemValueItemType } from '@fastgpt/global/core/chat/type'; import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { @@ -48,7 +44,6 @@ export const getScheduleTriggerApp = async () => { const { nodes, edges, chatConfig } = await retryFn(() => getAppLatestVersion(app._id, app)); const userQuery: UserChatItemValueItemType[] = [ { - type: ChatItemValueTypeEnum.text, text: { content: app.scheduledTriggerConfig?.defaultPrompt } diff --git a/projects/app/src/service/support/mcp/utils.ts b/projects/app/src/service/support/mcp/utils.ts index 7a930ae93..1ab4f100d 100644 --- a/projects/app/src/service/support/mcp/utils.ts +++ b/projects/app/src/service/support/mcp/utils.ts @@ -20,11 +20,7 @@ import { updateWorkflowToolInputByVariables } from '@fastgpt/service/core/app/tool/workflowTool/utils'; import { getWorkflowToolInputsFromStoreNodes } from '@fastgpt/global/core/app/tool/workflowTool/utils'; -import { - ChatItemValueTypeEnum, - ChatRoleEnum, - ChatSourceEnum -} from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import { getWorkflowEntryNodeIds, storeEdges2RuntimeEdges, @@ -188,7 +184,6 @@ export const callMcpServerTool = async ({ key, toolName, inputs }: toolCallProps obj: ChatRoleEnum.Human, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: variables.question } diff --git a/projects/app/src/web/common/api/fetch.ts b/projects/app/src/web/common/api/fetch.ts index ded8a08f2..64ae4339f 100644 --- a/projects/app/src/web/common/api/fetch.ts +++ b/projects/app/src/web/common/api/fetch.ts @@ -25,19 +25,27 @@ export type StreamResponseType = { type ResponseQueueItemType = | { responseValueId?: string; + subAppId?: string; event: SseResponseEventEnum.fastAnswer | SseResponseEventEnum.answer; text?: string; reasoningText?: string; } - | { responseValueId?: string; event: SseResponseEventEnum.interactive; [key: string]: any } | { responseValueId?: string; + subAppId?: string; + event: SseResponseEventEnum.interactive; + [key: string]: any; + } + | { + responseValueId?: string; + subAppId?: string; event: | SseResponseEventEnum.toolCall | SseResponseEventEnum.toolParams | SseResponseEventEnum.toolResponse; - [key: string]: any; + tools: any; }; + class FatalError extends Error {} export const streamFetch = ({ @@ -82,7 +90,7 @@ export const streamFetch = ({ if (abortCtrl.signal.aborted) { responseQueue.forEach((item) => { onMessage(item); - if (isAnswerEvent(item.event) && item.text) { + if (isAnswerEvent(item.event) && 'text' in item && item.text) { responseText += item.text; } }); @@ -94,7 +102,7 @@ export const streamFetch = ({ for (let i = 0; i < fetchCount; i++) { const item = responseQueue[i]; onMessage(item); - if (isAnswerEvent(item.event) && item.text) { + if (isAnswerEvent(item.event) && 'text' in item && item.text) { responseText += item.text; } } @@ -183,13 +191,14 @@ export const streamFetch = ({ })(); if (typeof parseJson !== 'object') return; - const { responseValueId, ...rest } = parseJson; + const { responseValueId, subAppId, ...rest } = parseJson; // console.log(parseJson, event); if (event === SseResponseEventEnum.answer) { const reasoningText = rest.choices?.[0]?.delta?.reasoning_content || ''; pushDataToQueue({ responseValueId, + subAppId, event, reasoningText }); @@ -198,6 +207,7 @@ export const streamFetch = ({ for (const item of text) { pushDataToQueue({ responseValueId, + subAppId, event, text: item }); @@ -206,6 +216,7 @@ export const streamFetch = ({ const reasoningText = rest.choices?.[0]?.delta?.reasoning_content || ''; pushDataToQueue({ responseValueId, + subAppId, event, reasoningText }); @@ -213,6 +224,7 @@ export const streamFetch = ({ const text = rest.choices?.[0]?.delta?.content || ''; pushDataToQueue({ responseValueId, + subAppId, event, text }); @@ -223,6 +235,7 @@ export const streamFetch = ({ ) { pushDataToQueue({ responseValueId, + subAppId, event, ...rest }); @@ -239,6 +252,7 @@ export const streamFetch = ({ } else if (event === SseResponseEventEnum.interactive) { pushDataToQueue({ responseValueId, + subAppId, event, ...rest }); diff --git a/test/cases/global/core/chat/utils.test.ts b/test/cases/global/core/chat/utils.test.ts index 81fd4903a..3db1a37fa 100644 --- a/test/cases/global/core/chat/utils.test.ts +++ b/test/cases/global/core/chat/utils.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import type { ChatItemType } from '@fastgpt/global/core/chat/type'; import { @@ -19,7 +19,7 @@ describe('transformPreviewHistories', () => { const histories: ChatItemType[] = [ { obj: ChatRoleEnum.AI, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }], + value: [{ text: { content: 'test response' } }], responseData: [ { ...mockResponseData, @@ -33,7 +33,7 @@ describe('transformPreviewHistories', () => { expect(result[0]).toEqual({ obj: ChatRoleEnum.AI, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }], + value: [{ text: { content: 'test response' } }], responseData: undefined, llmModuleAccount: 1, totalQuoteList: [], @@ -45,7 +45,7 @@ describe('transformPreviewHistories', () => { const histories: ChatItemType[] = [ { obj: ChatRoleEnum.AI, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }], + value: [{ text: { content: 'test response' } }], responseData: [ { ...mockResponseData, @@ -59,7 +59,7 @@ describe('transformPreviewHistories', () => { expect(result[0]).toEqual({ obj: ChatRoleEnum.AI, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }], + value: [{ text: { content: 'test response' } }], responseData: undefined, llmModuleAccount: 1, totalQuoteList: undefined, @@ -72,7 +72,7 @@ describe('addStatisticalDataToHistoryItem', () => { it('should return original item if obj is not AI', () => { const item: ChatItemType = { obj: ChatRoleEnum.Human, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }] + value: [{ text: { content: 'test response' } }] }; expect(addStatisticalDataToHistoryItem(item)).toBe(item); @@ -81,7 +81,7 @@ describe('addStatisticalDataToHistoryItem', () => { it('should return original item if totalQuoteList is already defined', () => { const item: ChatItemType = { obj: ChatRoleEnum.AI, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }], + value: [{ text: { content: 'test response' } }], totalQuoteList: [] }; @@ -91,7 +91,7 @@ describe('addStatisticalDataToHistoryItem', () => { it('should return original item if responseData is undefined', () => { const item: ChatItemType = { obj: ChatRoleEnum.AI, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }] + value: [{ text: { content: 'test response' } }] }; expect(addStatisticalDataToHistoryItem(item)).toBe(item); @@ -100,7 +100,7 @@ describe('addStatisticalDataToHistoryItem', () => { it('should calculate statistics correctly', () => { const item: ChatItemType = { obj: ChatRoleEnum.AI, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }], + value: [{ text: { content: 'test response' } }], responseData: [ { ...mockResponseData, @@ -141,7 +141,7 @@ describe('addStatisticalDataToHistoryItem', () => { it('should handle empty arrays and undefined values', () => { const item: ChatItemType = { obj: ChatRoleEnum.AI, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }], + value: [{ text: { content: 'test response' } }], responseData: [ { ...mockResponseData, @@ -163,7 +163,7 @@ describe('addStatisticalDataToHistoryItem', () => { it('should handle nested plugin and loop details', () => { const item: ChatItemType = { obj: ChatRoleEnum.AI, - value: [{ type: ChatItemValueTypeEnum.text, text: { content: 'test response' } }], + value: [{ text: { content: 'test response' } }], responseData: [ { ...mockResponseData, diff --git a/test/cases/service/core/app/workflow/dispatch/utils.test.ts b/test/cases/service/core/app/workflow/dispatch/utils.test.ts index 66e6ccb96..9641d8a4a 100644 --- a/test/cases/service/core/app/workflow/dispatch/utils.test.ts +++ b/test/cases/service/core/app/workflow/dispatch/utils.test.ts @@ -313,7 +313,7 @@ describe('valueTypeFormat', () => { }); import { getHistories } from '@fastgpt/service/core/workflow/dispatch/utils'; -import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; +import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import type { ChatItemType } from '@fastgpt/global/core/chat/type'; describe('getHistories test', async () => { @@ -322,7 +322,6 @@ describe('getHistories test', async () => { obj: ChatRoleEnum.System, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: '你好' } @@ -333,7 +332,6 @@ describe('getHistories test', async () => { obj: ChatRoleEnum.Human, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: '你好' } @@ -344,7 +342,6 @@ describe('getHistories test', async () => { obj: ChatRoleEnum.AI, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: '你好2' } @@ -355,7 +352,6 @@ describe('getHistories test', async () => { obj: ChatRoleEnum.Human, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: '你好3' } @@ -366,7 +362,6 @@ describe('getHistories test', async () => { obj: ChatRoleEnum.AI, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: '你好4' } @@ -392,7 +387,6 @@ describe('getHistories test', async () => { obj: ChatRoleEnum.Human, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: '你好' } @@ -407,7 +401,6 @@ describe('getHistories test', async () => { obj: ChatRoleEnum.Human, value: [ { - type: ChatItemValueTypeEnum.text, text: { content: '你好' } diff --git a/test/cases/service/core/app/workflow/workflowDispatch.test.ts b/test/cases/service/core/app/workflow/workflowDispatch.test.ts index 7231fd182..505b13402 100644 --- a/test/cases/service/core/app/workflow/workflowDispatch.test.ts +++ b/test/cases/service/core/app/workflow/workflowDispatch.test.ts @@ -6,7 +6,6 @@ import { getWorkflowEntryNodeIds, storeNodes2RuntimeNodes } from '@fastgpt/global/core/workflow/runtime/utils'; -import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants'; vi.mock(import('@fastgpt/service/common/string/tiktoken'), async (importOriginal) => { @@ -60,7 +59,6 @@ const testWorkflow = async (path: string) => { variables, query: [ { - type: ChatItemValueTypeEnum.text, text: { content: '你是谁' }