diff --git a/webui/src/App.tsx b/webui/src/App.tsx
index 78b9ccc..935a941 100644
--- a/webui/src/App.tsx
+++ b/webui/src/App.tsx
@@ -11,6 +11,7 @@ import Nodes from './pages/Nodes';
import Logs from './pages/Logs';
import Skills from './pages/Skills';
import Memory from './pages/Memory';
+import TaskAudit from './pages/TaskAudit';
export default function App() {
return (
@@ -27,6 +28,7 @@ export default function App() {
} />
} />
} />
+ } />
diff --git a/webui/src/components/Sidebar.tsx b/webui/src/components/Sidebar.tsx
index c20e75b..e220102 100644
--- a/webui/src/components/Sidebar.tsx
+++ b/webui/src/components/Sidebar.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { LayoutDashboard, MessageSquare, Settings, Clock, Server, Terminal, Zap, FolderOpen } from 'lucide-react';
+import { LayoutDashboard, MessageSquare, Settings, Clock, Server, Terminal, Zap, FolderOpen, ClipboardList } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useAppContext } from '../context/AppContext';
import NavItem from './NavItem';
@@ -21,6 +21,7 @@ const Sidebar: React.FC = () => {
} label={t('cronJobs')} to="/cron" />
} label={t('nodes')} to="/nodes" />
} label={t('memory')} to="/memory" />
+ } label={t('taskAudit')} to="/task-audit" />
diff --git a/webui/src/i18n/index.ts b/webui/src/i18n/index.ts
index 8314692..d2e2454 100644
--- a/webui/src/i18n/index.ts
+++ b/webui/src/i18n/index.ts
@@ -13,6 +13,12 @@ const resources = {
logs: 'Real-time Logs',
skills: 'Skills',
memory: 'Memory',
+ taskAudit: 'Task Audit',
+ taskList: 'Task List',
+ taskDetail: 'Task Detail',
+ noTaskAudit: 'No task audit records',
+ selectTask: 'Select a task from the left list',
+ loading: 'Loading...',
gatewayStatus: 'Gateway Status',
online: 'Online',
offline: 'Offline',
@@ -156,6 +162,12 @@ const resources = {
logs: '实时日志',
skills: '技能管理',
memory: '记忆文件',
+ taskAudit: '任务审计',
+ taskList: '任务列表',
+ taskDetail: '任务详情',
+ noTaskAudit: '暂无任务审计记录',
+ selectTask: '请从左侧选择任务',
+ loading: '加载中...',
gatewayStatus: '网关状态',
online: '在线',
offline: '离线',
diff --git a/webui/src/pages/TaskAudit.tsx b/webui/src/pages/TaskAudit.tsx
new file mode 100644
index 0000000..87ffc0f
--- /dev/null
+++ b/webui/src/pages/TaskAudit.tsx
@@ -0,0 +1,118 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useAppContext } from '../context/AppContext';
+
+type TaskAuditItem = {
+ task_id?: string;
+ time?: string;
+ channel?: string;
+ session?: string;
+ chat_id?: string;
+ sender_id?: string;
+ status?: string;
+ duration_ms?: number;
+ error?: string;
+ input_preview?: string;
+ [key: string]: any;
+};
+
+const TaskAudit: React.FC = () => {
+ const { t } = useTranslation();
+ const { q } = useAppContext();
+ const [items, setItems] = useState
([]);
+ const [selected, setSelected] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ const fetchData = async () => {
+ setLoading(true);
+ try {
+ const url = `/webui/api/task_audit${q ? `${q}&limit=300` : '?limit=300'}`;
+ const r = await fetch(url);
+ if (!r.ok) throw new Error(await r.text());
+ const j = await r.json();
+ const arr = Array.isArray(j.items) ? j.items : [];
+ setItems(arr.reverse());
+ if (arr.length > 0) setSelected(arr[arr.length - 1]);
+ } catch (e) {
+ console.error(e);
+ setItems([]);
+ setSelected(null);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => { fetchData(); }, [q]);
+
+ const selectedPretty = useMemo(() => selected ? JSON.stringify(selected, null, 2) : '', [selected]);
+
+ return (
+
+
+
{t('taskAudit')}
+
+
+
+
+
+
{t('taskList')}
+
+ {items.length === 0 ? (
+
{t('noTaskAudit')}
+ ) : items.map((it, idx) => {
+ const active = selected?.task_id === it.task_id && selected?.time === it.time;
+ return (
+
+ );
+ })}
+
+
+
+
+
{t('taskDetail')}
+
+ {!selected ? (
+
{t('selectTask')}
+ ) : (
+ <>
+
+
Task ID
{selected.task_id}
+
+
Duration
{selected.duration_ms || 0}ms
+
Channel
{selected.channel}
+
Session
{selected.session}
+
+
+
+
+
Input Preview
+
{selected.input_preview || '-'}
+
+
+
+
Error
+
{selected.error || '-'}
+
+
+
+
Raw JSON
+
{selectedPretty}
+
+ >
+ )}
+
+
+
+
+ );
+};
+
+export default TaskAudit;