mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
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)
This commit is contained in:
@ -17,7 +17,9 @@ import {
|
|||||||
useFetchDocumentThumbnailsByIds,
|
useFetchDocumentThumbnailsByIds,
|
||||||
} from '@/hooks/document-hooks';
|
} from '@/hooks/document-hooks';
|
||||||
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||||
|
import { INodeEvent } from '@/hooks/use-send-message';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workFlowTimeline';
|
||||||
import { IMessage } from '@/pages/chat/interface';
|
import { IMessage } from '@/pages/chat/interface';
|
||||||
import { getExtension, isImage } from '@/utils/document-util';
|
import { getExtension, isImage } from '@/utils/document-util';
|
||||||
import { Avatar, Button, Flex, List, Space, Typography } from 'antd';
|
import { Avatar, Button, Flex, List, Space, Typography } from 'antd';
|
||||||
@ -38,6 +40,9 @@ interface IProps
|
|||||||
IRegenerateMessage,
|
IRegenerateMessage,
|
||||||
PropsWithChildren {
|
PropsWithChildren {
|
||||||
item: IMessage;
|
item: IMessage;
|
||||||
|
conversationId?: string;
|
||||||
|
currentEventListWithoutMessageById?: (messageId: string) => INodeEvent[];
|
||||||
|
setCurrentMessageId?: (messageId: string) => void;
|
||||||
reference?: IReferenceObject;
|
reference?: IReferenceObject;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
sendLoading?: boolean;
|
sendLoading?: boolean;
|
||||||
@ -54,6 +59,9 @@ interface IProps
|
|||||||
|
|
||||||
function MessageItem({
|
function MessageItem({
|
||||||
item,
|
item,
|
||||||
|
conversationId,
|
||||||
|
currentEventListWithoutMessageById,
|
||||||
|
setCurrentMessageId,
|
||||||
reference,
|
reference,
|
||||||
loading = false,
|
loading = false,
|
||||||
avatar,
|
avatar,
|
||||||
@ -106,6 +114,11 @@ function MessageItem({
|
|||||||
}
|
}
|
||||||
}, [item.doc_ids, setDocumentIds, setIds, documentThumbnails]);
|
}, [item.doc_ids, setDocumentIds, setIds, documentThumbnails]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof setCurrentMessageId === 'function') {
|
||||||
|
setCurrentMessageId(item.id);
|
||||||
|
}
|
||||||
|
}, [item.id, setCurrentMessageId]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(styles.messageItem, {
|
className={classNames(styles.messageItem, {
|
||||||
@ -186,6 +199,15 @@ function MessageItem({
|
|||||||
list={referenceDocuments}
|
list={referenceDocuments}
|
||||||
></ReferenceDocumentList>
|
></ReferenceDocumentList>
|
||||||
)}
|
)}
|
||||||
|
{isAssistant && currentEventListWithoutMessageById && (
|
||||||
|
<WorkFlowTimeline
|
||||||
|
currentEventListWithoutMessage={currentEventListWithoutMessageById(
|
||||||
|
item.id,
|
||||||
|
)}
|
||||||
|
currentMessageId={item.id}
|
||||||
|
canvasId={conversationId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isUser && documentList.length > 0 && (
|
{isUser && documentList.length > 0 && (
|
||||||
<List
|
<List
|
||||||
bordered
|
bordered
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { IDebugSingleRequestBody } from '@/interfaces/request/agent';
|
|||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
import { BeginId } from '@/pages/agent/constant';
|
import { BeginId } from '@/pages/agent/constant';
|
||||||
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks';
|
||||||
import agentService from '@/services/agent-service';
|
import agentService, { fetchTrace } from '@/services/agent-service';
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { buildMessageListWithUuid } from '@/utils/chat';
|
import { buildMessageListWithUuid } from '@/utils/chat';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
@ -390,8 +390,12 @@ export const useUploadCanvasFileWithProgress = (
|
|||||||
return { data, loading, uploadCanvasFile: mutateAsync };
|
return { data, loading, uploadCanvasFile: mutateAsync };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFetchMessageTrace = () => {
|
export const useFetchMessageTrace = (
|
||||||
|
isStopFetchTrace: boolean,
|
||||||
|
canvasId?: string,
|
||||||
|
) => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const queryId = id || canvasId;
|
||||||
const [messageId, setMessageId] = useState('');
|
const [messageId, setMessageId] = useState('');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -399,16 +403,16 @@ export const useFetchMessageTrace = () => {
|
|||||||
isFetching: loading,
|
isFetching: loading,
|
||||||
refetch,
|
refetch,
|
||||||
} = useQuery<ITraceData[]>({
|
} = useQuery<ITraceData[]>({
|
||||||
queryKey: [AgentApiAction.Trace, id, messageId],
|
queryKey: [AgentApiAction.Trace, queryId, messageId],
|
||||||
refetchOnReconnect: false,
|
refetchOnReconnect: false,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
gcTime: 0,
|
gcTime: 0,
|
||||||
enabled: !!id && !!messageId,
|
enabled: !!queryId && !!messageId,
|
||||||
refetchInterval: 3000,
|
refetchInterval: !isStopFetchTrace ? 3000 : false,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await agentService.trace({
|
const { data } = await fetchTrace({
|
||||||
canvas_id: id,
|
canvas_id: queryId as string,
|
||||||
message_id: messageId,
|
message_id: messageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,8 @@ export interface INodeData {
|
|||||||
inputs: Record<string, any>;
|
inputs: Record<string, any>;
|
||||||
outputs: Record<string, any>;
|
outputs: Record<string, any>;
|
||||||
component_id: string;
|
component_id: string;
|
||||||
|
component_name: string;
|
||||||
|
component_type: string;
|
||||||
error: null | string;
|
error: null | string;
|
||||||
elapsed_time: number;
|
elapsed_time: number;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
|||||||
@ -124,7 +124,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
const {
|
const {
|
||||||
addEventList,
|
addEventList,
|
||||||
setCurrentMessageId,
|
setCurrentMessageId,
|
||||||
currentEventListWithoutMessage,
|
currentEventListWithoutMessageById,
|
||||||
clearEventList,
|
clearEventList,
|
||||||
currentMessageId,
|
currentMessageId,
|
||||||
} = useCacheChatLog();
|
} = useCacheChatLog();
|
||||||
@ -258,7 +258,9 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
{logSheetVisible && (
|
{logSheetVisible && (
|
||||||
<LogSheet
|
<LogSheet
|
||||||
hideModal={hideLogSheet}
|
hideModal={hideLogSheet}
|
||||||
currentEventListWithoutMessage={currentEventListWithoutMessage}
|
currentEventListWithoutMessageById={
|
||||||
|
currentEventListWithoutMessageById
|
||||||
|
}
|
||||||
currentMessageId={currentMessageId}
|
currentMessageId={currentMessageId}
|
||||||
></LogSheet>
|
></LogSheet>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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 { id: agentId } = useParams();
|
||||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||||
const inputs = useSelectBeginNodeDataInputs();
|
const inputs = useSelectBeginNodeDataInputs();
|
||||||
const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
|
const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
|
||||||
url || api.runCanvas,
|
url || api.runCanvas,
|
||||||
);
|
);
|
||||||
|
const messageId = useMemo(() => {
|
||||||
|
return answerList[0]?.message_id;
|
||||||
|
}, [answerList]);
|
||||||
|
|
||||||
const { findReferenceByMessageId } = useFindMessageReference(answerList);
|
const { findReferenceByMessageId } = useFindMessageReference(answerList);
|
||||||
const prologue = useGetBeginNodePrologue();
|
const prologue = useGetBeginNodePrologue();
|
||||||
const {
|
const {
|
||||||
@ -186,7 +193,7 @@ export const useSendAgentMessage = (url?: string) => {
|
|||||||
addNewestOneQuestion,
|
addNewestOneQuestion,
|
||||||
addNewestOneAnswer,
|
addNewestOneAnswer,
|
||||||
} = useSelectDerivedMessages();
|
} = useSelectDerivedMessages();
|
||||||
const { addEventList } = useContext(AgentChatLogContext);
|
const { addEventList: addEventListFun } = useContext(AgentChatLogContext);
|
||||||
const {
|
const {
|
||||||
appendUploadResponseList,
|
appendUploadResponseList,
|
||||||
clearUploadResponseList,
|
clearUploadResponseList,
|
||||||
@ -287,9 +294,11 @@ export const useSendAgentMessage = (url?: string) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof addEventList === 'function') {
|
if (typeof addEventList === 'function') {
|
||||||
addEventList(answerList);
|
addEventList(answerList, messageId);
|
||||||
|
} else if (typeof addEventListFun === 'function') {
|
||||||
|
addEventListFun(answerList, messageId);
|
||||||
}
|
}
|
||||||
}, [addEventList, answerList]);
|
}, [addEventList, answerList, addEventListFun, messageId]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {
|
|||||||
INodeEvent,
|
INodeEvent,
|
||||||
MessageEventType,
|
MessageEventType,
|
||||||
} from '@/hooks/use-send-message';
|
} from '@/hooks/use-send-message';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
export const ExcludeTypes = [
|
export const ExcludeTypes = [
|
||||||
MessageEventType.Message,
|
MessageEventType.Message,
|
||||||
@ -12,51 +12,76 @@ export const ExcludeTypes = [
|
|||||||
|
|
||||||
export function useCacheChatLog() {
|
export function useCacheChatLog() {
|
||||||
const [eventList, setEventList] = useState<IEventList>([]);
|
const [eventList, setEventList] = useState<IEventList>([]);
|
||||||
|
const [messageIdPool, setMessageIdPool] = useState<
|
||||||
|
Record<string, IEventList>
|
||||||
|
>({});
|
||||||
|
|
||||||
const [currentMessageId, setCurrentMessageId] = useState('');
|
const [currentMessageId, setCurrentMessageId] = useState('');
|
||||||
|
useEffect(() => {
|
||||||
|
setMessageIdPool((prev) => ({ ...prev, [currentMessageId]: eventList }));
|
||||||
|
}, [currentMessageId, eventList]);
|
||||||
|
|
||||||
const filterEventListByMessageId = useCallback(
|
const filterEventListByMessageId = useCallback(
|
||||||
(messageId: string) => {
|
(messageId: string) => {
|
||||||
return eventList.filter((x) => x.message_id === messageId);
|
return messageIdPool[messageId]?.filter(
|
||||||
|
(x) => x.message_id === messageId,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[eventList],
|
[messageIdPool],
|
||||||
);
|
);
|
||||||
|
|
||||||
const filterEventListByEventType = useCallback(
|
const filterEventListByEventType = useCallback(
|
||||||
(eventType: string) => {
|
(eventType: string) => {
|
||||||
return eventList.filter((x) => x.event === eventType);
|
return messageIdPool[currentMessageId]?.filter(
|
||||||
|
(x) => x.event === eventType,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[eventList],
|
[messageIdPool, currentMessageId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const clearEventList = useCallback(() => {
|
const clearEventList = useCallback(() => {
|
||||||
setEventList([]);
|
setEventList([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const addEventList = useCallback((events: IEventList) => {
|
const addEventList = useCallback(
|
||||||
setEventList((list) => {
|
(events: IEventList, message_id: string) => {
|
||||||
const nextList = [...list];
|
const nextList = [...eventList];
|
||||||
events.forEach((x) => {
|
events.forEach((x) => {
|
||||||
if (nextList.every((y) => y !== x)) {
|
if (nextList.every((y) => y !== x)) {
|
||||||
nextList.push(x);
|
nextList.push(x);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return nextList;
|
setEventList(nextList);
|
||||||
});
|
setMessageIdPool((prev) => ({ ...prev, [message_id]: nextList }));
|
||||||
}, []);
|
},
|
||||||
|
[eventList],
|
||||||
|
);
|
||||||
|
|
||||||
const currentEventListWithoutMessage = useMemo(() => {
|
const currentEventListWithoutMessage = useMemo(() => {
|
||||||
const list = eventList.filter(
|
const list = messageIdPool[currentMessageId]?.filter(
|
||||||
(x) =>
|
(x) =>
|
||||||
x.message_id === currentMessageId &&
|
x.message_id === currentMessageId &&
|
||||||
ExcludeTypes.every((y) => y !== x.event),
|
ExcludeTypes.every((y) => y !== x.event),
|
||||||
);
|
);
|
||||||
|
|
||||||
return list as INodeEvent[];
|
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 {
|
return {
|
||||||
eventList,
|
eventList,
|
||||||
currentEventListWithoutMessage,
|
currentEventListWithoutMessage,
|
||||||
|
currentEventListWithoutMessageById,
|
||||||
setEventList,
|
setEventList,
|
||||||
clearEventList,
|
clearEventList,
|
||||||
addEventList,
|
addEventList,
|
||||||
|
|||||||
@ -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 {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from '@/components/ui/sheet';
|
} 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 { 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 { NotebookText } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
|
||||||
import JsonView from 'react18-json-view';
|
|
||||||
import 'react18-json-view/src/style.css';
|
import 'react18-json-view/src/style.css';
|
||||||
import { Operator } from '../constant';
|
|
||||||
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
||||||
import OperatorIcon from '../operator-icon';
|
import { WorkFlowTimeline } from './workFlowTimeline';
|
||||||
import useGraphStore from '../store';
|
|
||||||
|
|
||||||
type LogSheetProps = IModalProps<any> &
|
type LogSheetProps = IModalProps<any> &
|
||||||
Pick<
|
Pick<
|
||||||
ReturnType<typeof useCacheChatLog>,
|
ReturnType<typeof useCacheChatLog>,
|
||||||
'currentEventListWithoutMessage' | 'currentMessageId'
|
'currentEventListWithoutMessageById' | 'currentMessageId'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
function JsonViewer({
|
|
||||||
data,
|
|
||||||
title,
|
|
||||||
}: {
|
|
||||||
data: Record<string, any>;
|
|
||||||
title: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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({
|
export function LogSheet({
|
||||||
hideModal,
|
hideModal,
|
||||||
currentEventListWithoutMessage,
|
currentEventListWithoutMessageById,
|
||||||
currentMessageId,
|
currentMessageId,
|
||||||
}: LogSheetProps) {
|
}: 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<Array<INodeEvent>>((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<ITraceData['trace']>((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 (
|
return (
|
||||||
<Sheet open onOpenChange={hideModal} modal={false}>
|
<Sheet open onOpenChange={hideModal} modal={false}>
|
||||||
<SheetContent className="top-20 right-[620px]">
|
<SheetContent className="top-20 right-[620px]">
|
||||||
@ -161,96 +31,12 @@ export function LogSheet({
|
|||||||
</SheetTitle>
|
</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
<section className="max-h-[82vh] overflow-auto mt-6">
|
<section className="max-h-[82vh] overflow-auto mt-6">
|
||||||
<Timeline>
|
<WorkFlowTimeline
|
||||||
{startedNodeList.map((x, idx) => {
|
currentEventListWithoutMessage={currentEventListWithoutMessageById(
|
||||||
const nodeDataList = filterFinishedNodeList(x.data.component_id);
|
currentMessageId,
|
||||||
const finishNodeIds = nodeDataList.map(
|
)}
|
||||||
(x: INodeData) => x.component_id,
|
currentMessageId={currentMessageId}
|
||||||
);
|
/>
|
||||||
const inputs = getInputsOrOutputs(nodeDataList, 'inputs');
|
|
||||||
const outputs = getInputsOrOutputs(nodeDataList, 'outputs');
|
|
||||||
const nodeLabel = getNode(x.data.component_id)?.data.label;
|
|
||||||
return (
|
|
||||||
<TimelineItem
|
|
||||||
key={idx}
|
|
||||||
step={idx}
|
|
||||||
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8"
|
|
||||||
>
|
|
||||||
<TimelineHeader>
|
|
||||||
<TimelineSeparator className="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-6.5 top-6 bg-background-checked" />
|
|
||||||
|
|
||||||
<TimelineIndicator className="bg-primary/10 group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 items-center justify-center border-none group-data-[orientation=vertical]/timeline:-left-7">
|
|
||||||
<div className='relative after:content-[""] after:absolute after:inset-0 after:z-10 after:bg-transparent after:transition-all after:duration-300'>
|
|
||||||
<div className="absolute inset-0 z-10 flex items-center justify-center ">
|
|
||||||
<div
|
|
||||||
className={cn('rounded-full w-6 h-6', {
|
|
||||||
' border-muted-foreground border-2 border-t-transparent animate-spin ':
|
|
||||||
!finishNodeIds.includes(x.data.component_id),
|
|
||||||
})}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<div className="size-6 flex items-center justify-center">
|
|
||||||
<OperatorIcon
|
|
||||||
className="size-5"
|
|
||||||
name={nodeLabel as Operator}
|
|
||||||
></OperatorIcon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TimelineIndicator>
|
|
||||||
</TimelineHeader>
|
|
||||||
<TimelineContent className="text-foreground rounded-lg border mb-5">
|
|
||||||
<section key={idx}>
|
|
||||||
<Accordion
|
|
||||||
type="single"
|
|
||||||
collapsible
|
|
||||||
className="bg-background-card px-3"
|
|
||||||
>
|
|
||||||
<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={inputs}
|
|
||||||
title="Input"
|
|
||||||
></JsonViewer>
|
|
||||||
|
|
||||||
{hasTrace(x.data.component_id) && (
|
|
||||||
<JsonViewer
|
|
||||||
data={filterTrace(x.data.component_id)}
|
|
||||||
title={'Trace'}
|
|
||||||
></JsonViewer>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<JsonViewer
|
|
||||||
data={outputs}
|
|
||||||
title={'Output'}
|
|
||||||
></JsonViewer>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</section>
|
|
||||||
</TimelineContent>
|
|
||||||
</TimelineItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Timeline>
|
|
||||||
</section>
|
</section>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
|
|||||||
118
web/src/pages/agent/log-sheet/toolTimelineItem.tsx
Normal file
118
web/src/pages/agent/log-sheet/toolTimelineItem.tsx
Normal file
@ -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<string, any>[] }) => {
|
||||||
|
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 (
|
||||||
|
<TimelineItem
|
||||||
|
key={idx}
|
||||||
|
step={idx}
|
||||||
|
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8"
|
||||||
|
>
|
||||||
|
<TimelineHeader>
|
||||||
|
<TimelineSeparator
|
||||||
|
className="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-6.5 top-6"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
idx < filteredTools.length - 1
|
||||||
|
? 'repeating-linear-gradient( to bottom, rgba(76, 164, 231, 1), rgba(76, 164, 231, 1) 5px, transparent 5px, transparent 10px'
|
||||||
|
: 'rgba(76, 164, 231, 1)',
|
||||||
|
width: '1px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TimelineIndicator
|
||||||
|
className={cn(
|
||||||
|
'group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 p-1 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7',
|
||||||
|
{
|
||||||
|
'border border-blue-500': !(
|
||||||
|
idx >= filteredTools.length - 1 && tool.result === '...'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='relative after:content-[""] after:absolute after:inset-0 after:z-10 after:bg-transparent after:transition-all after:duration-300'>
|
||||||
|
<div className="absolute inset-0 z-10 flex items-center justify-center ">
|
||||||
|
<div
|
||||||
|
className={cn('rounded-full w-6 h-6', {
|
||||||
|
' border-muted-foreground border-2 border-t-transparent animate-spin ':
|
||||||
|
idx >= filteredTools.length - 1 &&
|
||||||
|
tool.result === '...',
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div className="size-6 flex items-center justify-center">
|
||||||
|
<OperatorIcon
|
||||||
|
className="size-4"
|
||||||
|
name={'Agent' as Operator}
|
||||||
|
></OperatorIcon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TimelineIndicator>
|
||||||
|
</TimelineHeader>
|
||||||
|
<TimelineContent className="text-foreground rounded-lg border mb-5">
|
||||||
|
<section key={idx}>
|
||||||
|
<Accordion
|
||||||
|
type="single"
|
||||||
|
collapsible
|
||||||
|
className="bg-background-card px-3"
|
||||||
|
>
|
||||||
|
<AccordionItem value={idx.toString()}>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<span>{tool.tool_name}</span>
|
||||||
|
<span className="text-text-sub-title text-xs">
|
||||||
|
{/* 0:00
|
||||||
|
{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',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Online</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<JsonViewer
|
||||||
|
data={tool.result}
|
||||||
|
title="content"
|
||||||
|
></JsonViewer>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</section>
|
||||||
|
</TimelineContent>
|
||||||
|
</TimelineItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToolTimelineItem;
|
||||||
264
web/src/pages/agent/log-sheet/workFlowTimeline.tsx
Normal file
264
web/src/pages/agent/log-sheet/workFlowTimeline.tsx
Normal file
@ -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<typeof useCacheChatLog>,
|
||||||
|
'currentEventListWithoutMessage' | 'currentMessageId'
|
||||||
|
> & { canvasId?: string };
|
||||||
|
export function JsonViewer({
|
||||||
|
data,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
data: Record<string, any>;
|
||||||
|
title: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Array<INodeEvent>>((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<ITraceData['trace']>((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 (
|
||||||
|
<Timeline>
|
||||||
|
{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 (
|
||||||
|
<>
|
||||||
|
<TimelineItem
|
||||||
|
key={idx}
|
||||||
|
step={idx}
|
||||||
|
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8"
|
||||||
|
>
|
||||||
|
<TimelineHeader>
|
||||||
|
<TimelineSeparator
|
||||||
|
className="group-data-[orientation=vertical]/timeline:-left-7 group-data-[orientation=vertical]/timeline:h-[calc(100%-1.5rem-0.25rem)] group-data-[orientation=vertical]/timeline:translate-y-6.5 top-6 bg-background-checked"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
x.data.component_type === 'Agent'
|
||||||
|
? 'repeating-linear-gradient( to bottom, rgba(76, 164, 231, 1), rgba(76, 164, 231, 1) 5px, transparent 5px, transparent 10px'
|
||||||
|
: '',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TimelineIndicator
|
||||||
|
className={cn(
|
||||||
|
' group-data-completed/timeline-item:bg-primary group-data-completed/timeline-item:text-primary-foreground flex size-6 p-1 items-center justify-center group-data-[orientation=vertical]/timeline:-left-7',
|
||||||
|
{
|
||||||
|
'border border-blue-500': finishNodeIds.includes(
|
||||||
|
x.data.component_id,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className='relative after:content-[""] after:absolute after:inset-0 after:z-10 after:bg-transparent after:transition-all after:duration-300'>
|
||||||
|
<div className="absolute inset-0 z-10 flex items-center justify-center ">
|
||||||
|
<div
|
||||||
|
className={cn('rounded-full w-6 h-6', {
|
||||||
|
' border-muted-foreground border-2 border-t-transparent animate-spin ':
|
||||||
|
!finishNodeIds.includes(x.data.component_id),
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div className="size-6 flex items-center justify-center">
|
||||||
|
<OperatorIcon
|
||||||
|
className="size-4"
|
||||||
|
name={nodeLabel as Operator}
|
||||||
|
></OperatorIcon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TimelineIndicator>
|
||||||
|
</TimelineHeader>
|
||||||
|
<TimelineContent className="text-foreground rounded-lg border mb-5">
|
||||||
|
<section key={idx}>
|
||||||
|
<Accordion
|
||||||
|
type="single"
|
||||||
|
collapsible
|
||||||
|
className="bg-background-card px-3"
|
||||||
|
>
|
||||||
|
<AccordionItem value={idx.toString()}>
|
||||||
|
<AccordionTrigger>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<span>{x.data?.component_name}</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={inputs} title="Input"></JsonViewer>
|
||||||
|
|
||||||
|
<JsonViewer
|
||||||
|
data={outputs}
|
||||||
|
title={'Output'}
|
||||||
|
></JsonViewer>
|
||||||
|
</div>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</section>
|
||||||
|
</TimelineContent>
|
||||||
|
</TimelineItem>
|
||||||
|
{hasTrace(x.data.component_id) && (
|
||||||
|
<ToolTimelineItem
|
||||||
|
tools={filterTrace(x.data.component_id)}
|
||||||
|
></ToolTimelineItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Timeline>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { SharedFrom } from '@/constants/chat';
|
import { SharedFrom } from '@/constants/chat';
|
||||||
|
import { IEventList } from '@/hooks/use-send-message';
|
||||||
import { useSendAgentMessage } from '@/pages/agent/chat/use-send-agent-message';
|
import { useSendAgentMessage } from '@/pages/agent/chat/use-send-agent-message';
|
||||||
import trim from 'lodash/trim';
|
import trim from 'lodash/trim';
|
||||||
import { useSearchParams } from 'umi';
|
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 { from, sharedId: conversationId } = useGetSharedChatSearchParams();
|
||||||
const url = `/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`;
|
const url = `/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`;
|
||||||
|
|
||||||
const ret = useSendAgentMessage(url);
|
const ret = useSendAgentMessage(url, addEventList);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...ret,
|
...ret,
|
||||||
hasError: false,
|
hasError: false,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
} from '@/hooks/use-agent-request';
|
} from '@/hooks/use-agent-request';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import i18n from '@/locales/config';
|
import i18n from '@/locales/config';
|
||||||
|
import { useCacheChatLog } from '@/pages/agent/hooks/use-cache-chat-log';
|
||||||
import { useSendButtonDisabled } from '@/pages/chat/hooks';
|
import { useSendButtonDisabled } from '@/pages/chat/hooks';
|
||||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||||
import React, { forwardRef, useCallback, useMemo } from 'react';
|
import React, { forwardRef, useCallback, useMemo } from 'react';
|
||||||
@ -31,7 +32,13 @@ const ChatContainer = () => {
|
|||||||
|
|
||||||
const { uploadCanvasFile, loading } =
|
const { uploadCanvasFile, loading } =
|
||||||
useUploadCanvasFileWithProgress(conversationId);
|
useUploadCanvasFileWithProgress(conversationId);
|
||||||
|
const {
|
||||||
|
addEventList,
|
||||||
|
setCurrentMessageId,
|
||||||
|
currentEventListWithoutMessageById,
|
||||||
|
clearEventList,
|
||||||
|
currentMessageId,
|
||||||
|
} = useCacheChatLog();
|
||||||
const {
|
const {
|
||||||
handlePressEnter,
|
handlePressEnter,
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
@ -43,9 +50,22 @@ const ChatContainer = () => {
|
|||||||
stopOutputMessage,
|
stopOutputMessage,
|
||||||
findReferenceByMessageId,
|
findReferenceByMessageId,
|
||||||
appendUploadResponseList,
|
appendUploadResponseList,
|
||||||
} = useSendNextSharedMessage();
|
} = useSendNextSharedMessage(addEventList);
|
||||||
|
|
||||||
const sendDisabled = useSendButtonDisabled(value);
|
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(() => {
|
const useFetchAvatar = useMemo(() => {
|
||||||
return from === SharedFrom.Agent
|
return from === SharedFrom.Agent
|
||||||
? useFetchAgentAvatar
|
? useFetchAgentAvatar
|
||||||
@ -81,6 +101,11 @@ const ChatContainer = () => {
|
|||||||
return (
|
return (
|
||||||
<MessageItem
|
<MessageItem
|
||||||
visibleAvatar={visibleAvatar}
|
visibleAvatar={visibleAvatar}
|
||||||
|
conversationId={conversationId}
|
||||||
|
currentEventListWithoutMessageById={
|
||||||
|
currentEventListWithoutMessageById
|
||||||
|
}
|
||||||
|
setCurrentMessageId={setCurrentMessageId}
|
||||||
key={buildMessageUuidWithRole(message)}
|
key={buildMessageUuidWithRole(message)}
|
||||||
avatarDialog={avatarData.avatar}
|
avatarDialog={avatarData.avatar}
|
||||||
item={message}
|
item={message}
|
||||||
@ -103,7 +128,6 @@ const ChatContainer = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div ref={ref} />
|
<div ref={ref} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NextMessageInput
|
<NextMessageInput
|
||||||
isShared
|
isShared
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { registerNextServer } from '@/utils/register-server';
|
import { registerNextServer } from '@/utils/register-server';
|
||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getCanvasSSE,
|
getCanvasSSE,
|
||||||
@ -104,4 +105,8 @@ const methods = {
|
|||||||
|
|
||||||
const agentService = registerNextServer<keyof typeof methods>(methods);
|
const agentService = registerNextServer<keyof typeof methods>(methods);
|
||||||
|
|
||||||
|
export const fetchTrace = (data: { canvas_id: string; message_id: string }) => {
|
||||||
|
return request.get(methods.trace.url, { params: data });
|
||||||
|
};
|
||||||
|
|
||||||
export default agentService;
|
export default agentService;
|
||||||
|
|||||||
Reference in New Issue
Block a user