### 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)tags/v0.20.0
| @@ -170,13 +170,15 @@ function MessageItem({ | |||
| ></ReferenceDocumentList> | |||
| )} | |||
| {isAssistant && currentEventListWithoutMessageById && ( | |||
| <WorkFlowTimeline | |||
| currentEventListWithoutMessage={currentEventListWithoutMessageById( | |||
| item.id, | |||
| )} | |||
| currentMessageId={item.id} | |||
| canvasId={conversationId} | |||
| /> | |||
| <div className="mt-4"> | |||
| <WorkFlowTimeline | |||
| currentEventListWithoutMessage={currentEventListWithoutMessageById( | |||
| item.id, | |||
| )} | |||
| currentMessageId={item.id} | |||
| canvasId={conversationId} | |||
| /> | |||
| </div> | |||
| )} | |||
| {isUser && ( | |||
| <UploadedMessageFiles files={item.files}></UploadedMessageFiles> | |||
| @@ -143,6 +143,9 @@ const TimeRangePicker = ({ | |||
| const [date, setDate] = useState<DateRange | undefined>( | |||
| selectDateRange || { from: today, to: today }, | |||
| ); | |||
| useEffect(() => { | |||
| setDate(selectDateRange); | |||
| }, [selectDateRange]); | |||
| const onChange = (e: DateRange | undefined) => { | |||
| if (!e) return; | |||
| setDate(e); | |||
| @@ -20,6 +20,7 @@ export enum MessageEventType { | |||
| export interface IAnswerEvent<T> { | |||
| event: MessageEventType; | |||
| message_id: string; | |||
| session_id: string; | |||
| created_at: number; | |||
| task_id: string; | |||
| data: T; | |||
| @@ -180,6 +180,7 @@ export const useSendAgentMessage = ( | |||
| const { id: agentId } = useParams(); | |||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | |||
| const inputs = useSelectBeginNodeDataInputs(); | |||
| const [sessionId, setSessionId] = useState<string | null>(null); | |||
| const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE( | |||
| url || api.runCanvas, | |||
| ); | |||
| @@ -187,6 +188,12 @@ export const useSendAgentMessage = ( | |||
| return answerList[0]?.message_id; | |||
| }, [answerList]); | |||
| useEffect(() => { | |||
| if (answerList[0]?.session_id) { | |||
| setSessionId(answerList[0]?.session_id); | |||
| } | |||
| }, [answerList]); | |||
| const { findReferenceByMessageId } = useFindMessageReference(answerList); | |||
| const prologue = useGetBeginNodePrologue(); | |||
| const { | |||
| @@ -222,6 +229,8 @@ export const useSendAgentMessage = ( | |||
| params.inputs = transferInputsArrayToObject(query); // begin operator inputs | |||
| params.files = uploadResponseList; | |||
| params.session_id = sessionId; | |||
| } | |||
| const res = await send(params); | |||
| @@ -239,6 +248,7 @@ export const useSendAgentMessage = ( | |||
| }, | |||
| [ | |||
| agentId, | |||
| sessionId, | |||
| send, | |||
| inputs, | |||
| uploadResponseList, | |||
| @@ -43,19 +43,13 @@ export function useCacheChatLog() { | |||
| setEventList([]); | |||
| }, []); | |||
| const addEventList = useCallback( | |||
| (events: IEventList, message_id: string) => { | |||
| const nextList = [...eventList]; | |||
| events.forEach((x) => { | |||
| if (nextList.every((y) => y !== x)) { | |||
| nextList.push(x); | |||
| } | |||
| }); | |||
| setEventList(nextList); | |||
| setMessageIdPool((prev) => ({ ...prev, [message_id]: nextList })); | |||
| }, | |||
| [eventList], | |||
| ); | |||
| const addEventList = useCallback((events: IEventList, message_id: string) => { | |||
| setEventList((x) => { | |||
| const list = [...x, ...events]; | |||
| setMessageIdPool((prev) => ({ ...prev, [message_id]: list })); | |||
| return list; | |||
| }); | |||
| }, []); | |||
| const currentEventListWithoutMessage = useMemo(() => { | |||
| const list = messageIdPool[currentMessageId]?.filter( | |||
| @@ -18,16 +18,26 @@ 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 blackList = ['add_memory', 'gen_citations']; | |||
| const filteredTools = tools.filter( | |||
| (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 ( | |||
| <> | |||
| {filteredTools?.map((tool, idx) => { | |||
| return ( | |||
| <TimelineItem | |||
| key={idx} | |||
| key={'tool_' + idx} | |||
| step={idx} | |||
| 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()}> | |||
| <AccordionTrigger> | |||
| <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"> | |||
| {/* 0:00 | |||
| {x.data.elapsed_time?.toString().slice(0, 6)} */} | |||
| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -13,7 +13,12 @@ import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; | |||
| import { Spin } from '@/components/ui/spin'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| 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 { useParams } from 'umi'; | |||
| import { DateRange } from '../../components/originui/calendar/index'; | |||
| @@ -26,16 +31,27 @@ import { | |||
| TableRow, | |||
| } from '../../components/ui/table'; | |||
| 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 { navigateToAgentList, navigateToAgent } = useNavigatePage(); | |||
| const { flowDetail: agentDetail } = useFetchDataOnMount(); | |||
| const { id: canvasId } = useParams(); | |||
| const today = new Date(); | |||
| const queryClient = useQueryClient(); | |||
| const init = { | |||
| keywords: '', | |||
| from_date: today, | |||
| to_date: today, | |||
| from_date: getStartOfToday(), | |||
| to_date: getEndOfToday(), | |||
| orderby: 'create_time', | |||
| desc: false, | |||
| page: 1, | |||
| @@ -152,6 +168,13 @@ const AgentLogPage: React.FC = () => { | |||
| }); | |||
| }; | |||
| const handleClickSearch = () => { | |||
| setPagination({ ...pagination, current: 1 }); | |||
| handleSearch(); | |||
| queryClient.invalidateQueries({ | |||
| queryKey: ['fetchAgentLog'], | |||
| }); | |||
| }; | |||
| useEffect(() => { | |||
| handleSearch(); | |||
| }, [pagination.current, pagination.pageSize, sortConfig]); | |||
| @@ -166,7 +189,17 @@ const AgentLogPage: React.FC = () => { | |||
| const handleReset = () => { | |||
| 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 ( | |||
| <div className=" text-white"> | |||
| <PageHeader> | |||
| @@ -209,15 +242,14 @@ const AgentLogPage: React.FC = () => { | |||
| <span className="whitespace-nowrap">Latest Date</span> | |||
| <TimeRangePicker | |||
| onSelect={handleDateRangeChange} | |||
| selectDateRange={{ from: currentDate.from, to: currentDate.to }} | |||
| selectDateRange={currentDate} | |||
| /> | |||
| </div> | |||
| <button | |||
| type="button" | |||
| className="bg-foreground text-text-title-invert px-4 py-1 rounded" | |||
| onClick={() => { | |||
| setPagination({ ...pagination, current: 1 }); | |||
| handleSearch(); | |||
| handleClickSearch(); | |||
| }} | |||
| > | |||
| Search | |||
| @@ -276,7 +308,12 @@ const AgentLogPage: React.FC = () => { | |||
| )} | |||
| {!loading && | |||
| data?.map((item) => ( | |||
| <TableRow key={item.id}> | |||
| <TableRow | |||
| key={item.id} | |||
| onClick={() => { | |||
| showLogDetail(item); | |||
| }} | |||
| > | |||
| {columns.map((column) => ( | |||
| <TableCell key={column.dataIndex}> | |||
| {column.render | |||
| @@ -312,6 +349,12 @@ const AgentLogPage: React.FC = () => { | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <AgentLogDetailModal | |||
| isOpen={openModal} | |||
| message={modalData?.message as IAgentLogMessage[]} | |||
| reference={modalData?.reference as unknown as IReferenceObject} | |||
| onClose={() => setOpenModal(false)} | |||
| /> | |||
| </div> | |||
| ); | |||
| }; | |||
| @@ -91,55 +91,63 @@ const ChatContainer = () => { | |||
| } | |||
| return ( | |||
| <section className="h-[100vh]"> | |||
| <section className={cn('flex flex-1 flex-col p-2.5 h-full')}> | |||
| <div className={cn('flex flex-1 flex-col overflow-auto pr-2')}> | |||
| <div> | |||
| {derivedMessages?.map((message, i) => { | |||
| return ( | |||
| <MessageItem | |||
| visibleAvatar={visibleAvatar} | |||
| conversationId={conversationId} | |||
| currentEventListWithoutMessageById={ | |||
| currentEventListWithoutMessageById | |||
| } | |||
| setCurrentMessageId={setCurrentMessageId} | |||
| key={buildMessageUuidWithRole(message)} | |||
| avatarDialog={avatarData.avatar} | |||
| item={message} | |||
| nickname="You" | |||
| reference={findReferenceByMessageId(message.id)} | |||
| loading={ | |||
| message.role === MessageType.Assistant && | |||
| sendLoading && | |||
| derivedMessages?.length - 1 === i | |||
| } | |||
| index={i} | |||
| clickDocumentButton={clickDocumentButton} | |||
| showLikeButton={false} | |||
| showLoudspeaker={false} | |||
| showLog={false} | |||
| sendLoading={sendLoading} | |||
| ></MessageItem> | |||
| ); | |||
| })} | |||
| <section className="h-[100vh] flex justify-center items-center"> | |||
| <div className=" w-[80vw]"> | |||
| <div className="flex flex-1 flex-col p-2.5 h-[90vh] border rounded-lg"> | |||
| <div | |||
| className={cn('flex flex-1 flex-col overflow-auto m-auto w-5/6')} | |||
| > | |||
| <div> | |||
| {derivedMessages?.map((message, i) => { | |||
| return ( | |||
| <MessageItem | |||
| visibleAvatar={visibleAvatar} | |||
| conversationId={conversationId} | |||
| currentEventListWithoutMessageById={ | |||
| currentEventListWithoutMessageById | |||
| } | |||
| setCurrentMessageId={setCurrentMessageId} | |||
| key={buildMessageUuidWithRole(message)} | |||
| avatarDialog={avatarData.avatar} | |||
| item={message} | |||
| nickname="You" | |||
| reference={findReferenceByMessageId(message.id)} | |||
| loading={ | |||
| message.role === MessageType.Assistant && | |||
| sendLoading && | |||
| derivedMessages?.length - 1 === i | |||
| } | |||
| index={i} | |||
| clickDocumentButton={clickDocumentButton} | |||
| showLikeButton={false} | |||
| 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 ref={ref} /> | |||
| </div> | |||
| <NextMessageInput | |||
| isShared | |||
| value={value} | |||
| disabled={hasError} | |||
| sendDisabled={sendDisabled} | |||
| conversationId={conversationId} | |||
| onInputChange={handleInputChange} | |||
| onPressEnter={handlePressEnter} | |||
| sendLoading={sendLoading} | |||
| stopOutputMessage={stopOutputMessage} | |||
| onUpload={handleUploadFile} | |||
| isUploading={loading} | |||
| ></NextMessageInput> | |||
| </section> | |||
| </div> | |||
| {visible && ( | |||
| <PdfDrawer | |||
| visible={visible} | |||