"use client"; import { AlertTriangle, Bot, CalendarClock, CheckCircle2, ClipboardCheck, ClipboardList, Clock3, FileText, Lightbulb, ListChecks, Loader2, MessageSquare, Route, Send, ShieldAlert, Wrench } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; import { showMapNotice } from "@/features/map/core/components/notice"; import { getRunningWorkflowDefinition, getRunningWorkflowState, getWorkflowStepStatus } from "@/features/workbench/data/running-workflows"; import type { ScheduledConditionAnalysisInsight, ScheduledConditionItem, ScheduledConditionKpi, ScheduledConditionRecord, ScheduledConditionRiskLevel, ScheduledWorkOrderItem } from "@/features/workbench/types"; import { formatDuration, formatTime, formatTimeRange, getRiskLabel, getWorkOrderCurrentStage, getWorkOrderStagePillClassName, statusClassNames, statusLabels } from "./scheduled-condition-feed-utils"; import { getConditionReportRiskLevel } from "./condition-report-risk"; const RUNNING_WORKFLOW_TICK_MS = 1_000; const STATUS_PILL_CLASS_NAME = "rounded-full border px-2 py-0.5 text-xs font-semibold leading-4"; const NEUTRAL_SURFACE_CLASS_NAME = "rounded-xl border border-transparent bg-[var(--glass-readable)]"; const INSET_SURFACE_CLASS_NAME = "rounded-xl border border-slate-200/70 bg-[var(--glass-menu)]"; const REPORT_META_TILE_CLASS_NAME = "rounded-lg border border-slate-200/70 bg-[var(--glass-menu)] px-2 py-1.5"; const riskIndicatorClassNames: Record = { normal: "border-slate-200 bg-slate-50 text-slate-600", attention: "border-orange-200 bg-orange-50 text-orange-700", critical: "border-red-200 bg-red-50 text-red-700" }; type IncidentOperation = { target: string; elementType: string; action: string; command: string; verification: string; }; type SubmittedIncidentWorkOrder = { code: string; submittedAt: number; notifiedTeams: string[]; operations: IncidentOperation[]; }; type ExecutionStepStatus = "done" | "current" | "pending" | "confirmation"; type ExecutionBadgeTone = "active" | "confirmation" | "completed" | "pending"; type ExecutionStepMeta = { label: string; value: string; }; type ExecutionStep = { id: string; title: string; description: string; status: ExecutionStepStatus; meta?: ExecutionStepMeta[]; }; type ConditionDetailPanelProps = { condition: ScheduledConditionItem; onContinueConversation: () => void; }; export function StatusPill({ condition, className }: { condition: ScheduledConditionItem; className?: string; }) { const isWorkOrder = condition.kind === "work_order"; const currentStage = isWorkOrder ? getWorkOrderCurrentStage(condition) : null; return ( {isWorkOrder ? currentStage?.title ?? "工单" : statusLabels[condition.status]} ); } export function ConditionDetailPanel({ condition, onContinueConversation }: ConditionDetailPanelProps) { const detailKey = condition.kind === "work_order" ? `${condition.id}-${getWorkOrderCurrentStage(condition)?.id ?? condition.status}` : condition.id; const content = condition.kind === "work_order" ? ( ) : ( ); return ( {content} ); } function ConditionRecordDetailPanel({ condition, onContinueConversation }: { condition: ScheduledConditionRecord; onContinueConversation: () => void; }) { const insight = getConditionAnalysisInsight(condition); const [submittedWorkOrder, setSubmittedWorkOrder] = useState(null); const header = getConditionDetailHeader(condition); const riskLevel = getConditionReportRiskLevel(condition.status, condition.riskLevel); const bodyKey = `${condition.id}-${condition.status}-${condition.riskLevel}`; function handleSubmitIncidentWorkOrder() { const nextWorkOrder = createSubmittedIncidentWorkOrder(condition); setSubmittedWorkOrder(nextWorkOrder); showMapNotice({ id: `incident-work-order-${condition.id}`, tone: "success", title: "系统工单已提交", message: `${nextWorkOrder.code} 已通知 ${nextWorkOrder.notifiedTeams.join("、")}。` }); } return ( {condition.status === "running" ? ( ) : ( <> {riskLevel === "critical" ? ( <> ) : riskLevel === "attention" ? ( <> ) : ( <> )} )} ); } function WorkOrderDetailPanel({ condition }: { condition: ScheduledWorkOrderItem }) { const currentStage = getWorkOrderCurrentStage(condition); return ( ); } function RunningConditionBody({ condition }: { condition: ScheduledConditionRecord }) { const runningEvidence = (condition.evidence ?? []).slice(0, 2); const [nowMs, setNowMs] = useState(() => Date.now()); useEffect(() => { setNowMs(Date.now()); const interval = window.setInterval(() => { setNowMs(Date.now()); }, RUNNING_WORKFLOW_TICK_MS); return () => window.clearInterval(interval); }, [condition.id]); return ( <> ); } function getConditionDetailHeader(condition: ScheduledConditionRecord) { if (condition.status === "running") { return { icon: Loader2, iconClassName: "bg-blue-600 text-white", iconSpin: true }; } return { icon: FileText, iconClassName: undefined, iconSpin: false }; } function DetailScroll({ children }: { children: React.ReactNode }) { return (
{children}
); } type DetailShellProps = { children: React.ReactNode; }; function DetailShell({ children }: DetailShellProps) { return (
{children}
); } type DetailHeaderProps = { condition: ScheduledConditionItem; icon: typeof FileText; iconClassName?: string; iconSpin?: boolean; }; function DetailHeader({ condition, icon: Icon, iconClassName, iconSpin = false }: DetailHeaderProps) { return (
{formatTime(condition.scheduledAt)} {condition.kind === "work_order" || condition.status === "running" || condition.status === "pending" ? ( ) : null}

{condition.title}

{condition.detail ?

{condition.detail}

: null}
); } function DetailDecisionBody({ children }: { children: React.ReactNode }) { return (
{children}
); } function DecisionSummaryBlock({ icon: Icon, title, iconClassName, summary, notes }: { icon: typeof Bot; title: string; iconClassName: string; summary: string; notes: string[]; }) { return (

{summary}

    {notes.map((note) => (
  • ))}
); } function SolutionOptionsList({ options }: { options: NonNullable; }) { return (
{options.map((option, index) => (
{index + 1}

{option.title}

适用
{option.scenario}
动作
{option.action}
代价
{option.tradeoff}
))}
); } function IncidentWorkOrderPanel({ condition, submittedWorkOrder, onSubmitWorkOrder }: { condition: ScheduledConditionItem; submittedWorkOrder: SubmittedIncidentWorkOrder | null; onSubmitWorkOrder: () => void; }) { const operations = submittedWorkOrder?.operations ?? createIncidentOperations(condition); const submitted = Boolean(submittedWorkOrder); const steps: ExecutionStep[] = [ { id: "analysis", title: "事件分析", status: "done", description: "汇总异常证据、影响分区和风险等级。" }, { id: "solution", title: "形成方案", status: "done", description: "生成阀门、泵站和现场复核操作清单。" }, { id: "confirm", title: "人工确认", status: submitted ? "done" : "confirmation", description: "调度员确认影响范围和操作边界。" }, { id: "submit", title: "提交工单", status: submitted ? "done" : "pending", description: "写入系统工单并通知执行岗位。" } ]; return ( ); } function IncidentOperationList({ operations, submitted }: { operations: IncidentOperation[]; submitted: boolean; }) { return (
{operations.map((operation) => (
{submitted ? "已通知" : operation.elementType}
对象
{operation.target}
指令
{operation.command}
复核
{operation.verification}
))}
); } function EscalationCriteriaList({ items }: { items: string[] }) { return (
    {items.map((item, index) => (
  1. {index + 1} {item}
  2. ))}
); } function ConditionStatusReport({ condition, riskLevel }: { condition: ScheduledConditionRecord; riskLevel: ScheduledConditionRiskLevel; }) { if (!condition.report && !condition.kpis?.length) { return null; } const report = condition.report; const kpis = condition.kpis ?? []; const statusTime = formatTimeRange(condition.scheduledAt, condition.durationMinutes); return (

{report?.title ?? condition.title}

{report?.conclusion ? (

{report.conclusion}

) : null}
{kpis.length > 0 ? (
{kpis.map((kpi) => ( ))}
) : null} {report?.sections?.length ? (
{report.sections.map((section) => ( ))}
) : null}
); } function LifecyclePill() { return ( ); } function RiskBadge({ riskLevel }: { riskLevel: ScheduledConditionRiskLevel }) { const Icon = riskLevel === "critical" ? ShieldAlert : riskLevel === "attention" ? AlertTriangle : CheckCircle2; const label = getRiskLabel(riskLevel); return ( ); } function getRiskIndicatorClassName(riskLevel: ScheduledConditionRiskLevel) { return riskIndicatorClassNames[riskLevel]; } function ReportMetaTile({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function ReportSection({ title, items }: { title: string; items: string[]; }) { return (
{title}
    {items.map((item) => (
  • ))}
); } function KpiTile({ kpi }: { kpi: ScheduledConditionKpi }) { return (
{kpi.label}
{kpi.value} {kpi.unit ? {kpi.unit} : null}
{getRiskLabel(kpi.status)}

{kpi.description}

{kpi.threshold || kpi.baseline ? (
{kpi.threshold ?
阈值:{kpi.threshold}
: null} {kpi.baseline ?
基线:{kpi.baseline}
: null}
) : null}
); } function getKpiSurfaceClassName(riskLevel: ScheduledConditionRiskLevel) { if (riskLevel === "critical") { return "glass-danger border-red-200/80"; } if (riskLevel === "attention") { return "glass-warning border-orange-200/80"; } return "border-slate-200/70 bg-slate-50/80"; } function DecisionActionStrip({ icon: Icon, title, value, actionLabel, actionDisabled = false, actionTitle, onAction }: { icon: typeof Bot; title: string; value: string; actionLabel: string; actionDisabled?: boolean; actionTitle?: string; onAction?: () => void; }) { return (

{value}

); } function SectionEyebrow({ icon: Icon, label }: { icon?: typeof Bot; label: string; }) { return (
{Icon ?
); } function getTaskExecutionBadgeClassName(tone: ExecutionBadgeTone) { if (tone === "completed") { return "border-green-100 bg-green-50 text-green-700"; } if (tone === "confirmation") { return "border-orange-100 bg-orange-50 text-orange-700"; } if (tone === "active") { return "border-blue-100 bg-blue-50 text-blue-700"; } return "border-slate-200 bg-slate-50 text-slate-600"; } function getTaskExecutionStepIconClassName(status: ExecutionStepStatus) { if (status === "done") { return "bg-green-500 text-white"; } if (status === "current") { return "bg-blue-600 text-white"; } if (status === "confirmation") { return "bg-orange-500 text-white"; } return "bg-slate-100 text-slate-400"; } function getTaskExecutionStepSurfaceClassName(status: ExecutionStepStatus) { if (status === "current") { return "glass-info border-blue-100"; } if (status === "confirmation") { return "glass-warning border-orange-100"; } return "border-slate-200/70 bg-slate-50/80"; } function getTaskExecutionStepStatusClassName(status: ExecutionStepStatus) { if (status === "done") { return "border-green-100 bg-green-50 text-green-700"; } if (status === "current") { return "border-blue-100 bg-blue-50 text-blue-700"; } if (status === "confirmation") { return "border-orange-100 bg-orange-50 text-orange-700"; } return "border-slate-200 bg-white text-slate-500"; } function getTaskExecutionStepStatusLabel(status: ExecutionStepStatus) { if (status === "done") { return "已完成"; } if (status === "current") { return "当前"; } if (status === "confirmation") { return "待人工确认"; } return "待处理"; } function DetailMetricGrid({ condition }: { condition: ScheduledConditionItem }) { const durationLabel = condition.status === "running" ? "状态" : "耗时"; const durationValue = condition.status === "running" ? "实时执行" : formatDuration(condition.durationMinutes, condition.status); const metrics = condition.kind === "condition" ? [ { label: "模型", value: condition.modelName ?? "未记录" }, { label: "会话", value: condition.sessionId ?? "未记录" }, { label: durationLabel, value: durationValue, className: "col-span-2" } ] : [ { label: "类型", value: "预约工单" }, { label: "窗口", value: formatTimeRange(condition.scheduledAt, condition.durationMinutes) }, { label: "风险", value: getRiskLabel(condition.riskLevel) }, { label: durationLabel, value: durationValue } ]; return (
{metrics.map((metric) => (
))}
); } function RunningWorkflowPanel({ condition, nowMs }: { condition: ScheduledConditionRecord; nowMs: number; }) { const workflow = getRunningWorkflowDefinition(condition); const workflowState = getRunningWorkflowState(condition, workflow, nowMs); const currentStepIndex = workflowState.currentStepIndex; const currentStep = workflow.steps[currentStepIndex] ?? workflow.steps[0]; const nextStep = workflow.steps[currentStepIndex + 1]; const steps: ExecutionStep[] = workflow.steps.map((step, index) => ({ id: step.id, title: step.title, description: step.detail, status: getWorkflowStepStatus(condition, index, currentStepIndex) })); return ( ); } function SectionTitle({ icon: Icon, title, iconClassName }: { icon: typeof Bot; title: string; iconClassName?: string; }) { return (
); } function DetailSectionCard({ children, className }: { children: React.ReactNode; className?: string; }) { return (
{children}
); } function TaskExecutionPanel({ icon: Icon, title, badge, summary, meta, steps, badgeTone = "pending", children }: { icon: typeof Bot; title: string; badge: string; summary?: string; meta?: ExecutionStepMeta[]; steps: ExecutionStep[]; badgeTone?: ExecutionBadgeTone; children?: React.ReactNode; }) { return (
{badge}
{summary ?

{summary}

: null} {meta?.length ? (
{meta.map((item) => ( ))}
) : null}
    {steps.map((step, index) => ( ))}
{children ?
{children}
: null}
); } function TaskExecutionStepRow({ step, isLast }: { step: ExecutionStep; isLast: boolean; }) { return (
  • {!isLast ? ( {step.title} {getTaskExecutionStepStatusLabel(step.status)} {step.meta?.length ? ( {step.meta.map((item) => ( {item.label} {item.value} ))} ) : null} {step.description}
  • ); } function WorkOrderLifecyclePanel({ condition }: { condition: ScheduledWorkOrderItem }) { const duration = formatDuration(condition.durationMinutes, condition.status); const currentStage = getWorkOrderCurrentStage(condition); const steps = condition.stages.map((stage) => ({ id: stage.id, title: stage.title, status: stage.status, description: stage.description, meta: [ { label: "责任人", value: stage.owner }, { label: "时间", value: stage.timeLabel } ] })); return ( ); } function WorkOrderReplyRequirements({ condition }: { condition: ScheduledWorkOrderItem }) { return (
    执行窗口 {formatTimeRange(condition.scheduledAt, condition.durationMinutes)} 复令时限 执行完成后 {condition.replyWindowMinutes} 分钟内
      {condition.replyRequirements.map((item, index) => (
    • {index + 1} {item}
    • ))}
    ); } function RunningEvidencePreview({ items }: { items: string[] }) { return (
      {items.length > 0 ? ( items.map((item, index) => (
    1. {index + 1} {item} 采集中
    2. )) ) : (
    3. 正在等待首批证据返回。
    4. )}
    ); } function EvidenceList({ items }: { items: string[] }) { return (
      {items.length > 0 ? ( items.map((item, index) => (
    1. {index + 1} {item}
    2. )) ) : (
    3. 暂无关键证据记录。
    4. )}
    ); } function createSubmittedIncidentWorkOrder(condition: ScheduledConditionItem): SubmittedIncidentWorkOrder { const operations = createIncidentOperations(condition); const submittedAt = Date.now(); const codeSeed = `${condition.id}-${submittedAt}`.replace(/[^a-zA-Z0-9]/g, "").slice(-8).toUpperCase(); return { code: `INC-WO-${codeSeed}`, submittedAt, notifiedTeams: ["调度执行岗", "现场抢修班组", "模型复核岗"], operations }; } function createIncidentOperations(condition: ScheduledConditionItem): IncidentOperation[] { const baseVerification = condition.status === "error" ? "执行后 5 分钟内复核压力、流量和影响用户反馈。" : "下一轮工况继续跟踪偏差是否回落。"; if (condition.title.includes("智能调度")) { return [ { target: "华山路沿线阀门组 VG-11", elementType: "阀门", action: "阀门开度调整", command: "按方案将边界阀门预置到 45% 开度,执行前确认上下游压力。", verification: "确认高区末端压力不低于 0.22MPa。" }, { target: "北辰二级泵站 P-02", elementType: "泵站", action: "泵站出水复核", command: "保持当前泵组组合,复核出水压力和频率波动。", verification: baseVerification } ]; } if (condition.title.includes("SCADA")) { return [ { target: "SCADA 异常测点组", elementType: "测点", action: "异常测点隔离", command: "调度计算暂不采用异常测点,使用相邻测点插补边界。", verification: "数据维护岗确认通信链路恢复后再解除隔离。" }, { target: "异常 DMA 现场巡检点", elementType: "巡检点", action: "现场复核", command: "通知巡检班组核对压力表、流量计和阀井状态。", verification: baseVerification } ]; } return [ { target: "疑似影响区上下游阀门", elementType: "阀门", action: "阀门边界确认", command: "核对阀门当前开度,暂不执行大范围关断,保留应急隔离方案。", verification: "确认影响区压力恢复趋势和用户侧反馈。" }, { target: "关联泵站与分区边界", elementType: "泵站/边界", action: "调度边界复核", command: "使用最新泵站出水、阀门开度和 SCADA 流量刷新模拟边界。", verification: baseVerification } ]; } function getConditionAnalysisInsight(condition: ScheduledConditionItem): ScheduledConditionAnalysisInsight { if (condition.kind === "condition" && condition.analysisInsight) { return condition.analysisInsight; } if (condition.status === "error") { return { summary: "Agent 判断这条工况未完整闭环,需要先确认异常来源,再决定是否进入调度处置。", notes: [ "优先核对遥测源新鲜度和关键测点完整率。", "结合历史基线复核压力/流量偏差是否持续。", "处置前应先确认影响范围,避免误触发联动动作。" ], solutionOptions: [ { id: "fallback-source", title: "数据源复核", scenario: "遥测源超时或数据完整率不足。", action: "重试关键测点并标记缺失源。", tradeoff: "判断更稳妥,但会延后处置。" }, { id: "fallback-model", title: "模型比对", scenario: "压力或流量偏差集中在局部分区。", action: "对比基线工况并圈定疑似影响范围。", tradeoff: "响应更快,但依赖模型边界条件质量。" }, { id: "fallback-manual", title: "人工复核", scenario: "异常持续或伴随用户侧反馈。", action: "交由调度员确认阀门、泵站和巡检动作。", tradeoff: "闭环完整,但会占用人工资源。" } ] }; } return { summary: "Agent 将这条工况标记为关注项,当前偏差不足以触发自动处置。", notes: [ "保留当前证据,下一轮继续比对同一测点趋势。", "重点观察偏差是否扩大,避免单点噪声误判。", "需要时可在 Agent 面板追问偏差来源。" ], escalationCriteria: [ "连续两轮工况仍高于关注阈值。", "关键测点完整率继续下降。", "同一分区出现用户投诉、低压或水质联动线索。" ] }; }