P0: Neural Command 三個子組件移除所有 MOCK 常數,接上真實 API props - NeuralLiveCenter: 假歷史/假KPI/假雷達 → 從 stats/history/incidents 即時計算 - NeuralStats: MOCK_HISTORY/SCHEME_STATS/PLAYBOOK_RANKINGS → useMemo 聚合 - NeuralApprovalPanel: MOCK_PENDING → 真實 /api/v1/approvals 簽核操作 P1: 10+處假用戶身份 (demo-user/user-001/War Room User) → CURRENT_USER 常數統一 P2: 刪除 6 個 Demo 匯出 (GlobalPulseChartDemo/MOCK_APPROVAL/DEMO_DECISION_CHAIN) P3: /demo 頁面加 NEXT_PUBLIC_ENABLE_DEMO 環境變數保護 i18n: 新增 22 個翻譯鍵 (zh-TW + en) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* AICommandPanel - 戰情室右側 AI 指揮面板
|
|
* ==========================================
|
|
* Phase 1: OpenClaw + HITL 授權卡片整合
|
|
*
|
|
* Features:
|
|
* - OpenClaw AI 視覺化 (頂部)
|
|
* - 待授權卡片列表 (底部)
|
|
* - **Phase 15: SSE 即時更新** (取代輪詢)
|
|
* - Nothing.tech 純白極簡風格
|
|
*
|
|
* i18n: 100% next-intl
|
|
*/
|
|
|
|
import { useTranslations } from 'next-intl'
|
|
import { cn } from '@/lib/utils'
|
|
import { OpenClawPanel } from './openclaw-panel'
|
|
import { ApprovalCard } from '@/components/approval/approval-card'
|
|
import {
|
|
useApprovalStore,
|
|
usePendingApprovals,
|
|
toFrontendApproval,
|
|
} from '@/stores/approval.store'
|
|
import { useApprovalSSE } from '@/hooks/useApprovalSSE'
|
|
import { ShieldCheck, Bell } from 'lucide-react'
|
|
import { CURRENT_USER } from '@/lib/constants'
|
|
|
|
// =============================================================================
|
|
// Props
|
|
// =============================================================================
|
|
|
|
interface AICommandPanelProps {
|
|
className?: string
|
|
}
|
|
|
|
// =============================================================================
|
|
// Component
|
|
// =============================================================================
|
|
|
|
export function AICommandPanel({ className }: AICommandPanelProps) {
|
|
const _t = useTranslations()
|
|
const tApproval = useTranslations('approval')
|
|
|
|
// Store
|
|
const { fetchPending, signApproval, rejectApproval } = useApprovalStore()
|
|
const pendingApprovals = usePendingApprovals()
|
|
|
|
// Phase 15: SSE 即時更新 (取代輪詢)
|
|
useApprovalSSE({ autoConnect: true })
|
|
|
|
// Handle approval
|
|
const handleApprove = async (id: string) => {
|
|
await signApproval(id, CURRENT_USER.id, CURRENT_USER.name, 'Approved via Command Center')
|
|
await fetchPending()
|
|
}
|
|
|
|
// Handle rejection
|
|
const handleReject = async (id: string) => {
|
|
await rejectApproval(id, CURRENT_USER.id, CURRENT_USER.name, 'Rejected via Command Center')
|
|
await fetchPending()
|
|
}
|
|
|
|
return (
|
|
<div className={cn('flex flex-col gap-4', className)}>
|
|
{/* OpenClaw AI Section */}
|
|
<OpenClawPanel status="patrolling" />
|
|
|
|
{/* Pending Approvals Section */}
|
|
{pendingApprovals.length > 0 && (
|
|
<div className="space-y-3">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between px-1">
|
|
<div className="flex items-center gap-2">
|
|
<ShieldCheck className="w-4 h-4 text-claw-blue" />
|
|
<span className="font-body text-xs text-nothing-gray-600">
|
|
{tApproval('pendingApprovals')}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-1.5">
|
|
<Bell className="w-3 h-3 text-status-warning animate-pulse" />
|
|
<span className="font-body text-xs font-bold text-status-warning">
|
|
{pendingApprovals.length}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Approval Cards */}
|
|
<div className="space-y-3 max-h-[400px] overflow-y-auto pr-1 scrollbar-thin">
|
|
{pendingApprovals.map((approval) => {
|
|
const frontendApproval = toFrontendApproval(approval)
|
|
return (
|
|
<ApprovalCard
|
|
key={approval.id}
|
|
request={frontendApproval}
|
|
onApprove={handleApprove}
|
|
onReject={handleReject}
|
|
holdDuration={approval.risk_level === 'critical' ? 2000 : 1000}
|
|
/>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Empty State - No pending approvals */}
|
|
{pendingApprovals.length === 0 && (
|
|
<div className="flex flex-col items-center justify-center py-6 text-center">
|
|
<ShieldCheck className="w-8 h-8 text-nothing-gray-300 mb-2" />
|
|
<p className="font-body text-xs text-nothing-gray-400">
|
|
{tApproval('noApprovals')}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default AICommandPanel
|