### What problem does this PR solve? Feat: Get the running log of each message through the trace interface #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| import { AgentGlobals } from '@/constants/agent'; | import { AgentGlobals } from '@/constants/agent'; | ||||
| import { ITraceData } from '@/interfaces/database/agent'; | |||||
| import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow'; | import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow'; | ||||
| import i18n from '@/locales/config'; | import i18n from '@/locales/config'; | ||||
| import { BeginId } from '@/pages/agent/constant'; | import { BeginId } from '@/pages/agent/constant'; | ||||
| import { useDebounce } from 'ahooks'; | import { useDebounce } from 'ahooks'; | ||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { get, set } from 'lodash'; | import { get, set } from 'lodash'; | ||||
| import { useCallback } from 'react'; | |||||
| import { useCallback, useState } from 'react'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { useParams } from 'umi'; | import { useParams } from 'umi'; | ||||
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||
| SetAgent = 'setAgent', | SetAgent = 'setAgent', | ||||
| FetchAgentTemplates = 'fetchAgentTemplates', | FetchAgentTemplates = 'fetchAgentTemplates', | ||||
| UploadCanvasFile = 'uploadCanvasFile', | UploadCanvasFile = 'uploadCanvasFile', | ||||
| Trace = 'trace', | |||||
| } | } | ||||
| export const EmptyDsl = { | export const EmptyDsl = { | ||||
| return { data, loading, uploadCanvasFile: mutateAsync }; | return { data, loading, uploadCanvasFile: mutateAsync }; | ||||
| }; | }; | ||||
| export const useFetchMessageTrace = () => { | |||||
| const { id } = useParams(); | |||||
| const [messageId, setMessageId] = useState(''); | |||||
| const { | |||||
| data, | |||||
| isFetching: loading, | |||||
| refetch, | |||||
| } = useQuery<ITraceData[]>({ | |||||
| queryKey: [AgentApiAction.Trace, id, messageId], | |||||
| refetchOnReconnect: false, | |||||
| refetchOnMount: false, | |||||
| refetchOnWindowFocus: false, | |||||
| gcTime: 0, | |||||
| enabled: !!id && !!messageId, | |||||
| refetchInterval: 2000, | |||||
| queryFn: async () => { | |||||
| const { data } = await flowService.trace({ | |||||
| canvas_id: id, | |||||
| message_id: messageId, | |||||
| }); | |||||
| return data?.data ?? []; | |||||
| }, | |||||
| }); | |||||
| return { data, loading, refetch, setMessageId }; | |||||
| }; |
| nodes: RAGFlowNodeType[]; | nodes: RAGFlowNodeType[]; | ||||
| edges: Edge[]; | edges: Edge[]; | ||||
| } | } | ||||
| export interface ITraceData { | |||||
| component_id: string; | |||||
| trace: Array<Record<string, any>>; | |||||
| } |
| setCurrentMessageId, | setCurrentMessageId, | ||||
| currentEventListWithoutMessage, | currentEventListWithoutMessage, | ||||
| clearEventList, | clearEventList, | ||||
| currentMessageId, | |||||
| } = useCacheChatLog(); | } = useCacheChatLog(); | ||||
| const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({ | const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({ | ||||
| <LogSheet | <LogSheet | ||||
| hideModal={hideLogSheet} | hideModal={hideLogSheet} | ||||
| currentEventListWithoutMessage={currentEventListWithoutMessage} | currentEventListWithoutMessage={currentEventListWithoutMessage} | ||||
| currentMessageId={currentMessageId} | |||||
| ></LogSheet> | ></LogSheet> | ||||
| )} | )} | ||||
| </div> | </div> |
| filterEventListByEventType, | filterEventListByEventType, | ||||
| filterEventListByMessageId, | filterEventListByMessageId, | ||||
| setCurrentMessageId, | setCurrentMessageId, | ||||
| currentMessageId, | |||||
| }; | }; | ||||
| } | } |
| SheetHeader, | SheetHeader, | ||||
| SheetTitle, | SheetTitle, | ||||
| } from '@/components/ui/sheet'; | } from '@/components/ui/sheet'; | ||||
| import { | |||||
| ILogData, | |||||
| ILogEvent, | |||||
| MessageEventType, | |||||
| } from '@/hooks/use-send-message'; | |||||
| import { useFetchMessageTrace } from '@/hooks/use-agent-request'; | |||||
| import { ILogEvent, MessageEventType } from '@/hooks/use-send-message'; | |||||
| import { IModalProps } from '@/interfaces/common'; | import { IModalProps } from '@/interfaces/common'; | ||||
| import { ITraceData } from '@/interfaces/database/agent'; | |||||
| import { cn } from '@/lib/utils'; | import { cn } from '@/lib/utils'; | ||||
| import { isEmpty } from 'lodash'; | import { isEmpty } from 'lodash'; | ||||
| import { BellElectric, NotebookText } from 'lucide-react'; | import { BellElectric, NotebookText } from 'lucide-react'; | ||||
| import { useCallback, useMemo } from 'react'; | |||||
| import { useCallback, useEffect, useMemo } from 'react'; | |||||
| import JsonView from 'react18-json-view'; | import JsonView from 'react18-json-view'; | ||||
| import 'react18-json-view/src/style.css'; | import 'react18-json-view/src/style.css'; | ||||
| import { useCacheChatLog } from '../hooks/use-cache-chat-log'; | import { useCacheChatLog } from '../hooks/use-cache-chat-log'; | ||||
| import useGraphStore from '../store'; | import useGraphStore from '../store'; | ||||
| type LogSheetProps = IModalProps<any> & | type LogSheetProps = IModalProps<any> & | ||||
| Pick<ReturnType<typeof useCacheChatLog>, 'currentEventListWithoutMessage'>; | |||||
| Pick< | |||||
| ReturnType<typeof useCacheChatLog>, | |||||
| 'currentEventListWithoutMessage' | 'currentMessageId' | |||||
| >; | |||||
| function JsonViewer({ | function JsonViewer({ | ||||
| data, | data, | ||||
| export function LogSheet({ | export function LogSheet({ | ||||
| hideModal, | hideModal, | ||||
| currentEventListWithoutMessage, | currentEventListWithoutMessage, | ||||
| currentMessageId, | |||||
| }: LogSheetProps) { | }: LogSheetProps) { | ||||
| const getNode = useGraphStore((state) => state.getNode); | const getNode = useGraphStore((state) => state.getNode); | ||||
| const { data: traceData, setMessageId } = useFetchMessageTrace(); | |||||
| useEffect(() => { | |||||
| setMessageId(currentMessageId); | |||||
| }, [currentMessageId, setMessageId]); | |||||
| const getNodeName = useCallback( | const getNodeName = useCallback( | ||||
| (nodeId: string) => { | (nodeId: string) => { | ||||
| return getNode(nodeId)?.data.name; | return getNode(nodeId)?.data.name; | ||||
| [getNode], | [getNode], | ||||
| ); | ); | ||||
| const hasTrace = useCallback( | |||||
| (componentId: string) => { | |||||
| if (Array.isArray(traceData)) { | |||||
| return traceData?.some((x) => x.component_id === componentId); | |||||
| } | |||||
| }, | |||||
| [traceData], | |||||
| ); | |||||
| const filterTrace = useCallback( | |||||
| (componentId: string) => { | |||||
| return traceData | |||||
| ?.filter((x) => x.component_id === componentId) | |||||
| .reduce<ITraceData['trace']>((pre, cur) => { | |||||
| pre.push(...cur.trace); | |||||
| return pre; | |||||
| }, []); | |||||
| }, | |||||
| [traceData], | |||||
| ); | |||||
| // Look up to find the nearest start component id and concatenate the finish and log data into one | // Look up to find the nearest start component id and concatenate the finish and log data into one | ||||
| const finishedNodeList = useMemo(() => { | const finishedNodeList = useMemo(() => { | ||||
| return currentEventListWithoutMessage.filter( | return currentEventListWithoutMessage.filter( | ||||
| const item = pre.find((x) => x.startNodeIdx === startNodeIdx); | const item = pre.find((x) => x.startNodeIdx === startNodeIdx); | ||||
| const { logs = {}, inputs = {}, outputs = {} } = cur.data; | |||||
| const { inputs = {}, outputs = {} } = cur.data; | |||||
| if (item) { | if (item) { | ||||
| const { | |||||
| inputs: inputList, | |||||
| outputs: outputList, | |||||
| logs: logList, | |||||
| } = item.data; | |||||
| const { inputs: inputList, outputs: outputList } = item.data; | |||||
| item.data = { | item.data = { | ||||
| ...item.data, | ...item.data, | ||||
| inputs: concatData(inputList, inputs), | inputs: concatData(inputList, inputs), | ||||
| outputs: concatData(outputList, outputs), | outputs: concatData(outputList, outputs), | ||||
| logs: concatData(logList, logs), | |||||
| }; | }; | ||||
| } else { | } else { | ||||
| pre.push({ | pre.push({ | ||||
| title="Input" | title="Input" | ||||
| ></JsonViewer> | ></JsonViewer> | ||||
| {isEmpty((x.data as ILogData)?.logs) || ( | |||||
| {hasTrace(x.data.component_id) && ( | |||||
| <JsonViewer | <JsonViewer | ||||
| data={(x.data as ILogData)?.logs} | |||||
| title={'Logs'} | |||||
| data={filterTrace(x.data.component_id) ?? {}} | |||||
| title={'Trace'} | |||||
| ></JsonViewer> | ></JsonViewer> | ||||
| )} | )} | ||||
| listCanvasTeam, | listCanvasTeam, | ||||
| settingCanvas, | settingCanvas, | ||||
| uploadCanvasFile, | uploadCanvasFile, | ||||
| trace, | |||||
| } = api; | } = api; | ||||
| const methods = { | const methods = { | ||||
| url: uploadCanvasFile, | url: uploadCanvasFile, | ||||
| method: 'post', | method: 'post', | ||||
| }, | }, | ||||
| trace: { | |||||
| url: trace, | |||||
| method: 'get', | |||||
| }, | |||||
| } as const; | } as const; | ||||
| const flowService = registerServer<keyof typeof methods>(methods, request); | const flowService = registerServer<keyof typeof methods>(methods, request); |
| getInputElements: `${api_host}/canvas/input_elements`, | getInputElements: `${api_host}/canvas/input_elements`, | ||||
| debug: `${api_host}/canvas/debug`, | debug: `${api_host}/canvas/debug`, | ||||
| uploadCanvasFile: `${api_host}/canvas/upload`, | uploadCanvasFile: `${api_host}/canvas/upload`, | ||||
| trace: `${api_host}/canvas/trace`, | |||||
| // mcp server | // mcp server | ||||
| getMcpServerList: `${api_host}/mcp_server/list`, | getMcpServerList: `${api_host}/mcp_server/list`, |