From 381f9df9416499eac90430172220e48565ed006e Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Mon, 28 Jul 2025 16:48:46 +0800 Subject: [PATCH] Feat: Add agent log-sheet in cavas and log-sheet in share's page (#9072) ### What problem does this PR solve? Feat: Add agent log-sheet in cavas and log-sheet in share's page #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- .../components/next-message-item/index.tsx | 22 ++ web/src/hooks/use-agent-request.ts | 18 +- web/src/hooks/use-send-message.ts | 2 + web/src/pages/agent/canvas/index.tsx | 6 +- .../agent/chat/use-send-agent-message.ts | 17 +- .../pages/agent/hooks/use-cache-chat-log.ts | 53 +++- web/src/pages/agent/log-sheet/index.tsx | 232 +-------------- .../agent/log-sheet/toolTimelineItem.tsx | 118 ++++++++ .../agent/log-sheet/workFlowTimeline.tsx | 264 ++++++++++++++++++ .../hooks/use-send-shared-message.ts | 9 +- web/src/pages/next-chats/share/index.tsx | 30 +- web/src/services/agent-service.ts | 5 + 12 files changed, 520 insertions(+), 256 deletions(-) create mode 100644 web/src/pages/agent/log-sheet/toolTimelineItem.tsx create mode 100644 web/src/pages/agent/log-sheet/workFlowTimeline.tsx diff --git a/web/src/components/next-message-item/index.tsx b/web/src/components/next-message-item/index.tsx index 5aee701ec..c87539c6b 100644 --- a/web/src/components/next-message-item/index.tsx +++ b/web/src/components/next-message-item/index.tsx @@ -17,7 +17,9 @@ import { useFetchDocumentThumbnailsByIds, } from '@/hooks/document-hooks'; import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks'; +import { INodeEvent } from '@/hooks/use-send-message'; import { cn } from '@/lib/utils'; +import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workFlowTimeline'; import { IMessage } from '@/pages/chat/interface'; import { getExtension, isImage } from '@/utils/document-util'; import { Avatar, Button, Flex, List, Space, Typography } from 'antd'; @@ -38,6 +40,9 @@ interface IProps IRegenerateMessage, PropsWithChildren { item: IMessage; + conversationId?: string; + currentEventListWithoutMessageById?: (messageId: string) => INodeEvent[]; + setCurrentMessageId?: (messageId: string) => void; reference?: IReferenceObject; loading?: boolean; sendLoading?: boolean; @@ -54,6 +59,9 @@ interface IProps function MessageItem({ item, + conversationId, + currentEventListWithoutMessageById, + setCurrentMessageId, reference, loading = false, avatar, @@ -106,6 +114,11 @@ function MessageItem({ } }, [item.doc_ids, setDocumentIds, setIds, documentThumbnails]); + useEffect(() => { + if (typeof setCurrentMessageId === 'function') { + setCurrentMessageId(item.id); + } + }, [item.id, setCurrentMessageId]); return (
)} + {isAssistant && currentEventListWithoutMessageById && ( + + )} {isUser && documentList.length > 0 && ( { +export const useFetchMessageTrace = ( + isStopFetchTrace: boolean, + canvasId?: string, +) => { const { id } = useParams(); + const queryId = id || canvasId; const [messageId, setMessageId] = useState(''); const { @@ -399,16 +403,16 @@ export const useFetchMessageTrace = () => { isFetching: loading, refetch, } = useQuery({ - queryKey: [AgentApiAction.Trace, id, messageId], + queryKey: [AgentApiAction.Trace, queryId, messageId], refetchOnReconnect: false, refetchOnMount: false, refetchOnWindowFocus: false, gcTime: 0, - enabled: !!id && !!messageId, - refetchInterval: 3000, + enabled: !!queryId && !!messageId, + refetchInterval: !isStopFetchTrace ? 3000 : false, queryFn: async () => { - const { data } = await agentService.trace({ - canvas_id: id, + const { data } = await fetchTrace({ + canvas_id: queryId as string, message_id: messageId, }); diff --git a/web/src/hooks/use-send-message.ts b/web/src/hooks/use-send-message.ts index 8839be25b..3361a149e 100644 --- a/web/src/hooks/use-send-message.ts +++ b/web/src/hooks/use-send-message.ts @@ -29,6 +29,8 @@ export interface INodeData { inputs: Record; outputs: Record; component_id: string; + component_name: string; + component_type: string; error: null | string; elapsed_time: number; created_at: number; diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx index e44e593af..e19a90782 100644 --- a/web/src/pages/agent/canvas/index.tsx +++ b/web/src/pages/agent/canvas/index.tsx @@ -124,7 +124,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { const { addEventList, setCurrentMessageId, - currentEventListWithoutMessage, + currentEventListWithoutMessageById, clearEventList, currentMessageId, } = useCacheChatLog(); @@ -258,7 +258,9 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { {logSheetVisible && ( )} diff --git a/web/src/pages/agent/chat/use-send-agent-message.ts b/web/src/pages/agent/chat/use-send-agent-message.ts index e274db9b0..4edaee92b 100644 --- a/web/src/pages/agent/chat/use-send-agent-message.ts +++ b/web/src/pages/agent/chat/use-send-agent-message.ts @@ -169,13 +169,20 @@ export function useSetUploadResponseData() { }; } -export const useSendAgentMessage = (url?: string) => { +export const useSendAgentMessage = ( + url?: string, + addEventList?: (data: IEventList, messageId: string) => void, +) => { const { id: agentId } = useParams(); const { handleInputChange, value, setValue } = useHandleMessageInputChange(); const inputs = useSelectBeginNodeDataInputs(); const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE( url || api.runCanvas, ); + const messageId = useMemo(() => { + return answerList[0]?.message_id; + }, [answerList]); + const { findReferenceByMessageId } = useFindMessageReference(answerList); const prologue = useGetBeginNodePrologue(); const { @@ -186,7 +193,7 @@ export const useSendAgentMessage = (url?: string) => { addNewestOneQuestion, addNewestOneAnswer, } = useSelectDerivedMessages(); - const { addEventList } = useContext(AgentChatLogContext); + const { addEventList: addEventListFun } = useContext(AgentChatLogContext); const { appendUploadResponseList, clearUploadResponseList, @@ -287,9 +294,11 @@ export const useSendAgentMessage = (url?: string) => { useEffect(() => { if (typeof addEventList === 'function') { - addEventList(answerList); + addEventList(answerList, messageId); + } else if (typeof addEventListFun === 'function') { + addEventListFun(answerList, messageId); } - }, [addEventList, answerList]); + }, [addEventList, answerList, addEventListFun, messageId]); return { value, diff --git a/web/src/pages/agent/hooks/use-cache-chat-log.ts b/web/src/pages/agent/hooks/use-cache-chat-log.ts index 29f5b1a94..293ea6688 100644 --- a/web/src/pages/agent/hooks/use-cache-chat-log.ts +++ b/web/src/pages/agent/hooks/use-cache-chat-log.ts @@ -3,7 +3,7 @@ import { INodeEvent, MessageEventType, } from '@/hooks/use-send-message'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; export const ExcludeTypes = [ MessageEventType.Message, @@ -12,51 +12,76 @@ export const ExcludeTypes = [ export function useCacheChatLog() { const [eventList, setEventList] = useState([]); + const [messageIdPool, setMessageIdPool] = useState< + Record + >({}); + const [currentMessageId, setCurrentMessageId] = useState(''); + useEffect(() => { + setMessageIdPool((prev) => ({ ...prev, [currentMessageId]: eventList })); + }, [currentMessageId, eventList]); const filterEventListByMessageId = useCallback( (messageId: string) => { - return eventList.filter((x) => x.message_id === messageId); + return messageIdPool[messageId]?.filter( + (x) => x.message_id === messageId, + ); }, - [eventList], + [messageIdPool], ); const filterEventListByEventType = useCallback( (eventType: string) => { - return eventList.filter((x) => x.event === eventType); + return messageIdPool[currentMessageId]?.filter( + (x) => x.event === eventType, + ); }, - [eventList], + [messageIdPool, currentMessageId], ); const clearEventList = useCallback(() => { setEventList([]); }, []); - const addEventList = useCallback((events: IEventList) => { - setEventList((list) => { - const nextList = [...list]; + const addEventList = useCallback( + (events: IEventList, message_id: string) => { + const nextList = [...eventList]; events.forEach((x) => { if (nextList.every((y) => y !== x)) { nextList.push(x); } }); - return nextList; - }); - }, []); + setEventList(nextList); + setMessageIdPool((prev) => ({ ...prev, [message_id]: nextList })); + }, + [eventList], + ); const currentEventListWithoutMessage = useMemo(() => { - const list = eventList.filter( + const list = messageIdPool[currentMessageId]?.filter( (x) => x.message_id === currentMessageId && ExcludeTypes.every((y) => y !== x.event), ); - return list as INodeEvent[]; - }, [currentMessageId, eventList]); + }, [currentMessageId, messageIdPool]); + + const currentEventListWithoutMessageById = useCallback( + (messageId: string) => { + const list = messageIdPool[messageId]?.filter( + (x) => + x.message_id === messageId && + ExcludeTypes.every((y) => y !== x.event), + ); + return list as INodeEvent[]; + }, + [messageIdPool], + ); return { eventList, currentEventListWithoutMessage, + currentEventListWithoutMessageById, setEventList, clearEventList, addEventList, diff --git a/web/src/pages/agent/log-sheet/index.tsx b/web/src/pages/agent/log-sheet/index.tsx index 21ba73b40..90e50c410 100644 --- a/web/src/pages/agent/log-sheet/index.tsx +++ b/web/src/pages/agent/log-sheet/index.tsx @@ -1,156 +1,26 @@ -import { - Timeline, - TimelineContent, - TimelineHeader, - TimelineIndicator, - TimelineItem, - TimelineSeparator, -} from '@/components/originui/timeline'; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from '@/components/ui/accordion'; import { Sheet, SheetContent, SheetHeader, SheetTitle, } from '@/components/ui/sheet'; -import { useFetchMessageTrace } from '@/hooks/use-agent-request'; -import { - INodeData, - INodeEvent, - MessageEventType, -} from '@/hooks/use-send-message'; import { IModalProps } from '@/interfaces/common'; -import { ITraceData } from '@/interfaces/database/agent'; -import { cn } from '@/lib/utils'; -import { t } from 'i18next'; -import { get } from 'lodash'; import { NotebookText } from 'lucide-react'; -import { useCallback, useEffect, useMemo } from 'react'; -import JsonView from 'react18-json-view'; import 'react18-json-view/src/style.css'; -import { Operator } from '../constant'; import { useCacheChatLog } from '../hooks/use-cache-chat-log'; -import OperatorIcon from '../operator-icon'; -import useGraphStore from '../store'; +import { WorkFlowTimeline } from './workFlowTimeline'; type LogSheetProps = IModalProps & Pick< ReturnType, - 'currentEventListWithoutMessage' | 'currentMessageId' + 'currentEventListWithoutMessageById' | 'currentMessageId' >; -function JsonViewer({ - data, - title, -}: { - data: Record; - title: string; -}) { - return ( -
-
{title}
- -
- ); -} - -function getInputsOrOutputs( - nodeEventList: INodeData[], - field: 'inputs' | 'outputs', -) { - const inputsOrOutputs = nodeEventList.map((x) => get(x, field, {})); - - if (inputsOrOutputs.length < 2) { - return inputsOrOutputs[0] || {}; - } - - return inputsOrOutputs; -} - export function LogSheet({ hideModal, - currentEventListWithoutMessage, + currentEventListWithoutMessageById, currentMessageId, }: LogSheetProps) { - const getNode = useGraphStore((state) => state.getNode); - - const { data: traceData, setMessageId } = useFetchMessageTrace(); - - useEffect(() => { - setMessageId(currentMessageId); - }, [currentMessageId, setMessageId]); - - const getNodeName = useCallback( - (nodeId: string) => { - if ('begin' === nodeId) return t('flow.begin'); - return getNode(nodeId)?.data.name; - }, - [getNode], - ); - - const startedNodeList = useMemo(() => { - const duplicateList = currentEventListWithoutMessage.filter( - (x) => x.event === MessageEventType.NodeStarted, - ) as INodeEvent[]; - - // Remove duplicate nodes - return duplicateList.reduce>((pre, cur) => { - if (pre.every((x) => x.data.component_id !== cur.data.component_id)) { - pre.push(cur); - } - return pre; - }, []); - }, [currentEventListWithoutMessage]); - - const hasTrace = useCallback( - (componentId: string) => { - if (Array.isArray(traceData)) { - return traceData?.some((x) => x.component_id === componentId); - } - return false; - }, - [traceData], - ); - - const filterTrace = useCallback( - (componentId: string) => { - const trace = traceData - ?.filter((x) => x.component_id === componentId) - .reduce((pre, cur) => { - pre.push(...cur.trace); - - return pre; - }, []); - return Array.isArray(trace) ? trace : {}; - }, - [traceData], - ); - - const filterFinishedNodeList = useCallback( - (componentId: string) => { - const nodeEventList = currentEventListWithoutMessage - .filter( - (x) => - x.event === MessageEventType.NodeFinished && - (x.data as INodeData)?.component_id === componentId, - ) - .map((x) => x.data); - - return nodeEventList; - }, - [currentEventListWithoutMessage], - ); - return ( @@ -161,96 +31,12 @@ export function LogSheet({
- - {startedNodeList.map((x, idx) => { - const nodeDataList = filterFinishedNodeList(x.data.component_id); - const finishNodeIds = nodeDataList.map( - (x: INodeData) => x.component_id, - ); - const inputs = getInputsOrOutputs(nodeDataList, 'inputs'); - const outputs = getInputsOrOutputs(nodeDataList, 'outputs'); - const nodeLabel = getNode(x.data.component_id)?.data.label; - return ( - - - - - -
-
-
-
-
- -
-
-
-
- -
- - - -
- {getNodeName(x.data?.component_id)} - - {x.data.elapsed_time?.toString().slice(0, 6)} - - - Online - -
-
- -
- - - {hasTrace(x.data.component_id) && ( - - )} - - -
-
-
-
-
-
-
- ); - })} -
+
diff --git a/web/src/pages/agent/log-sheet/toolTimelineItem.tsx b/web/src/pages/agent/log-sheet/toolTimelineItem.tsx new file mode 100644 index 000000000..4cdcbd8f3 --- /dev/null +++ b/web/src/pages/agent/log-sheet/toolTimelineItem.tsx @@ -0,0 +1,118 @@ +import { + TimelineContent, + TimelineHeader, + TimelineIndicator, + TimelineItem, + TimelineSeparator, +} from '@/components/originui/timeline'; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from '@/components/ui/accordion'; +import { cn } from '@/lib/utils'; +import { Operator } from '../constant'; +import OperatorIcon from '../operator-icon'; +import { JsonViewer } from './workFlowTimeline'; + +const ToolTimelineItem = ({ tools }: { tools: Record[] }) => { + if (!tools || tools.length === 0 || !Array.isArray(tools)) return null; + const blackList = ['analyze_task', 'add_memory', 'gen_citations']; + const filteredTools = tools.filter( + (tool) => !blackList.includes(tool.tool_name), + ); + return ( + <> + {filteredTools?.map((tool, idx) => { + return ( + + + + + = filteredTools.length - 1 && tool.result === '...' + ), + }, + )} + > +
+
+
= filteredTools.length - 1 && + tool.result === '...', + })} + >
+
+
+ +
+
+
+
+ +
+ + + +
+ {tool.tool_name} + + {/* 0:00 + {x.data.elapsed_time?.toString().slice(0, 6)} */} + + + Online + +
+
+ +
+ +
+
+
+
+
+
+
+ ); + })} + + ); +}; + +export default ToolTimelineItem; diff --git a/web/src/pages/agent/log-sheet/workFlowTimeline.tsx b/web/src/pages/agent/log-sheet/workFlowTimeline.tsx new file mode 100644 index 000000000..bf1d34475 --- /dev/null +++ b/web/src/pages/agent/log-sheet/workFlowTimeline.tsx @@ -0,0 +1,264 @@ +import { + Timeline, + TimelineContent, + TimelineHeader, + TimelineIndicator, + TimelineItem, + TimelineSeparator, +} from '@/components/originui/timeline'; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from '@/components/ui/accordion'; +import { useFetchMessageTrace } from '@/hooks/use-agent-request'; +import { + INodeData, + INodeEvent, + MessageEventType, +} from '@/hooks/use-send-message'; +import { ITraceData } from '@/interfaces/database/agent'; +import { cn } from '@/lib/utils'; +import { get } from 'lodash'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import JsonView from 'react18-json-view'; +import { Operator } from '../constant'; +import { useCacheChatLog } from '../hooks/use-cache-chat-log'; +import OperatorIcon from '../operator-icon'; +import ToolTimelineItem from './toolTimelineItem'; +type LogFlowTimelineProps = Pick< + ReturnType, + 'currentEventListWithoutMessage' | 'currentMessageId' +> & { canvasId?: string }; +export function JsonViewer({ + data, + title, +}: { + data: Record; + title: string; +}) { + return ( +
+
{title}
+ +
+ ); +} + +function getInputsOrOutputs( + nodeEventList: INodeData[], + field: 'inputs' | 'outputs', +) { + const inputsOrOutputs = nodeEventList.map((x) => get(x, field, {})); + + if (inputsOrOutputs.length < 2) { + return inputsOrOutputs[0] || {}; + } + + return inputsOrOutputs; +} +export const WorkFlowTimeline = ({ + currentEventListWithoutMessage, + currentMessageId, + canvasId, +}: LogFlowTimelineProps) => { + // const getNode = useGraphStore((state) => state.getNode); + const [isStopFetchTrace, setISStopFetchTrace] = useState(false); + + const { data: traceData, setMessageId } = useFetchMessageTrace( + isStopFetchTrace, + canvasId, + ); + + useEffect(() => { + setMessageId(currentMessageId); + }, [currentMessageId, setMessageId]); + // const getNodeName = useCallback( + // (nodeId: string) => { + // if ('begin' === nodeId) return t('flow.begin'); + // return getNode(nodeId)?.data.name; + // }, + // [getNode], + // ); + // const getNodeById = useCallback( + // (nodeId: string) => { + // const data = currentEventListWithoutMessage + // .map((x) => x.data) + // .filter((x) => x.component_id === nodeId); + // if ('begin' === nodeId) return t('flow.begin'); + // if (data && data.length) { + // return data[0]; + // } + // return {}; + // }, + // [currentEventListWithoutMessage], + // ); + const startedNodeList = useMemo(() => { + const finish = currentEventListWithoutMessage?.some( + (item) => item.event === MessageEventType.WorkflowFinished, + ); + setISStopFetchTrace(finish); + const duplicateList = currentEventListWithoutMessage?.filter( + (x) => x.event === MessageEventType.NodeStarted, + ) as INodeEvent[]; + + // Remove duplicate nodes + return duplicateList?.reduce>((pre, cur) => { + if (pre.every((x) => x.data.component_id !== cur.data.component_id)) { + pre.push(cur); + } + return pre; + }, []); + }, [currentEventListWithoutMessage]); + + const hasTrace = useCallback( + (componentId: string) => { + if (Array.isArray(traceData)) { + return traceData?.some((x) => x.component_id === componentId); + } + return false; + }, + [traceData], + ); + + const filterTrace = useCallback( + (componentId: string) => { + const trace = traceData + ?.filter((x) => x.component_id === componentId) + .reduce((pre, cur) => { + pre.push(...cur.trace); + + return pre; + }, []); + return Array.isArray(trace) ? trace : [{}]; + }, + [traceData], + ); + + const filterFinishedNodeList = useCallback( + (componentId: string) => { + const nodeEventList = currentEventListWithoutMessage + .filter( + (x) => + x.event === MessageEventType.NodeFinished && + (x.data as INodeData)?.component_id === componentId, + ) + .map((x) => x.data); + + return nodeEventList; + }, + [currentEventListWithoutMessage], + ); + return ( + + {startedNodeList?.map((x, idx) => { + const nodeDataList = filterFinishedNodeList(x.data.component_id); + const finishNodeIds = nodeDataList.map( + (x: INodeData) => x.component_id, + ); + const inputs = getInputsOrOutputs(nodeDataList, 'inputs'); + const outputs = getInputsOrOutputs(nodeDataList, 'outputs'); + const nodeLabel = x.data.component_type; + return ( + <> + + + + + +
+
+
+
+
+ +
+
+
+
+ +
+ + + +
+ {x.data?.component_name} + + {x.data.elapsed_time?.toString().slice(0, 6)} + + + Online + +
+
+ +
+ + + +
+
+
+
+
+
+
+ {hasTrace(x.data.component_id) && ( + + )} + + ); + })} +
+ ); +}; diff --git a/web/src/pages/next-chats/hooks/use-send-shared-message.ts b/web/src/pages/next-chats/hooks/use-send-shared-message.ts index e80b9d9fb..bdbb472b9 100644 --- a/web/src/pages/next-chats/hooks/use-send-shared-message.ts +++ b/web/src/pages/next-chats/hooks/use-send-shared-message.ts @@ -1,4 +1,5 @@ import { SharedFrom } from '@/constants/chat'; +import { IEventList } from '@/hooks/use-send-message'; import { useSendAgentMessage } from '@/pages/agent/chat/use-send-agent-message'; import trim from 'lodash/trim'; import { useSearchParams } from 'umi'; @@ -27,14 +28,16 @@ export const useGetSharedChatSearchParams = () => { }; }; -export function useSendNextSharedMessage() { +export const useSendNextSharedMessage = ( + addEventList: (data: IEventList, messageId: string) => void, +) => { const { from, sharedId: conversationId } = useGetSharedChatSearchParams(); const url = `/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`; - const ret = useSendAgentMessage(url); + const ret = useSendAgentMessage(url, addEventList); return { ...ret, hasError: false, }; -} +}; diff --git a/web/src/pages/next-chats/share/index.tsx b/web/src/pages/next-chats/share/index.tsx index 97a545daa..d76be2e5a 100644 --- a/web/src/pages/next-chats/share/index.tsx +++ b/web/src/pages/next-chats/share/index.tsx @@ -11,6 +11,7 @@ import { } from '@/hooks/use-agent-request'; import { cn } from '@/lib/utils'; import i18n from '@/locales/config'; +import { useCacheChatLog } from '@/pages/agent/hooks/use-cache-chat-log'; import { useSendButtonDisabled } from '@/pages/chat/hooks'; import { buildMessageUuidWithRole } from '@/utils/chat'; import React, { forwardRef, useCallback, useMemo } from 'react'; @@ -31,7 +32,13 @@ const ChatContainer = () => { const { uploadCanvasFile, loading } = useUploadCanvasFileWithProgress(conversationId); - + const { + addEventList, + setCurrentMessageId, + currentEventListWithoutMessageById, + clearEventList, + currentMessageId, + } = useCacheChatLog(); const { handlePressEnter, handleInputChange, @@ -43,9 +50,22 @@ const ChatContainer = () => { stopOutputMessage, findReferenceByMessageId, appendUploadResponseList, - } = useSendNextSharedMessage(); + } = useSendNextSharedMessage(addEventList); + const sendDisabled = useSendButtonDisabled(value); + // useEffect(() => { + // if (derivedMessages.length) { + // const derivedMessagesFilter = derivedMessages.filter( + // (message) => message.role === MessageType.Assistant, + // ); + // if (derivedMessagesFilter.length) { + // const message = derivedMessagesFilter[derivedMessagesFilter.length - 1]; + // setCurrentMessageId(message.id); + // } + // } + // }, [derivedMessages, setCurrentMessageId]); + const useFetchAvatar = useMemo(() => { return from === SharedFrom.Agent ? useFetchAgentAvatar @@ -81,6 +101,11 @@ const ChatContainer = () => { return ( {
- (methods); +export const fetchTrace = (data: { canvas_id: string; message_id: string }) => { + return request.get(methods.trace.url, { params: data }); +}; + export default agentService;