### 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
| @@ -1,4 +1,5 @@ | |||
| import { AgentGlobals } from '@/constants/agent'; | |||
| import { ITraceData } from '@/interfaces/database/agent'; | |||
| import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow'; | |||
| import i18n from '@/locales/config'; | |||
| import { BeginId } from '@/pages/agent/constant'; | |||
| @@ -9,7 +10,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |||
| import { useDebounce } from 'ahooks'; | |||
| import { message } from 'antd'; | |||
| import { get, set } from 'lodash'; | |||
| import { useCallback } from 'react'; | |||
| import { useCallback, useState } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { useParams } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| @@ -27,6 +28,7 @@ export const enum AgentApiAction { | |||
| SetAgent = 'setAgent', | |||
| FetchAgentTemplates = 'fetchAgentTemplates', | |||
| UploadCanvasFile = 'uploadCanvasFile', | |||
| Trace = 'trace', | |||
| } | |||
| export const EmptyDsl = { | |||
| @@ -300,3 +302,32 @@ export const useUploadCanvasFile = () => { | |||
| 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 }; | |||
| }; | |||
| @@ -217,3 +217,8 @@ export interface IGraph { | |||
| nodes: RAGFlowNodeType[]; | |||
| edges: Edge[]; | |||
| } | |||
| export interface ITraceData { | |||
| component_id: string; | |||
| trace: Array<Record<string, any>>; | |||
| } | |||
| @@ -115,6 +115,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| setCurrentMessageId, | |||
| currentEventListWithoutMessage, | |||
| clearEventList, | |||
| currentMessageId, | |||
| } = useCacheChatLog(); | |||
| const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({ | |||
| @@ -221,6 +222,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) { | |||
| <LogSheet | |||
| hideModal={hideLogSheet} | |||
| currentEventListWithoutMessage={currentEventListWithoutMessage} | |||
| currentMessageId={currentMessageId} | |||
| ></LogSheet> | |||
| )} | |||
| </div> | |||
| @@ -57,5 +57,6 @@ export function useCacheChatLog() { | |||
| filterEventListByEventType, | |||
| filterEventListByMessageId, | |||
| setCurrentMessageId, | |||
| currentMessageId, | |||
| }; | |||
| } | |||
| @@ -18,23 +18,24 @@ import { | |||
| SheetHeader, | |||
| SheetTitle, | |||
| } 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 { ITraceData } from '@/interfaces/database/agent'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { isEmpty } from 'lodash'; | |||
| import { BellElectric, NotebookText } from 'lucide-react'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import { useCallback, useEffect, useMemo } from 'react'; | |||
| import JsonView from 'react18-json-view'; | |||
| import 'react18-json-view/src/style.css'; | |||
| import { useCacheChatLog } from '../hooks/use-cache-chat-log'; | |||
| import useGraphStore from '../store'; | |||
| type LogSheetProps = IModalProps<any> & | |||
| Pick<ReturnType<typeof useCacheChatLog>, 'currentEventListWithoutMessage'>; | |||
| Pick< | |||
| ReturnType<typeof useCacheChatLog>, | |||
| 'currentEventListWithoutMessage' | 'currentMessageId' | |||
| >; | |||
| function JsonViewer({ | |||
| data, | |||
| @@ -78,9 +79,16 @@ type EventWithIndex = { startNodeIdx: number } & ILogEvent; | |||
| export function LogSheet({ | |||
| hideModal, | |||
| currentEventListWithoutMessage, | |||
| currentMessageId, | |||
| }: LogSheetProps) { | |||
| const getNode = useGraphStore((state) => state.getNode); | |||
| const { data: traceData, setMessageId } = useFetchMessageTrace(); | |||
| useEffect(() => { | |||
| setMessageId(currentMessageId); | |||
| }, [currentMessageId, setMessageId]); | |||
| const getNodeName = useCallback( | |||
| (nodeId: string) => { | |||
| return getNode(nodeId)?.data.name; | |||
| @@ -88,6 +96,28 @@ export function LogSheet({ | |||
| [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 | |||
| const finishedNodeList = useMemo(() => { | |||
| return currentEventListWithoutMessage.filter( | |||
| @@ -109,19 +139,14 @@ export function LogSheet({ | |||
| const item = pre.find((x) => x.startNodeIdx === startNodeIdx); | |||
| const { logs = {}, inputs = {}, outputs = {} } = cur.data; | |||
| const { inputs = {}, outputs = {} } = cur.data; | |||
| if (item) { | |||
| const { | |||
| inputs: inputList, | |||
| outputs: outputList, | |||
| logs: logList, | |||
| } = item.data; | |||
| const { inputs: inputList, outputs: outputList } = item.data; | |||
| item.data = { | |||
| ...item.data, | |||
| inputs: concatData(inputList, inputs), | |||
| outputs: concatData(outputList, outputs), | |||
| logs: concatData(logList, logs), | |||
| }; | |||
| } else { | |||
| pre.push({ | |||
| @@ -195,10 +220,10 @@ export function LogSheet({ | |||
| title="Input" | |||
| ></JsonViewer> | |||
| {isEmpty((x.data as ILogData)?.logs) || ( | |||
| {hasTrace(x.data.component_id) && ( | |||
| <JsonViewer | |||
| data={(x.data as ILogData)?.logs} | |||
| title={'Logs'} | |||
| data={filterTrace(x.data.component_id) ?? {}} | |||
| title={'Trace'} | |||
| ></JsonViewer> | |||
| )} | |||
| @@ -19,6 +19,7 @@ const { | |||
| listCanvasTeam, | |||
| settingCanvas, | |||
| uploadCanvasFile, | |||
| trace, | |||
| } = api; | |||
| const methods = { | |||
| @@ -86,6 +87,10 @@ const methods = { | |||
| url: uploadCanvasFile, | |||
| method: 'post', | |||
| }, | |||
| trace: { | |||
| url: trace, | |||
| method: 'get', | |||
| }, | |||
| } as const; | |||
| const flowService = registerServer<keyof typeof methods>(methods, request); | |||
| @@ -144,6 +144,7 @@ export default { | |||
| getInputElements: `${api_host}/canvas/input_elements`, | |||
| debug: `${api_host}/canvas/debug`, | |||
| uploadCanvasFile: `${api_host}/canvas/upload`, | |||
| trace: `${api_host}/canvas/trace`, | |||
| // mcp server | |||
| getMcpServerList: `${api_host}/mcp_server/list`, | |||