From 0e66b51ca05304d0411bbe292c6bb86a2cfe4851 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 20 Jan 2026 17:36:57 +0800 Subject: [PATCH] fix: history messages toolcalls --- .../base/chat/chat-with-history/hooks.tsx | 5 +- web/app/components/base/chat/chat/type.ts | 38 +++++++++++- web/app/components/base/chat/utils.ts | 62 ++++++++++++++++++- .../workflow/panel/chat-record/index.tsx | 18 +++--- web/eslint-suppressions.json | 2 +- 5 files changed, 113 insertions(+), 12 deletions(-) diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index ed1981b530..17b42fbbe7 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -42,7 +42,7 @@ import { import { TransferMethod } from '@/types/app' import { addFileInfos, sortAgentSorts } from '../../../tools/utils' import { CONVERSATION_ID_INFO } from '../constants' -import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams, getRawInputsFromUrlParams, getRawUserVariablesFromUrlParams } from '../utils' +import { buildChatItemTree, buildToolCallsFromHistorySequence, getProcessedSystemVariablesFromUrlParams, getRawInputsFromUrlParams, getRawUserVariablesFromUrlParams } from '../utils' function getFormattedChatList(messages: any[]) { const newChatList: ChatItem[] = [] @@ -58,7 +58,8 @@ function getFormattedChatList(messages: any[]) { const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [] newChatList.push({ id: item.id, - content: item.answer, + content: buildToolCallsFromHistorySequence(item).message, + toolCalls: buildToolCallsFromHistorySequence(item).toolCalls, agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files), feedback: item.feedback, isAnswer: true, diff --git a/web/app/components/base/chat/chat/type.ts b/web/app/components/base/chat/chat/type.ts index 5f2740dce2..2e07e287a0 100644 --- a/web/app/components/base/chat/chat/type.ts +++ b/web/app/components/base/chat/chat/type.ts @@ -2,7 +2,7 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types' import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { InputVarType } from '@/app/components/workflow/types' import type { Annotation, MessageRating } from '@/models/log' -import type { FileResponse, ToolCallItem } from '@/types/workflow' +import type { FileResponse, IconObject, ToolCallItem } from '@/types/workflow' export type MessageMore = { time: string @@ -148,3 +148,39 @@ export type InputForm = { hide: boolean [key: string]: any } + +export type ToolCallDetail = { + id: string + name: string + arguments: string + result: string + elapsed_time?: number + icon?: string | IconObject + icon_dark?: string | IconObject +} +export type SequenceSegment = | { type: 'content', start: number, end: number } + | { type: 'reasoning', index: number } + | { type: 'tool_call', index: number } + +export type GenerationDetail = { + reasoning_content?: string[] + tool_calls?: ToolCallDetail[] + sequence?: SequenceSegment[] +} + +export type ChatMessageRes = { + id: string + parent_message_id?: string | null + workflow_run_id?: string + answer?: string + query: string + message_files: { + id: string + type: string + url: string + belongs_to: string + }[] + feedback?: FeedbackType + metadata?: Metadata + generation_detail?: GenerationDetail +} diff --git a/web/app/components/base/chat/utils.ts b/web/app/components/base/chat/utils.ts index b47fec1d0a..958190e7b2 100644 --- a/web/app/components/base/chat/utils.ts +++ b/web/app/components/base/chat/utils.ts @@ -1,5 +1,7 @@ -import type { IChatItem } from './chat/type' +import type { ChatMessageRes, IChatItem } from './chat/type' import type { ChatItem, ChatItemInTree } from './types' +import type { ToolCallItem } from '@/types/workflow' +import { v4 as uuidV4 } from 'uuid' import { UUID_NIL } from './constants' async function decodeBase64AndDecompress(base64String: string) { @@ -232,8 +234,66 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch return ret } +const buildToolCallsFromHistorySequence = (message: ChatMessageRes): { + toolCalls: ToolCallItem[] + message: string +} => { + const { answer, generation_detail } = message + + if (!generation_detail) { + return { toolCalls: [], message: answer || '' } + } + + const { reasoning_content = [], tool_calls = [], sequence = [] } = generation_detail + const toolCalls: ToolCallItem[] = [] + let answerMessage = '' + + sequence.forEach((segment) => { + switch (segment.type) { + case 'content': { + const text = answer?.substring(segment.start, segment.end) + if (text?.trim()) { + answerMessage += text + } + break + } + case 'reasoning': { + const reasoning = reasoning_content[segment.index] + if (reasoning) { + toolCalls.push({ + id: uuidV4(), + type: 'thought', + thoughtOutput: reasoning, + thoughtCompleted: true, + }) + } + break + } + case 'tool_call': { + const toolCall = tool_calls[segment.index] + if (toolCall) { + toolCalls.push({ + id: uuidV4(), + type: 'tool', + toolName: toolCall.name, + toolArguments: toolCall.arguments, + toolOutput: toolCall.result, + toolDuration: toolCall.elapsed_time, + toolIcon: toolCall.icon, + toolIconDark: toolCall.icon_dark, + }) + } + break + } + } + }) + + return { toolCalls, message: answerMessage || '' } +} + export { buildChatItemTree, + buildToolCallsFromHistorySequence, getLastAnswer, getProcessedInputsFromUrlParams, getProcessedSystemVariablesFromUrlParams, diff --git a/web/app/components/workflow/panel/chat-record/index.tsx b/web/app/components/workflow/panel/chat-record/index.tsx index 6afe463c30..f02350c314 100644 --- a/web/app/components/workflow/panel/chat-record/index.tsx +++ b/web/app/components/workflow/panel/chat-record/index.tsx @@ -1,4 +1,7 @@ -import type { IChatItem } from '@/app/components/base/chat/chat/type' +import type { + ChatMessageRes, + IChatItem, +} from '@/app/components/base/chat/chat/type' import type { ChatItem, ChatItemInTree } from '@/app/components/base/chat/types' import { RiCloseLine } from '@remixicon/react' import { @@ -9,7 +12,7 @@ import { } from 'react' import { useStore as useAppStore } from '@/app/components/app/store' import Chat from '@/app/components/base/chat/chat' -import { buildChatItemTree, getThreadMessages } from '@/app/components/base/chat/utils' +import { buildChatItemTree, buildToolCallsFromHistorySequence, getThreadMessages } from '@/app/components/base/chat/utils' import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils' import Loading from '@/app/components/base/loading' import { fetchConversationMessages } from '@/service/debug' @@ -21,10 +24,10 @@ import { import { formatWorkflowRunIdentifier } from '../../utils' import UserInput from './user-input' -function getFormattedChatList(messages: any[]) { +function getFormattedChatList(messages: ChatMessageRes[]) { const res: ChatItem[] = [] - messages.forEach((item: any) => { - const questionFiles = item.message_files?.filter((file: any) => file.belongs_to === 'user') || [] + messages.forEach((item: ChatMessageRes) => { + const questionFiles = item.message_files?.filter(file => file.belongs_to === 'user') || [] res.push({ id: `question-${item.id}`, content: item.query, @@ -35,7 +38,8 @@ function getFormattedChatList(messages: any[]) { const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [] res.push({ id: item.id, - content: item.answer, + content: buildToolCallsFromHistorySequence(item).message, + toolCalls: buildToolCallsFromHistorySequence(item).toolCalls, feedback: item.feedback, isAnswer: true, citation: item.metadata?.retriever_resources, @@ -63,7 +67,7 @@ const ChatRecord = () => { setFetched(false) const res = await fetchConversationMessages(appDetail.id, currentConversationID) - const newAllChatItems = getFormattedChatList((res as any).data) + const newAllChatItems = getFormattedChatList((res as any).data as ChatMessageRes[]) const tree = buildChatItemTree(newAllChatItems) setChatItemTree(tree) diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 4103daae8f..65648030c8 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -3801,7 +3801,7 @@ }, "app/components/workflow/panel/chat-record/index.tsx": { "ts/no-explicit-any": { - "count": 8 + "count": 5 } }, "app/components/workflow/panel/chat-record/user-input.tsx": {