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) => (
))}
)}
{/* 生成时间 */}
{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';