import React from 'react'; import { Zap, AlertCircle, CheckCircle2, AlertTriangle, Clock, Bot, RefreshCw, ChevronDown, ChevronUp, Wrench, ShieldAlert, Wifi, Activity, Info } from 'lucide-react'; import type { InitialResponse, DevicePlan, RemediationRun } from '../../infrastructure/inspection/inspectionApi'; // ==================================================================== // 告警 AI 面板(响应包 + 预案 + 处置) // ==================================================================== interface Props { alertId: string; matchedPlan?: DevicePlan | null; initialResponse?: InitialResponse | null; remediation?: RemediationRun | null; onRegenerateResponse?: () => void; onStartRemediation?: () => void; onPauseRemediation?: () => void; onResumeRemediation?: () => void; onCancelRemediation?: () => void; isRemediationRunning?: boolean; } export default function AlertAIPanel({ alertId, matchedPlan, initialResponse, remediation, onRegenerateResponse, onStartRemediation, onPauseRemediation, onResumeRemediation, onCancelRemediation, }: Props) { return (
{/* 预案匹配 */} {matchedPlan && ( )} {/* AI 初始响应包 */} {initialResponse ? ( ) : matchedPlan ? (
AI 正在生成初始响应包…
) : null} {/* 处置执行区 */} {matchedPlan?.remediation?.steps?.length ? ( ) : null}
); } // -------------------------------------------------------------------- // 预案匹配徽章 // -------------------------------------------------------------------- function PlanMatchedBadge({ plan }: { plan: DevicePlan }) { const levelColors: Record = { critical: 'bg-red-500/15 text-red-400 border-red-500/30', warning: 'bg-amber-500/15 text-amber-400 border-amber-500/30', info: 'bg-sky-500/15 text-sky-400 border-sky-500/30', }; return (
匹配预案:{plan.description || plan.id}
{plan.escalation_fallback?.auto_escalate_minutes ? (
挂起 {plan.escalation_fallback.auto_escalate_minutes} 分钟自动升级
) : null}
); } // -------------------------------------------------------------------- // AI 初始响应包面板(可折叠) // -------------------------------------------------------------------- function ResponsePanel({ response, onRegenerate, }: { response: InitialResponse; onRegenerate?: () => void; }) { const [expanded, setExpanded] = React.useState(true); const [causesExpanded, setCausesExpanded] = React.useState(true); const statusColor = response.status?.includes('fallback') ? 'text-amber-400' : response.status?.includes('partial') ? 'text-amber-400' : 'text-emerald-400'; return (
{/* Header */}
setExpanded(!expanded)} > AI 初始响应包 {response.generator === 'fallback' ? '降级' : 'AI'} · {response.status?.replace(/_/g, ' ')} {onRegenerate && ( )} {expanded ? : }
{expanded && (
{/* 快速排查清单 */} {response.quick_checklist?.length > 0 && (
快速排查清单
{response.quick_checklist.map((item, i) => (
{item.priority || i + 1}
{item.action}
{item.purpose &&
{item.purpose}
}
))}
)} {/* 可能原因(仅 AI 生成有) */} {response.possible_causes?.length > 0 && (
setCausesExpanded(!causesExpanded)} > 可能原因 {causesExpanded ? : }
{causesExpanded && (
{response.possible_causes.map((cause, i) => (
{cause.label} {cause.hint && · {cause.hint}}
))}
)}
)} {/* 通知状态 */} {(response.auto_notifications_sent?.length > 0 || response.pending_notifications?.length > 0) && (
通知状态
{response.auto_notifications_sent.map((n, i) => ( {n.channel} ✓ ))} {response.pending_notifications.map((n: any, i: number) => ( {n.channel || 'pending'} · 待外部 ))}
)} {/* Warnings */} {response.warnings?.length > 0 && (
{response.warnings.map((w, i) => (
{w}
))}
)} {/* 生成时间 */}
{new Date(response.generated_at).toLocaleString()}
)}
); } function ProbabilityBadge({ p }: { p: string }) { const map: Record = { high: { cls: 'bg-red-500/20 text-red-400 border-red-500/40', label: '高' }, medium: { cls: 'bg-amber-500/20 text-amber-400 border-amber-500/40', label: '中' }, low: { cls: 'bg-slate-500/20 text-slate-400 border-slate-500/40', label: '低' }, }; const cfg = map[p] || map.low; return ( {cfg.label} ); } // -------------------------------------------------------------------- // 处置执行面板 // -------------------------------------------------------------------- function RemediationPanel({ alertId: _alertId, remediation, planSteps, onStart, onPause, onResume, onCancel, }: { alertId: string; remediation?: RemediationRun | null; planSteps: any[]; onStart?: () => void; onPause?: () => void; onResume?: () => void; onCancel?: () => void; }) { const statusMap: Record = { pending: { cls: 'text-slate-400', label: '待处置', icon: }, running: { cls: 'text-sky-400', label: '执行中', icon: }, completed: { cls: 'text-emerald-400', label: '完成', icon: }, partial: { cls: 'text-amber-400', label: '部分完成', icon: }, failed: { cls: 'text-red-400', label: '失败', icon: }, cancelled: { cls: 'text-slate-400', label: '已取消', icon: }, paused: { cls: 'text-amber-400', label: '已暂停', icon: }, }; const runStatus = remediation?.status || 'pending'; const cfg = statusMap[runStatus] || statusMap.pending; const steps = remediation?.steps?.length ? remediation.steps : planSteps.map((s: any, i: number) => ({ order: s.order || i + 1, command_id: s.command_id, execution: s.execution, status: 'pending', })); return (
{/* Header */}
处置执行 {cfg.icon} {cfg.label}
{/* Steps */}
{steps.map((step: any, i: number) => ( ))}
{/* Actions */}
{(runStatus === 'pending') && onStart && ( )} {runStatus === 'running' && onPause && ( )} {runStatus === 'paused' && onResume && ( )} {(runStatus === 'running' || runStatus === 'paused') && onCancel && ( )}
); } function StepRow({ step }: { step: any }) { const execBadge: Record = { auto: { cls: 'bg-emerald-500/15 text-emerald-400', label: '自动' }, confirm: { cls: 'bg-amber-500/15 text-amber-400', label: '确认' }, manual_only: { cls: 'bg-slate-500/15 text-slate-400', label: '手动' }, }; const stepStatusMap: Record = { pending: { dot: 'bg-slate-500', icon: , cls: 'text-slate-400' }, running: { dot: 'bg-sky-500 animate-pulse', icon: , cls: 'text-sky-400' }, success: { dot: 'bg-emerald-500', icon: , cls: 'text-emerald-400' }, failed: { dot: 'bg-red-500', icon: , cls: 'text-red-400' }, skipped: { dot: 'bg-slate-500', icon: , cls: 'text-slate-400' }, pending_manual: { dot: 'bg-amber-500', icon: , cls: 'text-amber-400' }, }; const ss = stepStatusMap[step.status || 'pending'] || stepStatusMap.pending; const eb = execBadge[step.execution] || execBadge.auto; return (
{ss.icon} #{step.order || '?'} {step.command_id || 'checklist'} {eb.label} {step.error && ✗}
); } // 懒加载这些避免循环 import import { Loader2, XCircle, Pause } from 'lucide-react';