diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx index 04b884388e..f6a9e896f9 100644 --- a/web/app/components/base/chat/chat/answer/index.tsx +++ b/web/app/components/base/chat/chat/answer/index.tsx @@ -21,6 +21,7 @@ import BasicContent from './basic-content' import More from './more' import Operation from './operation' import SuggestedQuestions from './suggested-questions' +import ToolCalls from './tool-calls' import WorkflowProcessItem from './workflow-process' type AnswerProps = { @@ -61,6 +62,7 @@ const Answer: FC = ({ workflowProcess, allFiles, message_files, + toolCalls, } = item const hasAgentThoughts = !!agent_thoughts?.length @@ -154,6 +156,11 @@ const Answer: FC = ({ /> ) } + { + !!toolCalls?.length && ( + + ) + } { responding && contentIsEmpty && !hasAgentThoughts && (
diff --git a/web/app/components/base/chat/chat/answer/tool-calls/index.tsx b/web/app/components/base/chat/chat/answer/tool-calls/index.tsx new file mode 100644 index 0000000000..9de41233aa --- /dev/null +++ b/web/app/components/base/chat/chat/answer/tool-calls/index.tsx @@ -0,0 +1,19 @@ +import type { ToolCallItem } from '../../type' +import ToolCallsItem from './item' + +type ToolCallsProps = { + toolCalls: ToolCallItem[] +} +const ToolCalls = ({ + toolCalls, +}: ToolCallsProps) => { + return ( +
+ {toolCalls.map((toolCall: ToolCallItem) => ( + + ))} +
+ ) +} + +export default ToolCalls diff --git a/web/app/components/base/chat/chat/answer/tool-calls/item.tsx b/web/app/components/base/chat/chat/answer/tool-calls/item.tsx new file mode 100644 index 0000000000..539f1a83ba --- /dev/null +++ b/web/app/components/base/chat/chat/answer/tool-calls/item.tsx @@ -0,0 +1,75 @@ +import type { ToolCallItem } from '../../type' +import { + RiArrowDownSLine, +} from '@remixicon/react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' + +type ToolCallsItemProps = { + payload: ToolCallItem +} +const ToolCallsItem = ({ + payload, +}: ToolCallsItemProps) => { + const { t } = useTranslation() + const [expand, setExpand] = useState(false) + return ( +
+
setExpand(!expand)}> +
{payload.tool_name}
+ { + !!payload.tool_elapsed_time && ( +
+ {payload.tool_elapsed_time?.toFixed(3)} + s +
+ ) + } + +
+ { + expand && ( +
+
+ { + payload.is_thought && ( +
{payload.tool_output}
+ ) + } + { + !payload.is_thought && ( + {t('common.input', { ns: 'workflow' })}
} + language={CodeLanguage.json} + value={JSON.parse(payload.tool_arguments || '{}')} + isJSONStringifyBeauty + /> + ) + } + { + !payload.is_thought && ( + {t('common.output', { ns: 'workflow' })}
} + language={CodeLanguage.json} + value={{ + answer: payload.tool_output, + }} + isJSONStringifyBeauty + /> + ) + } +
+ ) + } + + ) +} + +export default ToolCallsItem diff --git a/web/app/components/base/chat/chat/type.ts b/web/app/components/base/chat/chat/type.ts index 291b0ae064..56ffdc7e06 100644 --- a/web/app/components/base/chat/chat/type.ts +++ b/web/app/components/base/chat/chat/type.ts @@ -64,6 +64,17 @@ export type CitationItem = { word_count: number } +export type ToolCallItem = { + is_thought?: boolean + tool_call_id?: string + tool_name?: string + tool_arguments?: string + tool_files?: string[] + tool_error?: string + tool_output?: string + tool_elapsed_time?: number +} + export type IChatItem = { id: string content: string @@ -104,6 +115,7 @@ export type IChatItem = { siblingIndex?: number prevSibling?: string nextSibling?: string + toolCalls?: ToolCallItem[] } export type Metadata = { diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index 2c46833df8..068be40023 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -270,9 +270,53 @@ export const useChat = ( handleRun( bodyParams, { - onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { + onData: (message: string, isFirstMessage: boolean, { + conversationId: newConversationId, + messageId, + taskId, + chunk_type, + tool_call_id, + tool_name, + tool_arguments, + tool_files, + tool_error, + tool_elapsed_time, + }: any) => { responseItem.content = responseItem.content + message + if (chunk_type === 'tool_call') { + if (!responseItem.toolCalls) + responseItem.toolCalls = [] + responseItem.toolCalls?.push({ + tool_call_id, + tool_name, + tool_arguments, + tool_files, + tool_error, + tool_elapsed_time, + }) + } + + if (chunk_type === 'tool_result') { + const currentToolCallIndex = responseItem.toolCalls?.findIndex(item => item.tool_call_id === tool_call_id) ?? -1 + + if (currentToolCallIndex > -1) + responseItem.toolCalls![currentToolCallIndex].tool_output = message + } + + if (chunk_type === 'thought_start') { + responseItem.toolCalls?.push({ + is_thought: true, + tool_elapsed_time, + }) + } + + if (chunk_type === 'thought') { + const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.is_thought) ?? -1 + if (currentThoughtIndex > -1) + responseItem.toolCalls![currentThoughtIndex].tool_output = message + } + if (messageId && !hasSetResponseId) { questionItem.id = `question-${messageId}` responseItem.id = messageId diff --git a/web/service/base.ts b/web/service/base.ts index d9f3dba53a..bbb05cf357 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -40,6 +40,13 @@ export type IOnDataMoreInfo = { messageId: string errorMessage?: string errorCode?: string + chunk_type?: 'text' | 'tool_call' | 'tool_result' | 'thought' | 'thought_start' + tool_call_id?: string + tool_name?: string + tool_arguments?: string + tool_files?: string[] + tool_error?: string + tool_elapsed_time?: number } export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void @@ -234,6 +241,13 @@ export const handleStream = ( conversationId: bufferObj.conversation_id, taskId: bufferObj.task_id, messageId: bufferObj.id, + chunk_type: bufferObj.chunk_type, + tool_call_id: bufferObj.tool_call_id, + tool_name: bufferObj.tool_name, + tool_arguments: bufferObj.tool_arguments, + tool_files: bufferObj.tool_files, + tool_error: bufferObj.tool_error, + tool_elapsed_time: bufferObj.tool_elapsed_time, }) isFirstMessage = false }