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 index f51daa7497..66118006fe 100644 --- a/web/app/components/base/chat/chat/answer/tool-calls/index.tsx +++ b/web/app/components/base/chat/chat/answer/tool-calls/index.tsx @@ -9,9 +9,9 @@ const ToolCalls = ({ }: ToolCallsProps) => { return (
- {toolCalls.map((toolCall: ToolCallItem) => ( + {toolCalls.map((toolCall: ToolCallItem, index: number) => ( diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index 9b8a9b11dc..c9b9a29f34 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -319,6 +319,9 @@ export const useChat = ( return player } + let toolCallId = '' + let thoughtId = '' + ssePost( url, { @@ -326,7 +329,19 @@ export const useChat = ( }, { isPublicAPI, - onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => { + onData: (message: string, isFirstMessage: boolean, { + conversationId: newConversationId, + messageId, + taskId, + chunk_type, + tool_icon, + tool_icon_dark, + tool_name, + tool_arguments, + tool_files, + tool_error, + tool_elapsed_time, + }: any) => { if (!isAgentMode) { responseItem.content = responseItem.content + message } @@ -336,6 +351,57 @@ export const useChat = ( lastThought.thought = lastThought.thought + message // need immer setAutoFreeze } + if (chunk_type === 'tool_call') { + if (!responseItem.toolCalls) + responseItem.toolCalls = [] + toolCallId = uuidV4() + responseItem.toolCalls?.push({ + id: toolCallId, + type: 'tool', + toolName: tool_name, + toolArguments: tool_arguments, + toolIcon: tool_icon, + toolIconDark: tool_icon_dark, + }) + } + + if (chunk_type === 'tool_result') { + const currentToolCallIndex = responseItem.toolCalls?.findIndex(item => item.id === toolCallId) ?? -1 + + if (currentToolCallIndex > -1) { + responseItem.toolCalls![currentToolCallIndex].toolError = tool_error + responseItem.toolCalls![currentToolCallIndex].toolDuration = tool_elapsed_time + responseItem.toolCalls![currentToolCallIndex].toolFiles = tool_files + responseItem.toolCalls![currentToolCallIndex].toolOutput = message + } + } + + if (chunk_type === 'thought_start') { + if (!responseItem.toolCalls) + responseItem.toolCalls = [] + thoughtId = uuidV4() + responseItem.toolCalls.push({ + id: thoughtId, + type: 'thought', + thoughtOutput: '', + }) + } + + if (chunk_type === 'thought') { + const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1 + if (currentThoughtIndex > -1) { + responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message + } + } + + if (chunk_type === 'thought_end') { + const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1 + if (currentThoughtIndex > -1) { + responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message + responseItem.toolCalls![currentThoughtIndex].thoughtCompleted = true + } + } + if (messageId && !hasSetResponseId) { questionItem.id = `question-${messageId}` responseItem.id = messageId diff --git a/web/app/components/base/chat/chat/type.ts b/web/app/components/base/chat/chat/type.ts index 1a6ff44f2d..5f2740dce2 100644 --- a/web/app/components/base/chat/chat/type.ts +++ b/web/app/components/base/chat/chat/type.ts @@ -64,11 +64,6 @@ export type CitationItem = { word_count: number } -export type IconObject = { - background: string - content: string -} - export type IChatItem = { id: string content: string diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index 4d95db7fcf..b06e194485 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -150,6 +150,10 @@ export const LLM_OUTPUT_STRUCT: Var[] = [ variable: 'usage', type: VarType.object, }, + { + variable: 'generation', + type: VarType.object, + }, ] export const KNOWLEDGE_RETRIEVAL_OUTPUT_STRUCT: Var[] = [ diff --git a/web/app/components/workflow/nodes/llm/panel.tsx b/web/app/components/workflow/nodes/llm/panel.tsx index 9d336d25b4..9ddd3ecd88 100644 --- a/web/app/components/workflow/nodes/llm/panel.tsx +++ b/web/app/components/workflow/nodes/llm/panel.tsx @@ -315,6 +315,11 @@ const Panel: FC> = ({ type="object" description={t(`${i18nPrefix}.outputVars.usage`, { ns: 'workflow' })} /> + {inputs.structured_output_enabled && ( <> 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 6cdb1c194a..7b82f10630 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -15,6 +15,7 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' +import { v4 as uuidV4 } from 'uuid' import { getProcessedInputs, processOpeningStatement, @@ -266,6 +267,8 @@ export const useChat = ( } let hasSetResponseId = false + let toolCallId = '' + let thoughtId = '' handleRun( bodyParams, @@ -277,7 +280,6 @@ export const useChat = ( chunk_type, tool_icon, tool_icon_dark, - tool_call_id, tool_name, tool_arguments, tool_files, @@ -289,43 +291,52 @@ export const useChat = ( if (chunk_type === 'tool_call') { if (!responseItem.toolCalls) responseItem.toolCalls = [] + toolCallId = uuidV4() responseItem.toolCalls?.push({ + id: toolCallId, type: 'tool', - tool_call_id, - tool_name, - tool_arguments, - tool_icon, - tool_icon_dark, - tool_files, - tool_error, - tool_elapsed_time, + toolName: tool_name, + toolArguments: tool_arguments, + toolIcon: tool_icon, + toolIconDark: tool_icon_dark, }) } if (chunk_type === 'tool_result') { - const currentToolCallIndex = responseItem.toolCalls?.findIndex(item => item.tool_call_id === tool_call_id) ?? -1 + const currentToolCallIndex = responseItem.toolCalls?.findIndex(item => item.id === toolCallId) ?? -1 - if (currentToolCallIndex > -1) - responseItem.toolCalls![currentToolCallIndex].tool_output = message + if (currentToolCallIndex > -1) { + responseItem.toolCalls![currentToolCallIndex].toolError = tool_error + responseItem.toolCalls![currentToolCallIndex].toolDuration = tool_elapsed_time + responseItem.toolCalls![currentToolCallIndex].toolFiles = tool_files + responseItem.toolCalls![currentToolCallIndex].toolOutput = message + } } if (chunk_type === 'thought_start') { - console.log(message, 'xx1') - responseItem.toolCalls?.push({ + if (!responseItem.toolCalls) + responseItem.toolCalls = [] + thoughtId = uuidV4() + responseItem.toolCalls.push({ + id: thoughtId, type: 'thought', - tool_elapsed_time, + thoughtOutput: '', }) } - if (chunk_type === 'thought_end') { - console.log(message, 'xx2') - // const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.is_thought) ?? -1 - // if (currentThoughtIndex > -1) - // responseItem.toolCalls![currentThoughtIndex].tool_output = message + if (chunk_type === 'thought') { + const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1 + if (currentThoughtIndex > -1) { + responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message + } } - if (chunk_type === 'thought') { - console.log(message, 'xx3') + if (chunk_type === 'thought_end') { + const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1 + if (currentThoughtIndex > -1) { + responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message + responseItem.toolCalls![currentThoughtIndex].thoughtCompleted = true + } } if (messageId && !hasSetResponseId) { diff --git a/web/app/components/workflow/run/llm-log/llm-result-panel.tsx b/web/app/components/workflow/run/llm-log/llm-result-panel.tsx index 0223a4cdec..56687050a9 100644 --- a/web/app/components/workflow/run/llm-log/llm-result-panel.tsx +++ b/web/app/components/workflow/run/llm-log/llm-result-panel.tsx @@ -22,18 +22,30 @@ const LLMResultPanel: FC = ({ onBack, }) => { const { t } = useTranslation() - const formattedList = list.map(item => ({ - type: item.type, - tool_call_id: item.provider, - tool_name: item.name, - tool_arguments: item.type === 'tool' ? item.output.arguments : undefined, - tool_icon: item.icon, - tool_icon_dark: item.icon_dark, - tool_files: [], - tool_error: item.error, - tool_output: item.type === 'tool' ? item.output.output : item.output, - tool_elapsed_time: item.duration, - })) + const formattedList = list.map((item) => { + if (item.type === 'tool') { + return { + type: 'tool', + toolName: item.name, + toolProvider: item.provider, + toolIcon: item.icon, + toolIconDark: item.icon_dark, + toolArguments: item.output.arguments, + toolOutput: item.output.output, + toolDuration: item.duration, + } + } + + return { + type: 'model', + modelName: item.name, + modelProvider: item.provider, + modelIcon: item.icon, + modelIconDark: item.icon_dark, + modelOutput: item.output, + modelDuration: item.duration, + } + }) return (
diff --git a/web/app/components/workflow/run/llm-log/tool-call-item.tsx b/web/app/components/workflow/run/llm-log/tool-call-item.tsx index 026f85df9a..e3e5802655 100644 --- a/web/app/components/workflow/run/llm-log/tool-call-item.tsx +++ b/web/app/components/workflow/run/llm-log/tool-call-item.tsx @@ -4,6 +4,8 @@ import { } from '@remixicon/react' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import AppIcon from '@/app/components/base/app-icon' +import { Thinking } from '@/app/components/base/icons/src/vender/workflow' import BlockIcon from '@/app/components/workflow/block-icon' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' @@ -24,17 +26,73 @@ const ToolCallItemComponent = ({
-
setExpand(!expand)}> - -
{payload.tool_name}
+
{ + setExpand(!expand) + }} + > { - !!payload.tool_elapsed_time && ( + payload.type === 'thought' && ( + + ) + } + { + payload.type === 'tool' && ( + + ) + } + { + payload.type === 'model' && ( + + ) + } + { + payload.type === 'thought' && ( +
+ { + payload.thoughtCompleted && !expand && (payload.thoughtOutput || '') as string + } + { + payload.thoughtCompleted && expand && 'THOUGHT' + } + { + !payload.thoughtCompleted && 'THINKING...' + } +
+ ) + } + { + payload.type === 'tool' && ( +
{payload.toolName}
+ ) + } + { + payload.type === 'model' && ( +
{payload.modelName}
+ ) + } + { + !!payload.toolDuration && (
- {payload.tool_elapsed_time?.toFixed(1)} + {payload.toolDuration?.toFixed(1)} + s +
+ ) + } + { + !!payload.modelDuration && ( +
+ {payload.modelDuration?.toFixed(1)} s
) @@ -46,8 +104,8 @@ const ToolCallItemComponent = ({
{ - payload.type === 'thought' && typeof payload.tool_output === 'string' && ( -
{payload.tool_output}
+ payload.type === 'thought' && typeof payload.thoughtOutput === 'string' && ( +
{payload.thoughtOutput}
) } { @@ -56,7 +114,7 @@ const ToolCallItemComponent = ({ readOnly title={
{t('common.data', { ns: 'workflow' })}
} language={CodeLanguage.json} - value={payload.tool_output} + value={payload.modelOutput} isJSONStringifyBeauty /> ) @@ -67,7 +125,7 @@ const ToolCallItemComponent = ({ readOnly title={
{t('common.input', { ns: 'workflow' })}
} language={CodeLanguage.json} - value={payload.tool_arguments} + value={payload.toolArguments} isJSONStringifyBeauty /> ) @@ -79,7 +137,7 @@ const ToolCallItemComponent = ({ className="mt-1" title={
{t('common.output', { ns: 'workflow' })}
} language={CodeLanguage.json} - value={payload.tool_output} + value={payload.toolOutput} isJSONStringifyBeauty /> ) diff --git a/web/i18n/en-US/workflow.json b/web/i18n/en-US/workflow.json index 371487856d..198a1d0061 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/en-US/workflow.json @@ -651,6 +651,7 @@ "nodes.llm.jsonSchema.warningTips.saveSchema": "Please finish editing the current field before saving the schema", "nodes.llm.model": "model", "nodes.llm.notSetContextInPromptTip": "To enable the context feature, please fill in the context variable in PROMPT.", + "nodes.llm.outputVars.generation": "Generation Information", "nodes.llm.outputVars.output": "Generate content", "nodes.llm.outputVars.reasoning_content": "Reasoning Content", "nodes.llm.outputVars.usage": "Model Usage Information", diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/zh-Hans/workflow.json index 50a0801706..a133c3234d 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/zh-Hans/workflow.json @@ -651,6 +651,7 @@ "nodes.llm.jsonSchema.warningTips.saveSchema": "请先完成当前字段的编辑", "nodes.llm.model": "模型", "nodes.llm.notSetContextInPromptTip": "要启用上下文功能,请在提示中填写上下文变量。", + "nodes.llm.outputVars.generation": "生成信息", "nodes.llm.outputVars.output": "生成内容", "nodes.llm.outputVars.reasoning_content": "推理内容", "nodes.llm.outputVars.usage": "模型用量信息", diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 3241ff0732..8de0df840e 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -34,16 +34,27 @@ export type IconObject = { } export type ToolCallItem = { + id: string type: 'model' | 'tool' | 'thought' - tool_call_id?: string - tool_name?: string - tool_arguments?: string - tool_icon?: string | IconObject - tool_icon_dark?: string | IconObject - tool_files?: string[] - tool_error?: string - tool_output?: Record | string - tool_elapsed_time?: number + thoughtCompleted?: boolean + thoughtOutput?: string + + toolName?: string + toolProvider?: string + toolIcon?: string | IconObject + toolIconDark?: string | IconObject + toolArguments?: string + toolOutput?: Record | string + toolFiles?: string[] + toolError?: string + toolDuration?: number + + modelName?: string + modelProvider?: string + modelOutput?: Record | string + modelDuration?: number + modelIcon?: string | IconObject + modelIconDark?: string | IconObject } export type ToolCallDetail = {