mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-12 03:33:33 +08:00
Feat: Export Agent Logs. (#13658)
### What problem does this PR solve? Feat: Export Agent Logs. ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Co-authored-by: balibabu <assassin_cike@163.com>
This commit is contained in:
@@ -71,6 +71,8 @@ export const enum AgentApiAction {
|
||||
CreateAgentSession = 'createAgentSession',
|
||||
DeleteAgentSession = 'deleteAgentSession',
|
||||
FetchSessionByIdManually = 'fetchSessionByIdManually',
|
||||
FetchAgentLog = 'fetchAgentLog',
|
||||
FetchFlowDetailSSE = 'flowDetailSSE',
|
||||
}
|
||||
|
||||
export const useFetchAgentTemplates = () => {
|
||||
@@ -599,7 +601,7 @@ export const useFetchAgentAvatar = (): {
|
||||
export const useFetchAgentLog = (searchParams: IAgentLogsRequest) => {
|
||||
const { id } = useParams();
|
||||
const { data, isFetching: loading } = useQuery<IAgentLogsResponse>({
|
||||
queryKey: ['fetchAgentLog', id, searchParams],
|
||||
queryKey: [AgentApiAction.FetchAgentLog, id, searchParams],
|
||||
initialData: {} as IAgentLogsResponse,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
@@ -803,7 +805,7 @@ export const useFetchFlowSSE = (): {
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ['flowDetailSSE'],
|
||||
queryKey: [AgentApiAction.FetchFlowDetailSSE],
|
||||
initialData: {} as IFlow,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
@@ -970,3 +972,21 @@ export function useFetchSessionManually() {
|
||||
|
||||
return { data, loading, fetchSessionManually: mutateAsync };
|
||||
}
|
||||
|
||||
export const useExportAgentLog = () => {
|
||||
const { id } = useParams();
|
||||
const { mutateAsync, isPending: loading } = useMutation({
|
||||
mutationKey: [AgentApiAction.FetchAgentLog, 'export', id],
|
||||
mutationFn: async (searchParams: IAgentLogsRequest) => {
|
||||
const { data } = await fetchAgentLogsByCanvasId(id as string, {
|
||||
...searchParams,
|
||||
page: 1,
|
||||
page_size: 100000,
|
||||
});
|
||||
|
||||
return data?.data?.sessions ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { exportLogs: mutateAsync, loading };
|
||||
};
|
||||
|
||||
@@ -97,10 +97,12 @@ export interface IFlowTemplate {
|
||||
description: {
|
||||
en: string;
|
||||
zh: string;
|
||||
de: string;
|
||||
};
|
||||
title: {
|
||||
en: string;
|
||||
zh: string;
|
||||
de: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -264,6 +266,7 @@ export interface IAgentLogResponse {
|
||||
dsl: string;
|
||||
reference: IReference;
|
||||
name: string;
|
||||
version_title: string;
|
||||
}
|
||||
export interface IAgentLogsResponse {
|
||||
total: number;
|
||||
|
||||
@@ -2420,6 +2420,15 @@ Important structured information may include: names, dates, locations, events, k
|
||||
},
|
||||
saveToMemory: 'Save to memory',
|
||||
retrievalFrom: 'Retrieval from',
|
||||
id: 'ID',
|
||||
state: 'State',
|
||||
number: 'Number',
|
||||
latestDate: 'Latest date',
|
||||
createDate: 'Create date',
|
||||
noDataToExport: 'No data to export',
|
||||
success: 'Success',
|
||||
failed: 'Failed',
|
||||
logTitle: 'Title',
|
||||
},
|
||||
llmTools: {
|
||||
bad_calculator: {
|
||||
|
||||
@@ -1260,6 +1260,12 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
consumerApp: '消费者应用',
|
||||
other: '其他',
|
||||
agents: '智能体',
|
||||
id: 'ID',
|
||||
logTitle: '标题',
|
||||
state: '状态',
|
||||
number: '轮数',
|
||||
latestDate: '最新日期',
|
||||
createDate: '创建日期',
|
||||
publishedAt: '发布于',
|
||||
beginInput: '开始输入',
|
||||
seconds: '秒',
|
||||
@@ -1352,6 +1358,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
version: {
|
||||
details: '版本详情',
|
||||
download: '下载',
|
||||
version: '版本',
|
||||
},
|
||||
cite: '引用',
|
||||
citeTip: '引用',
|
||||
|
||||
@@ -72,7 +72,7 @@ export function VersionDialog({
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<section className="flex gap-8 relative">
|
||||
<div className="w-1/3 max-h-[60vh] overflow-auto min-h-[40vh]">
|
||||
<div className="w-72 max-h-[60vh] overflow-auto min-h-[40vh]">
|
||||
{loading ? (
|
||||
<Spin className="top-1/2"></Spin>
|
||||
) : (
|
||||
@@ -100,7 +100,7 @@ export function VersionDialog({
|
||||
) : (
|
||||
<Card className="h-full">
|
||||
<CardContent className="h-full p-5 flex flex-col">
|
||||
<section className="flex justify-between">
|
||||
<section className="flex justify-between pb-2">
|
||||
<div>
|
||||
<div className="flex">
|
||||
<span className="pb-1 truncate">{agent?.title}</span>
|
||||
@@ -143,6 +143,9 @@ export function VersionDialog({
|
||||
zoomOnDoubleClick={false}
|
||||
preventScrolling={true}
|
||||
minZoom={0.1}
|
||||
nodesDraggable={false}
|
||||
nodesConnectable={false}
|
||||
elementsSelectable={false}
|
||||
>
|
||||
<AgentBackground></AgentBackground>
|
||||
<Spotlight className="z-0" opcity={0.7} coverage={70} />
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
} from '../../components/ui/table';
|
||||
import { useFetchDataOnMount } from '../agent/hooks/use-fetch-data';
|
||||
import { AgentLogDetailModal } from './agent-log-detail-modal';
|
||||
import { useExportAgentLogToCSV } from './hooks/use-export-agent-log';
|
||||
const getStartOfToday = (): Date => {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
@@ -64,18 +65,18 @@ const AgentLogPage: React.FC = () => {
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
title: t('flow.id'),
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: 'User ID',
|
||||
title: t('flow.userId'),
|
||||
dataIndex: 'user_id',
|
||||
key: 'user_id',
|
||||
render: (text: string) => <span>{text}</span>,
|
||||
},
|
||||
{
|
||||
title: 'Title',
|
||||
title: t('flow.logTitle'),
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
render: (_text: string, record: IAgentLogResponse) => (
|
||||
@@ -85,7 +86,7 @@ const AgentLogPage: React.FC = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'State',
|
||||
title: t('flow.state'),
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
render: (_text: string, record: IAgentLogResponse) => (
|
||||
@@ -96,31 +97,32 @@ const AgentLogPage: React.FC = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Number',
|
||||
title: t('flow.number'),
|
||||
dataIndex: 'round',
|
||||
key: 'round',
|
||||
},
|
||||
{
|
||||
title: 'Latest Date',
|
||||
title: t('flow.latestDate'),
|
||||
dataIndex: 'update_date',
|
||||
key: 'update_date',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
title: 'Create Date',
|
||||
title: t('flow.createDate'),
|
||||
dataIndex: 'create_date',
|
||||
key: 'create_date',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
title: 'Version',
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
title: t('flow.version.version'),
|
||||
dataIndex: 'version_title',
|
||||
key: 'version_title',
|
||||
},
|
||||
];
|
||||
|
||||
const { data: logData, loading } = useFetchAgentLog(searchParams);
|
||||
const { sessions: data, total } = logData || {};
|
||||
const { handleExport, loading: exportLoading } = useExportAgentLogToCSV();
|
||||
const [currentDate, setCurrentDate] = useState<DateRange>({
|
||||
from: searchParams.from_date,
|
||||
to: searchParams.to_date,
|
||||
@@ -158,7 +160,6 @@ const AgentLogPage: React.FC = () => {
|
||||
} | null>({ orderby: init.orderby, desc: init.desc ? true : false });
|
||||
|
||||
const handlePageChange = (current?: number, pageSize?: number) => {
|
||||
console.log('current', current, 'pageSize', pageSize);
|
||||
let page = current || 1;
|
||||
if (pagination.pageSize !== pageSize) {
|
||||
page = 1;
|
||||
@@ -219,6 +220,16 @@ const AgentLogPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onExportClick = () => {
|
||||
handleExport({
|
||||
keywords: searchParams.keywords,
|
||||
from_date: searchParams.from_date,
|
||||
to_date: searchParams.to_date,
|
||||
orderby: searchParams.orderby,
|
||||
desc: searchParams.desc,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className=" text-white">
|
||||
<PageHeader>
|
||||
@@ -246,7 +257,9 @@ const AgentLogPage: React.FC = () => {
|
||||
|
||||
<div className="flex justify-end space-x-2 mb-4 text-foreground">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button>{t('flow.export')}</Button>
|
||||
<Button onClick={onExportClick} loading={exportLoading}>
|
||||
{t('flow.export')}
|
||||
</Button>
|
||||
<span>ID/Title</span>
|
||||
<SearchInput
|
||||
value={keywords}
|
||||
|
||||
81
web/src/pages/agents/hooks/use-export-agent-log.ts
Normal file
81
web/src/pages/agents/hooks/use-export-agent-log.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { useExportAgentLog } from '@/hooks/use-agent-request';
|
||||
import { IAgentLogResponse } from '@/interfaces/database/agent';
|
||||
import { downloadFileFromBlob } from '@/utils/file-util';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
interface ISearchParams {
|
||||
keywords?: string;
|
||||
from_date?: Date;
|
||||
to_date?: Date;
|
||||
orderby?: string;
|
||||
desc?: boolean;
|
||||
}
|
||||
|
||||
export const useExportAgentLogToCSV = () => {
|
||||
const { t } = useTranslation();
|
||||
const { id: canvasId } = useParams();
|
||||
const { exportLogs, loading } = useExportAgentLog();
|
||||
|
||||
const convertToCSV = (data: IAgentLogResponse[]) => {
|
||||
const headers = [
|
||||
t('flow.id'),
|
||||
t('flow.userId'),
|
||||
t('flow.logTitle'),
|
||||
t('flow.state'),
|
||||
t('flow.number'),
|
||||
t('flow.latestDate'),
|
||||
t('flow.createDate'),
|
||||
t('flow.version.version'),
|
||||
];
|
||||
|
||||
const rows = data.map((item) => [
|
||||
item.id,
|
||||
item.user_id,
|
||||
item.message?.length ? item.message[0]?.content : '',
|
||||
item.errors ? t('flow.failed') : t('flow.success'),
|
||||
item.round,
|
||||
item.update_date,
|
||||
item.create_date,
|
||||
item.version_title,
|
||||
]);
|
||||
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...rows.map((row) =>
|
||||
row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(','),
|
||||
),
|
||||
].join('\n');
|
||||
|
||||
return csvContent;
|
||||
};
|
||||
|
||||
const handleExport = async (searchParams: ISearchParams) => {
|
||||
const allData = await exportLogs({
|
||||
keywords: searchParams.keywords,
|
||||
from_date: searchParams.from_date,
|
||||
to_date: searchParams.to_date,
|
||||
orderby: searchParams.orderby,
|
||||
desc: searchParams.desc,
|
||||
});
|
||||
|
||||
if (allData.length === 0) {
|
||||
message.warning(t('flow.noDataToExport'));
|
||||
return;
|
||||
}
|
||||
|
||||
const csvContent = convertToCSV(allData);
|
||||
// Add BOM for Excel to correctly display UTF-8
|
||||
const BOM = '\uFEFF';
|
||||
const blob = new Blob([BOM + csvContent], {
|
||||
type: 'text/csv;charset=utf-8;',
|
||||
});
|
||||
downloadFileFromBlob(
|
||||
blob,
|
||||
`agent-logs-${canvasId}-${new Date().toISOString().split('T')[0]}.csv`,
|
||||
);
|
||||
};
|
||||
|
||||
return { handleExport, loading };
|
||||
};
|
||||
Reference in New Issue
Block a user