浏览代码

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)
tags/v0.20.0
chanx 3 个月前
父节点
当前提交
db6d4307f2
没有帐户链接到提交者的电子邮件

+ 9
- 7
web/src/components/next-message-item/index.tsx 查看文件

></ReferenceDocumentList> ></ReferenceDocumentList>
)} )}
{isAssistant && currentEventListWithoutMessageById && ( {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 && ( {isUser && (
<UploadedMessageFiles files={item.files}></UploadedMessageFiles> <UploadedMessageFiles files={item.files}></UploadedMessageFiles>

+ 3
- 0
web/src/components/originui/time-range-picker.tsx 查看文件

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);

+ 1
- 0
web/src/hooks/use-send-message.ts 查看文件

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;

+ 10
- 0
web/src/pages/agent/chat/use-send-agent-message.ts 查看文件

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,
); );
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 {
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);


}, },
[ [
agentId, agentId,
sessionId,
send, send,
inputs, inputs,
uploadResponseList, uploadResponseList,

+ 7
- 13
web/src/pages/agent/hooks/use-cache-chat-log.ts 查看文件

setEventList([]); 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 currentEventListWithoutMessage = useMemo(() => {
const list = messageIdPool[currentMessageId]?.filter( const list = messageIdPool[currentMessageId]?.filter(

+ 16
- 3
web/src/pages/agent/log-sheet/toolTimelineItem.tsx 查看文件



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"
> >
<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)} */}

+ 58
- 0
web/src/pages/agents/agent-log-detail-modal.tsx 查看文件

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>
);
};

+ 51
- 8
web/src/pages/agents/agent-log-page.tsx 查看文件

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';
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,
to_date: today,
from_date: getStartOfToday(),
to_date: getEndOfToday(),
orderby: 'create_time', orderby: 'create_time',
desc: false, desc: false,
page: 1, page: 1,
}); });
}; };


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]);


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>
<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 });
handleSearch();
handleClickSearch();
}} }}
> >
Search Search
)} )}
{!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
</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>
); );
}; };

+ 55
- 47
web/src/pages/next-chats/share/index.tsx 查看文件

} }


return ( 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>
<div ref={ref} />
</div> </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 && ( {visible && (
<PdfDrawer <PdfDrawer
visible={visible} visible={visible}

正在加载...
取消
保存