'use client' /** * NeuralCommandPanel — 神經指揮中心面板 (不含 AppLayout) * ========================================================= * Sprint 5: 從 /neural-command/page.tsx 抽取 * 供原始頁面和整合頁面 (/automation) 共用 * * 建立時間: 2026-04-09 (台北時區) */ import { useState, useEffect, useCallback } from 'react' import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' import { BrainCircuit, ShieldCheck, RefreshCw, Clock, Database, Activity, Lock, } from 'lucide-react' import { NeuralPreFlight } from '@/components/neural-command/NeuralPreFlight' import { NeuralLiveCenter } from '@/components/neural-command/NeuralLiveCenter' import { NeuralStats } from '@/components/neural-command/NeuralStats' import { NeuralApprovalPanel } from '@/components/neural-command/NeuralApprovalPanel' import type { AutoRepairStats, PlaybookItem, RepairHistoryItem, NeuralTab, PendingApprovalItem, ActiveIncident } from '@/components/neural-command/types' const TABS: { id: NeuralTab; labelKey: string; Icon: React.ElementType }[] = [ { id: 'preflight', labelKey: 'preFlightAudit', Icon: ShieldCheck }, { id: 'live', labelKey: 'liveCommand', Icon: Activity }, { id: 'stats', labelKey: 'statsHistory', Icon: Database }, { id: 'approval', labelKey: 'nuclearApproval', Icon: Lock }, ] export function NeuralCommandPanel() { const t = useTranslations('neuralCommand') const [activeTab, setActiveTab] = useState('preflight') const [stats, setStats] = useState(null) const [playbooks, setPlaybooks] = useState([]) const [history, setHistory] = useState([]) const [loading, setLoading] = useState(true) const [lastRefresh, setLastRefresh] = useState(new Date()) const [pendingApprovals, setPendingApprovals] = useState(0) const [pendingApprovalList, setPendingApprovalList] = useState([]) const [activeIncidents, setActiveIncidents] = useState([]) const [dispositionSummary, setDispositionSummary] = useState<{ total: number; auto_repair: number; human_approved: number; manual_resolved: number; cold_start_trust: number; auto_rate: number } | null>(null) const fetchData = useCallback(async () => { try { const [statsRes, pbRes, histRes, approvalsRes, incidentsRes, dispRes] = await Promise.all([ fetch('/api/v1/auto-repair/stats'), fetch('/api/v1/playbooks/'), fetch('/api/v1/auto-repair/history?limit=20'), fetch('/api/v1/approvals/pending'), fetch('/api/v1/incidents?status=firing&limit=10'), fetch('/api/v1/stats/disposition').catch(() => null), ]) if (statsRes.ok) { const data = await statsRes.json() setStats(data) } if (pbRes.ok) { const data = await pbRes.json() setPlaybooks(data.items?.map((i: { playbook: PlaybookItem }) => i.playbook) ?? []) } if (histRes.ok) { const data = await histRes.json() setHistory(data.items ?? []) } if (approvalsRes.ok) { const data = await approvalsRes.json() setPendingApprovals(data.count ?? 0) setPendingApprovalList(data.approvals ?? []) } if (incidentsRes.ok) { const data = await incidentsRes.json() setActiveIncidents(data.incidents ?? []) } if (dispRes?.ok) { const data = await dispRes.json() setDispositionSummary(data.summary ?? null) } setLastRefresh(new Date()) } catch { // silently fail — show stale data } finally { setLoading(false) } }, []) useEffect(() => { fetchData() const interval = setInterval(fetchData, 30_000) return () => clearInterval(interval) }, [fetchData]) const approvedPlaybooks = playbooks.filter(p => p.status === 'approved') return (
{/* Page header */}

{t('title')}

{t('subtitle')}

OpenClaw NemoTron
{t('lastRefresh', { time: lastRefresh.toLocaleTimeString('zh-TW', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) })}
{/* Tabs */}
{TABS.map(({ id, labelKey, Icon }) => ( ))}
{/* Tab content */}
{activeTab === 'preflight' && ( )} {activeTab === 'live' && ( )} {activeTab === 'stats' && ( )} {activeTab === 'approval' && ( )}
) }