import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { RefreshCw } from 'lucide-react'; import ArtifactPreviewCard from '../components/data-display/ArtifactPreviewCard'; import { useAppContext } from '../context/AppContext'; import { dataUrlForArtifact, formatArtifactBytes } from '../utils/artifacts'; import CodeBlockPanel from '../components/data-display/CodeBlockPanel'; import DetailGrid from '../components/data-display/DetailGrid'; import EmptyState from '../components/data-display/EmptyState'; import { FixedButton } from '../components/ui/Button'; import InfoBlock from '../components/data-display/InfoBlock'; import ListPanel from '../components/layout/ListPanel'; import PageHeader from '../components/layout/PageHeader'; import PanelHeader from '../components/layout/PanelHeader'; import { SelectField } from '../components/ui/FormControls'; import SummaryListItem from '../components/list/SummaryListItem'; import ToolbarRow from '../components/layout/ToolbarRow'; import { formatLocalDateTime } from '../utils/time'; type TaskAuditItem = { task_id?: string; time?: string; channel?: string; session?: string; chat_id?: string; sender_id?: string; status?: string; source?: string; idle_run?: boolean; block_reason?: string; last_pause_reason?: string; last_pause_at?: string; duration_ms?: number; retry_count?: number; attempts?: number; error?: string; provider?: string; model?: string; input_preview?: string; logs?: string[]; media_items?: Array<{ source?: string; type?: string; ref?: string; path?: string; channel?: string }>; [key: string]: any; }; type NodeDispatchItem = { time?: string; node?: string; action?: string; ok?: boolean; used_transport?: string; fallback_from?: string; duration_ms?: number; error?: string; artifact_count?: number; artifact_kinds?: string[]; artifacts?: any[]; [key: string]: any; }; const TaskAudit: React.FC = () => { const { t } = useTranslation(); const { q } = useAppContext(); const [items, setItems] = useState([]); const [selected, setSelected] = useState(null); const [nodeItems, setNodeItems] = useState([]); const [selectedNode, setSelectedNode] = useState(null); const [loading, setLoading] = useState(false); const [sourceFilter, setSourceFilter] = useState('all'); const [statusFilter, setStatusFilter] = useState('all'); const fetchData = async () => { setLoading(true); try { const taskURL = `/webui/api/task_queue${q ? `${q}&limit=300` : '?limit=300'}`; const nodeURL = `/webui/api/node_dispatches${q ? `${q}&limit=150` : '?limit=150'}`; const [taskResp, nodeResp] = await Promise.all([fetch(taskURL), fetch(nodeURL)]); if (!taskResp.ok) throw new Error(await taskResp.text()); if (!nodeResp.ok) throw new Error(await nodeResp.text()); const taskJSON = await taskResp.json(); const nodeJSON = await nodeResp.json(); const arr = Array.isArray(taskJSON.items) ? taskJSON.items : []; const sorted = arr.sort((a: any, b: any) => String(b.time || '').localeCompare(String(a.time || ''))); setItems(sorted); if (sorted.length > 0) setSelected(sorted[0]); const nodeArr = Array.isArray(nodeJSON.items) ? nodeJSON.items : []; setNodeItems(nodeArr); if (nodeArr.length > 0) setSelectedNode(nodeArr[0]); } catch (e) { console.error(e); setItems([]); setSelected(null); setNodeItems([]); setSelectedNode(null); } finally { setLoading(false); } }; useEffect(() => { fetchData(); }, [q]); const filteredItems = useMemo(() => items.filter((it) => { if (sourceFilter !== 'all' && String(it.source || '-') !== sourceFilter) return false; if (statusFilter !== 'all' && String(it.status || '-') !== statusFilter) return false; return true; }), [items, sourceFilter, statusFilter]); const selectedPretty = useMemo(() => selected ? JSON.stringify(selected, null, 2) : '', [selected]); return (
setSourceFilter(e.target.value)}> setStatusFilter(e.target.value)}> )} />
{filteredItems.length === 0 ? ( ) : filteredItems.map((it, idx) => { const active = selected?.task_id === it.task_id && selected?.time === it.time; return ( setSelected(it)} active={active} title={it.task_id || `task-${idx + 1}`} subtitle={`${it.channel || '-'} · ${it.status} · attempts:${it.attempts || 1} · ${it.duration_ms || 0}ms · retry:${it.retry_count || 0} · ${it.source || '-'} · ${it.provider || '-'} / ${it.model || '-'}`} meta={formatLocalDateTime(it.time)} /> ); })}
{!selected ? ( ) : ( <> {selected.input_preview || '-'} {selected.error || '-'} {selected.block_reason || '-'}
{selected.last_pause_reason || '-'} {formatLocalDateTime(selected.last_pause_at)}
{Array.isArray(selected.logs) && selected.logs.length ? selected.logs.join('\n') : '-'} {Array.isArray(selected.media_items) && selected.media_items.length > 0 ? (
{selected.media_items.map((m, i) => (
[{m.channel || '-'}] {m.source || '-'} / {m.type || '-'} · {m.path || m.ref || '-'}
))}
) : '-'}
{selectedPretty}
)}
{nodeItems.length === 0 ? ( ) : nodeItems.map((it, idx) => { const active = selectedNode?.time === it.time && selectedNode?.node === it.node && selectedNode?.action === it.action; return ( setSelectedNode(it)} active={active} title={`${it.node || '-'} · ${it.action || '-'}`} subtitle={`${it.used_transport || '-'} · ${(it.duration_ms || 0)}ms · ${(it.artifact_count || 0)} ${t('dashboardNodeDispatchArtifacts')}`} meta={formatLocalDateTime(it.time)} /> ); })}
{!selectedNode ? ( ) : ( <> {selectedNode.error || '-'}
{t('dashboardNodeDispatchArtifactPreview')}
{Array.isArray(selectedNode.artifacts) && selectedNode.artifacts.length > 0 ? selectedNode.artifacts.map((artifact, artifactIndex) => { const dataUrl = dataUrlForArtifact(artifact); return ( ); }) : ( )}
{JSON.stringify(selectedNode, null, 2)} )}
); }; export default TaskAudit;