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:
balibabu
2026-03-17 18:51:26 +08:00
committed by GitHub
parent fc4f1e2488
commit 6cae364ac2
13 changed files with 199 additions and 24 deletions

View File

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

View File

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

View File

@@ -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: {

View File

@@ -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: '引用',

View File

@@ -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} />

View File

@@ -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}

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