Feat: Display agent operator call log #3221 (#8169)

### What problem does this PR solve?

Feat: Display agent operator call log #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-06-11 09:22:07 +08:00
committed by GitHub
parent e6d36f3a3a
commit f0a3d91171
23 changed files with 1513 additions and 124 deletions

View File

@ -6,7 +6,11 @@ import {
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { ChatSheet } from '../chat/chat-sheet';
import { AgentInstanceContext } from '../context';
import {
AgentChatContext,
AgentChatLogContext,
AgentInstanceContext,
} from '../context';
import FormSheet from '../form-sheet/next';
import {
useHandleDrop,
@ -16,6 +20,7 @@ import {
} from '../hooks';
import { useAddNode } from '../hooks/use-add-node';
import { useBeforeDelete } from '../hooks/use-before-delete';
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
import { useShowDrawer, useShowLogSheet } from '../hooks/use-show-drawer';
import { LogSheet } from '../log-sheet';
import RunSheet from '../run-sheet';
@ -101,7 +106,12 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
hideDrawer,
});
const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet();
const { addEventList, setCurrentMessageId, currentEventListWithoutMessage } =
useCacheChatLog();
const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({
setCurrentMessageId,
});
const { handleBeforeDelete } = useBeforeDelete();
@ -176,10 +186,13 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
</AgentInstanceContext.Provider>
)}
{chatVisible && (
<ChatSheet
visible={chatVisible}
hideModal={hideRunOrChatDrawer}
></ChatSheet>
<AgentChatContext.Provider value={{ showLogSheet }}>
<AgentChatLogContext.Provider
value={{ addEventList, setCurrentMessageId }}
>
<ChatSheet hideModal={hideRunOrChatDrawer}></ChatSheet>
</AgentChatLogContext.Provider>
</AgentChatContext.Provider>
)}
{runVisible && (
<RunSheet
@ -188,7 +201,10 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
></RunSheet>
)}
{logSheetVisible && (
<LogSheet hideModal={hideLogSheet} showModal={showLogSheet}></LogSheet>
<LogSheet
hideModal={hideLogSheet}
currentEventListWithoutMessage={currentEventListWithoutMessage}
></LogSheet>
)}
</div>
);

View File

@ -1,4 +1,3 @@
import MessageItem from '@/components/message-item';
import { MessageType } from '@/constants/chat';
import { useGetFileIcon } from '@/pages/chat/hooks';
import { buildMessageItemReference } from '@/pages/chat/utils';
@ -7,6 +6,7 @@ import { Spin } from 'antd';
import { useSendNextMessage } from './hooks';
import MessageInput from '@/components/message-input';
import MessageItem from '@/components/next-message-item';
import PdfDrawer from '@/components/pdf-drawer';
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
import { useFetchAgent } from '@/hooks/use-agent-request';

View File

@ -8,9 +8,16 @@ import { IModalProps } from '@/interfaces/common';
import { cn } from '@/lib/utils';
import AgentChatBox from './box';
export function ChatSheet({ visible, hideModal }: IModalProps<any>) {
export function ChatSheet({ hideModal }: IModalProps<any>) {
return (
<Sheet open={visible} modal={false} onOpenChange={hideModal}>
<Sheet
open
modal={false}
onOpenChange={(open) => {
console.log('🚀 ~ ChatSheet ~ open:', open);
hideModal();
}}
>
<SheetTitle className="hidden"></SheetTitle>
<SheetContent className={cn('top-20 p-0')}>
<SheetHeader>

View File

@ -16,10 +16,11 @@ import api from '@/utils/api';
import { message } from 'antd';
import { get } from 'lodash';
import trim from 'lodash/trim';
import { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useContext, useEffect, useMemo } from 'react';
import { useParams } from 'umi';
import { v4 as uuid } from 'uuid';
import { BeginId } from '../constant';
import { AgentChatLogContext } from '../context';
import useGraphStore from '../store';
import { receiveMessageError } from '../utils';
@ -86,6 +87,7 @@ export const useSendNextMessage = () => {
const { id: agentId } = useParams();
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { refetch } = useFetchAgent();
const { addEventList } = useContext(AgentChatLogContext);
const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
api.runCanvas,
@ -160,6 +162,10 @@ export const useSendNextMessage = () => {
}
}, [addNewestAnswer, prologue]);
useEffect(() => {
addEventList(answerList);
}, [addEventList, answerList]);
return {
handlePressEnter,
handleInputChange,

View File

@ -1,6 +1,8 @@
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { createContext } from 'react';
import { useAddNode } from './hooks/use-add-node';
import { useCacheChatLog } from './hooks/use-cache-chat-log';
import { useShowLogSheet } from './hooks/use-show-drawer';
export const AgentFormContext = createContext<RAGFlowNodeType | undefined>(
undefined,
@ -14,3 +16,21 @@ type AgentInstanceContextType = Pick<
export const AgentInstanceContext = createContext<AgentInstanceContextType>(
{} as AgentInstanceContextType,
);
type AgentChatContextType = Pick<
ReturnType<typeof useShowLogSheet>,
'showLogSheet'
>;
export const AgentChatContext = createContext<AgentChatContextType>(
{} as AgentChatContextType,
);
type AgentChatLogContextType = Pick<
ReturnType<typeof useCacheChatLog>,
'addEventList' | 'setCurrentMessageId'
>;
export const AgentChatLogContext = createContext<AgentChatLogContextType>(
{} as AgentChatLogContextType,
);

View File

@ -0,0 +1,61 @@
import { IEventList, MessageEventType } from '@/hooks/use-send-message';
import { useCallback, useMemo, useState } from 'react';
export const ExcludeTypes = [
MessageEventType.Message,
MessageEventType.MessageEnd,
];
export function useCacheChatLog() {
const [eventList, setEventList] = useState<IEventList>([]);
const [currentMessageId, setCurrentMessageId] = useState('');
const filterEventListByMessageId = useCallback(
(messageId: string) => {
return eventList.filter((x) => x.message_id === messageId);
},
[eventList],
);
const filterEventListByEventType = useCallback(
(eventType: string) => {
return eventList.filter((x) => x.event === eventType);
},
[eventList],
);
const clearEventList = useCallback(() => {
setEventList([]);
}, []);
const addEventList = useCallback((events: IEventList) => {
setEventList((list) => {
const nextList = [...list];
events.forEach((x) => {
if (nextList.every((y) => y !== x)) {
nextList.push(x);
}
});
return nextList;
});
}, []);
const currentEventListWithoutMessage = useMemo(() => {
return eventList.filter(
(x) =>
x.message_id === currentMessageId &&
ExcludeTypes.every((y) => y !== x.event),
);
}, [currentMessageId, eventList]);
return {
eventList,
currentEventListWithoutMessage,
setEventList,
clearEventList,
addEventList,
filterEventListByEventType,
filterEventListByMessageId,
setCurrentMessageId,
};
}

View File

@ -5,6 +5,7 @@ import { useCallback, useEffect } from 'react';
import { Operator } from '../constant';
import { BeginQuery } from '../interface';
import useGraphStore from '../store';
import { useCacheChatLog } from './use-cache-chat-log';
import { useGetBeginNodeDataQuery } from './use-get-begin-query';
import { useSaveGraph } from './use-save-graph';
@ -152,12 +153,22 @@ export function useShowDrawer({
};
}
export function useShowLogSheet() {
export function useShowLogSheet({
setCurrentMessageId,
}: Pick<ReturnType<typeof useCacheChatLog>, 'setCurrentMessageId'>) {
const { visible, showModal, hideModal } = useSetModalState();
const handleShow = useCallback(
(messageId: string) => {
setCurrentMessageId(messageId);
showModal();
},
[setCurrentMessageId, showModal],
);
return {
logSheetVisible: visible,
hideLogSheet: hideModal,
showLogSheet: showModal,
showLogSheet: handleShow,
};
}

View File

@ -1,23 +1,116 @@
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion';
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet';
import { INodeEvent, MessageEventType } from '@/hooks/use-send-message';
import { IModalProps } from '@/interfaces/common';
import { cn } from '@/lib/utils';
import { NotebookText } from 'lucide-react';
import { useCallback, 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';
export function LogSheet({ hideModal }: IModalProps<any>) {
type LogSheetProps = IModalProps<any> &
Pick<ReturnType<typeof useCacheChatLog>, 'currentEventListWithoutMessage'>;
function JsonViewer({
data,
title,
}: {
data: Record<string, any>;
title: string;
}) {
return (
<Sheet open onOpenChange={hideModal}>
<SheetContent>
<section className="space-y-2">
<div>{title}</div>
<JsonView
src={data}
displaySize
collapseStringsAfterLength={100000000000}
className="w-full h-[200px] break-words overflow-auto p-2 bg-slate-800"
/>
</section>
);
}
export function LogSheet({
hideModal,
currentEventListWithoutMessage,
}: LogSheetProps) {
const getNode = useGraphStore((state) => state.getNode);
const getNodeName = useCallback(
(nodeId: string) => {
return getNode(nodeId)?.data.name;
},
[getNode],
);
const finishedNodeList = useMemo(() => {
return currentEventListWithoutMessage.filter(
(x) => x.event === MessageEventType.NodeFinished,
) as INodeEvent[];
}, [currentEventListWithoutMessage]);
return (
<Sheet open onOpenChange={hideModal} modal={false}>
<SheetContent className="top-20 right-96">
<SheetHeader>
<SheetTitle>Are you absolutely sure?</SheetTitle>
<SheetDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</SheetDescription>
<SheetTitle className="flex items-center gap-1">
<NotebookText className="size-4" />
Log
</SheetTitle>
</SheetHeader>
<section className="max-h-[82vh] overflow-auto">
{finishedNodeList.map((x, idx) => (
<section key={idx}>
<Accordion type="single" collapsible>
<AccordionItem value={idx.toString()}>
<AccordionTrigger>
<div className="flex gap-2 items-center">
<span>{getNodeName(x.data?.component_id)}</span>
<span className="text-text-sub-title text-xs">
{x.data.elapsed_time?.toString().slice(0, 6)}
</span>
<span
className={cn(
'border-background -end-1 -top-1 size-2 rounded-full border-2 bg-dot-green',
{ 'text-dot-green': x.data.error === null },
{ 'text-dot-red': x.data.error !== null },
)}
>
<span className="sr-only">Online</span>
</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="space-y-2">
<JsonViewer
data={x.data.inputs}
title="Input"
></JsonViewer>
<JsonViewer
data={x.data.outputs}
title="Output"
></JsonViewer>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</section>
))}
</section>
</SheetContent>
</Sheet>
);

View File

@ -1,56 +0,0 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { Form, useForm } from 'react-hook-form';
import { z } from 'zod';
import { Button } from '@/components/ui/button';
import { useCallback, useState } from 'react';
import DynamicCategorize from './agent/form/categorize-form/dynamic-categorize';
const formSchema = z.object({
items: z
.array(
z
.object({
name: z.string().min(1, 'xxx').trim(),
description: z.string().optional(),
// examples: z
// .array(
// z.object({
// value: z.string(),
// }),
// )
// .optional(),
})
.optional(),
)
.optional(),
});
export function Demo() {
const [flag, setFlag] = useState(false);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
items: [],
},
});
const handleReset = useCallback(() => {
form?.reset();
}, [form]);
const handleSwitch = useCallback(() => {
setFlag(true);
}, []);
return (
<div>
<Form {...form}>
<DynamicCategorize></DynamicCategorize>
</Form>
<Button onClick={handleReset}>reset</Button>
<Button onClick={handleSwitch}>switch</Button>
</div>
);
}