From 4dfd6ce11f4ad2121f0120624418f16e7e4bc8e0 Mon Sep 17 00:00:00 2001 From: DBT Date: Sat, 28 Feb 2026 02:34:20 +0000 Subject: [PATCH] webui: add bilingual task audit split-view page and backend task audit API; final-result only long-run flow --- webui/src/App.tsx | 2 + webui/src/components/Sidebar.tsx | 3 +- webui/src/i18n/index.ts | 12 ++++ webui/src/pages/TaskAudit.tsx | 118 +++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 webui/src/pages/TaskAudit.tsx 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}
+
Status
{selected.status}
+
Duration
{selected.duration_ms || 0}ms
+
Channel
{selected.channel}
+
Session
{selected.session}
+
Time
{selected.time}
+
+ +
+
Input Preview
+
{selected.input_preview || '-'}
+
+ +
+
Error
+
{selected.error || '-'}
+
+ +
+
Raw JSON
+
{selectedPretty}
+
+ + )} +
+
+
+
+ ); +}; + +export default TaskAudit;