Feat: Add log-detail page,Improve the style of chat boxes (#9119)

### What problem does this PR solve?

Feat: Add log-detail page,Improve the style of chat boxes #3221

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
chanx
2025-07-30 17:38:31 +08:00
committed by GitHub
parent 840abd5239
commit db6d4307f2
9 changed files with 210 additions and 78 deletions

View File

@ -170,13 +170,15 @@ function MessageItem({
></ReferenceDocumentList> ></ReferenceDocumentList>
)} )}
{isAssistant && currentEventListWithoutMessageById && ( {isAssistant && currentEventListWithoutMessageById && (
<WorkFlowTimeline <div className="mt-4">
currentEventListWithoutMessage={currentEventListWithoutMessageById( <WorkFlowTimeline
item.id, currentEventListWithoutMessage={currentEventListWithoutMessageById(
)} item.id,
currentMessageId={item.id} )}
canvasId={conversationId} currentMessageId={item.id}
/> canvasId={conversationId}
/>
</div>
)} )}
{isUser && ( {isUser && (
<UploadedMessageFiles files={item.files}></UploadedMessageFiles> <UploadedMessageFiles files={item.files}></UploadedMessageFiles>

View File

@ -143,6 +143,9 @@ const TimeRangePicker = ({
const [date, setDate] = useState<DateRange | undefined>( const [date, setDate] = useState<DateRange | undefined>(
selectDateRange || { from: today, to: today }, selectDateRange || { from: today, to: today },
); );
useEffect(() => {
setDate(selectDateRange);
}, [selectDateRange]);
const onChange = (e: DateRange | undefined) => { const onChange = (e: DateRange | undefined) => {
if (!e) return; if (!e) return;
setDate(e); setDate(e);

View File

@ -20,6 +20,7 @@ export enum MessageEventType {
export interface IAnswerEvent<T> { export interface IAnswerEvent<T> {
event: MessageEventType; event: MessageEventType;
message_id: string; message_id: string;
session_id: string;
created_at: number; created_at: number;
task_id: string; task_id: string;
data: T; data: T;

View File

@ -180,6 +180,7 @@ export const useSendAgentMessage = (
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 [sessionId, setSessionId] = useState<string | null>(null);
const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE( const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
url || api.runCanvas, url || api.runCanvas,
); );
@ -187,6 +188,12 @@ export const useSendAgentMessage = (
return answerList[0]?.message_id; return answerList[0]?.message_id;
}, [answerList]); }, [answerList]);
useEffect(() => {
if (answerList[0]?.session_id) {
setSessionId(answerList[0]?.session_id);
}
}, [answerList]);
const { findReferenceByMessageId } = useFindMessageReference(answerList); const { findReferenceByMessageId } = useFindMessageReference(answerList);
const prologue = useGetBeginNodePrologue(); const prologue = useGetBeginNodePrologue();
const { const {
@ -222,6 +229,8 @@ export const useSendAgentMessage = (
params.inputs = transferInputsArrayToObject(query); // begin operator inputs params.inputs = transferInputsArrayToObject(query); // begin operator inputs
params.files = uploadResponseList; params.files = uploadResponseList;
params.session_id = sessionId;
} }
const res = await send(params); const res = await send(params);
@ -239,6 +248,7 @@ export const useSendAgentMessage = (
}, },
[ [
agentId, agentId,
sessionId,
send, send,
inputs, inputs,
uploadResponseList, uploadResponseList,

View File

@ -43,19 +43,13 @@ export function useCacheChatLog() {
setEventList([]); setEventList([]);
}, []); }, []);
const addEventList = useCallback( const addEventList = useCallback((events: IEventList, message_id: string) => {
(events: IEventList, message_id: string) => { setEventList((x) => {
const nextList = [...eventList]; const list = [...x, ...events];
events.forEach((x) => { setMessageIdPool((prev) => ({ ...prev, [message_id]: list }));
if (nextList.every((y) => y !== x)) { return list;
nextList.push(x); });
} }, []);
});
setEventList(nextList);
setMessageIdPool((prev) => ({ ...prev, [message_id]: nextList }));
},
[eventList],
);
const currentEventListWithoutMessage = useMemo(() => { const currentEventListWithoutMessage = useMemo(() => {
const list = messageIdPool[currentMessageId]?.filter( const list = messageIdPool[currentMessageId]?.filter(

View File

@ -18,16 +18,26 @@ import { JsonViewer } from './workFlowTimeline';
const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => { const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => {
if (!tools || tools.length === 0 || !Array.isArray(tools)) return null; if (!tools || tools.length === 0 || !Array.isArray(tools)) return null;
const blackList = ['analyze_task', 'add_memory', 'gen_citations']; const blackList = ['add_memory', 'gen_citations'];
const filteredTools = tools.filter( const filteredTools = tools.filter(
(tool) => !blackList.includes(tool.tool_name), (tool) => !blackList.includes(tool.tool_name),
); );
const capitalizeWords = (str: string, separator: string = '_'): string => {
if (!str) return '';
return str
.split(separator)
.map((word) => {
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
})
.join(' ');
};
return ( return (
<> <>
{filteredTools?.map((tool, idx) => { {filteredTools?.map((tool, idx) => {
return ( return (
<TimelineItem <TimelineItem
key={idx} key={'tool_' + idx}
step={idx} step={idx}
className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8" className="group-data-[orientation=vertical]/timeline:ms-10 group-data-[orientation=vertical]/timeline:not-last:pb-8"
> >
@ -82,7 +92,10 @@ const ToolTimelineItem = ({ tools }: { tools: Record<string, any>[] }) => {
<AccordionItem value={idx.toString()}> <AccordionItem value={idx.toString()}>
<AccordionTrigger> <AccordionTrigger>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<span>{tool.tool_name}</span> <span>
{tool.path + ' '}
{capitalizeWords(tool.tool_name, '_')}
</span>
<span className="text-text-sub-title text-xs"> <span className="text-text-sub-title text-xs">
{/* 0:00 {/* 0:00
{x.data.elapsed_time?.toString().slice(0, 6)} */} {x.data.elapsed_time?.toString().slice(0, 6)} */}

View File

@ -0,0 +1,58 @@
import MessageItem from '@/components/next-message-item';
import { Modal } from '@/components/ui/modal';
import { useFetchAgent } from '@/hooks/use-agent-request';
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
import { IAgentLogMessage } from '@/interfaces/database/agent';
import { IReferenceObject, Message } from '@/interfaces/database/chat';
import { buildMessageUuidWithRole } from '@/utils/chat';
import React from 'react';
import { IMessage } from '../chat/interface';
interface CustomModalProps {
isOpen: boolean;
onClose: () => void;
message: IAgentLogMessage[];
reference: IReferenceObject;
}
export const AgentLogDetailModal: React.FC<CustomModalProps> = ({
isOpen,
onClose,
message: derivedMessages,
reference,
}) => {
const { data: userInfo } = useFetchUserInfo();
const { data: canvasInfo } = useFetchAgent();
return (
<Modal
open={isOpen}
onCancel={onClose}
showfooter={false}
footer={null}
title={derivedMessages?.length ? derivedMessages[0]?.content : ''}
className="!w-[900px]"
>
<div className="flex items-start mb-4 flex-col gap-4 justify-start">
<div>
{derivedMessages?.map((message, i) => {
return (
<MessageItem
key={buildMessageUuidWithRole(
message as Partial<Message | IMessage>,
)}
nickname={userInfo.nickname}
avatar={userInfo.avatar}
avatarDialog={canvasInfo.avatar}
item={message as IMessage}
reference={reference}
index={i}
showLikeButton={false}
showLog={false}
></MessageItem>
);
})}
</div>
</div>
</Modal>
);
};

View File

@ -13,7 +13,12 @@ import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { Spin } from '@/components/ui/spin'; import { Spin } from '@/components/ui/spin';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useFetchAgentLog } from '@/hooks/use-agent-request'; import { useFetchAgentLog } from '@/hooks/use-agent-request';
import { IAgentLogResponse } from '@/interfaces/database/agent'; import {
IAgentLogMessage,
IAgentLogResponse,
} from '@/interfaces/database/agent';
import { IReferenceObject } from '@/interfaces/database/chat';
import { useQueryClient } from '@tanstack/react-query';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useParams } from 'umi'; import { useParams } from 'umi';
import { DateRange } from '../../components/originui/calendar/index'; import { DateRange } from '../../components/originui/calendar/index';
@ -26,16 +31,27 @@ import {
TableRow, TableRow,
} from '../../components/ui/table'; } from '../../components/ui/table';
import { useFetchDataOnMount } from '../agent/hooks/use-fetch-data'; import { useFetchDataOnMount } from '../agent/hooks/use-fetch-data';
import { AgentLogDetailModal } from './agent-log-detail-modal';
const getStartOfToday = (): Date => {
const today = new Date();
today.setHours(0, 0, 0, 0);
return today;
};
const getEndOfToday = (): Date => {
const today = new Date();
today.setHours(23, 59, 59, 999);
return today;
};
const AgentLogPage: React.FC = () => { const AgentLogPage: React.FC = () => {
const { navigateToAgentList, navigateToAgent } = useNavigatePage(); const { navigateToAgentList, navigateToAgent } = useNavigatePage();
const { flowDetail: agentDetail } = useFetchDataOnMount(); const { flowDetail: agentDetail } = useFetchDataOnMount();
const { id: canvasId } = useParams(); const { id: canvasId } = useParams();
const today = new Date(); const queryClient = useQueryClient();
const init = { const init = {
keywords: '', keywords: '',
from_date: today, from_date: getStartOfToday(),
to_date: today, to_date: getEndOfToday(),
orderby: 'create_time', orderby: 'create_time',
desc: false, desc: false,
page: 1, page: 1,
@ -152,6 +168,13 @@ const AgentLogPage: React.FC = () => {
}); });
}; };
const handleClickSearch = () => {
setPagination({ ...pagination, current: 1 });
handleSearch();
queryClient.invalidateQueries({
queryKey: ['fetchAgentLog'],
});
};
useEffect(() => { useEffect(() => {
handleSearch(); handleSearch();
}, [pagination.current, pagination.pageSize, sortConfig]); }, [pagination.current, pagination.pageSize, sortConfig]);
@ -166,7 +189,17 @@ const AgentLogPage: React.FC = () => {
const handleReset = () => { const handleReset = () => {
setSearchParams(init); setSearchParams(init);
setKeywords(init.keywords);
setCurrentDate({ from: init.from_date, to: init.to_date });
}; };
const [openModal, setOpenModal] = useState(false);
const [modalData, setModalData] = useState<IAgentLogResponse>();
const showLogDetail = (item: IAgentLogResponse) => {
setModalData(item);
setOpenModal(true);
};
return ( return (
<div className=" text-white"> <div className=" text-white">
<PageHeader> <PageHeader>
@ -209,15 +242,14 @@ const AgentLogPage: React.FC = () => {
<span className="whitespace-nowrap">Latest Date</span> <span className="whitespace-nowrap">Latest Date</span>
<TimeRangePicker <TimeRangePicker
onSelect={handleDateRangeChange} onSelect={handleDateRangeChange}
selectDateRange={{ from: currentDate.from, to: currentDate.to }} selectDateRange={currentDate}
/> />
</div> </div>
<button <button
type="button" type="button"
className="bg-foreground text-text-title-invert px-4 py-1 rounded" className="bg-foreground text-text-title-invert px-4 py-1 rounded"
onClick={() => { onClick={() => {
setPagination({ ...pagination, current: 1 }); handleClickSearch();
handleSearch();
}} }}
> >
Search Search
@ -276,7 +308,12 @@ const AgentLogPage: React.FC = () => {
)} )}
{!loading && {!loading &&
data?.map((item) => ( data?.map((item) => (
<TableRow key={item.id}> <TableRow
key={item.id}
onClick={() => {
showLogDetail(item);
}}
>
{columns.map((column) => ( {columns.map((column) => (
<TableCell key={column.dataIndex}> <TableCell key={column.dataIndex}>
{column.render {column.render
@ -312,6 +349,12 @@ const AgentLogPage: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
<AgentLogDetailModal
isOpen={openModal}
message={modalData?.message as IAgentLogMessage[]}
reference={modalData?.reference as unknown as IReferenceObject}
onClose={() => setOpenModal(false)}
/>
</div> </div>
); );
}; };

View File

@ -91,55 +91,63 @@ const ChatContainer = () => {
} }
return ( return (
<section className="h-[100vh]"> <section className="h-[100vh] flex justify-center items-center">
<section className={cn('flex flex-1 flex-col p-2.5 h-full')}> <div className=" w-[80vw]">
<div className={cn('flex flex-1 flex-col overflow-auto pr-2')}> <div className="flex flex-1 flex-col p-2.5 h-[90vh] border rounded-lg">
<div> <div
{derivedMessages?.map((message, i) => { className={cn('flex flex-1 flex-col overflow-auto m-auto w-5/6')}
return ( >
<MessageItem <div>
visibleAvatar={visibleAvatar} {derivedMessages?.map((message, i) => {
conversationId={conversationId} return (
currentEventListWithoutMessageById={ <MessageItem
currentEventListWithoutMessageById visibleAvatar={visibleAvatar}
} conversationId={conversationId}
setCurrentMessageId={setCurrentMessageId} currentEventListWithoutMessageById={
key={buildMessageUuidWithRole(message)} currentEventListWithoutMessageById
avatarDialog={avatarData.avatar} }
item={message} setCurrentMessageId={setCurrentMessageId}
nickname="You" key={buildMessageUuidWithRole(message)}
reference={findReferenceByMessageId(message.id)} avatarDialog={avatarData.avatar}
loading={ item={message}
message.role === MessageType.Assistant && nickname="You"
sendLoading && reference={findReferenceByMessageId(message.id)}
derivedMessages?.length - 1 === i loading={
} message.role === MessageType.Assistant &&
index={i} sendLoading &&
clickDocumentButton={clickDocumentButton} derivedMessages?.length - 1 === i
showLikeButton={false} }
showLoudspeaker={false} index={i}
showLog={false} clickDocumentButton={clickDocumentButton}
sendLoading={sendLoading} showLikeButton={false}
></MessageItem> showLoudspeaker={false}
); showLog={false}
})} sendLoading={sendLoading}
></MessageItem>
);
})}
</div>
<div ref={ref} />
</div>
<div className="flex w-full justify-center mb-8">
<div className="w-5/6">
<NextMessageInput
isShared
value={value}
disabled={hasError}
sendDisabled={sendDisabled}
conversationId={conversationId}
onInputChange={handleInputChange}
onPressEnter={handlePressEnter}
sendLoading={sendLoading}
stopOutputMessage={stopOutputMessage}
onUpload={handleUploadFile}
isUploading={loading}
></NextMessageInput>
</div>
</div> </div>
<div ref={ref} />
</div> </div>
<NextMessageInput </div>
isShared
value={value}
disabled={hasError}
sendDisabled={sendDisabled}
conversationId={conversationId}
onInputChange={handleInputChange}
onPressEnter={handlePressEnter}
sendLoading={sendLoading}
stopOutputMessage={stopOutputMessage}
onUpload={handleUploadFile}
isUploading={loading}
></NextMessageInput>
</section>
{visible && ( {visible && (
<PdfDrawer <PdfDrawer
visible={visible} visible={visible}