import ReactECharts from "echarts-for-react"; import { Activity, AlertTriangle, Check, ChevronRight, Clock3, Database, FileSearch, Focus, Map as MapIcon, Minimize2, PanelRightClose, PanelRightOpen, ShieldCheck, Sparkles, Trash2, X } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { showMapNotice } from "@/features/map/core"; import { cn } from "@/shared/ui/cn"; import type { AnalysisArtifact, AnalysisArtifactAction, AnalysisBlock, AnalysisMetric, ArtifactRequestedView, WorkbenchSurfaceMode } from "./workspace-model"; type AnalysisArtifactWorkspaceProps = { artifact: AnalysisArtifact; surfaceMode: WorkbenchSurfaceMode; mapSplitAvailable: boolean; onSetView: (view: ArtifactRequestedView) => void; onCollapse: () => void; onDestroy: () => void; }; export function AnalysisArtifactWorkspace({ artifact, surfaceMode, mapSplitAvailable, onSetView, onCollapse, onDestroy }: AnalysisArtifactWorkspaceProps) { const [selectedBlockId, setSelectedBlockId] = useState(null); const metricBlock = artifact.blocks.find((block) => block.kind === "metric-grid"); const evidenceBlocks = artifact.blocks.filter((block) => block.kind !== "metric-grid"); const selectedBlock = useMemo( () => artifact.blocks.find((block) => block.id === selectedBlockId) ?? null, [artifact.blocks, selectedBlockId] ); useEffect(() => { setSelectedBlockId(null); }, [artifact.id]); useEffect(() => { if (!selectedBlock) return; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") setSelectedBlockId(null); }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [selectedBlock]); return (

Summary

结论摘要

{artifact.summary}

{metricBlock?.kind === "metric-grid" ? (
{metricBlock.metrics.map((metric) => )}
) : null}

Evidence canvas

证据画布

{evidenceBlocks.length} 个证据模块
{evidenceBlocks.map((block) => ( setSelectedBlockId(block.id)} /> ))}
setSelectedBlockId((current) => current ? null : evidenceBlocks[0]?.id ?? null)} onSetView={onSetView} onCollapse={onCollapse} onDestroy={onDestroy} /> {selectedBlock ? ( setSelectedBlockId(null)} /> ) : null}
); } function ArtifactHeader({ artifact }: { artifact: AnalysisArtifact }) { return (
{artifactTypeLabel(artifact.type)} R{artifact.revision}

{artifact.title}

{artifact.subtitle}

范围 {artifact.scope} 置信度 {artifact.confidence} 地图关系 {mapRelationLabel(artifact.mapRelation)}
); } function AnalysisBlockView({ block, selected, onSelect }: { block: Exclude; selected: boolean; onSelect: () => void; }) { const spanClass = block.span === 5 ? "col-span-12 2xl:col-span-5" : block.span === 7 ? "col-span-12 2xl:col-span-7" : "col-span-12"; return (
); } function AnalysisBlockBody({ block }: { block: Exclude }) { if (block.kind === "chart") { return (
); } if (block.kind === "process-flow") { return (
    {block.nodes.map((node, index) => (
  1. {index < block.nodes.length - 1 ?
  2. ))}
); } if (block.kind === "data-table") { return (
{block.columns.map((column) => )} {block.rows.map((row, index) => ( {block.columns.map((column) => )} ))}
{column.label}
{row[column.key]}
); } return
{block.paragraphs.map((paragraph) =>

{paragraph}

)}
; } function ArtifactProvenance({ artifact }: { artifact: AnalysisArtifact }) { return (

    {artifact.sources.map((source) => (
  • {source}
  • ))}

    {artifact.limitations.map((item) => (
  • {item}
  • ))}
); } function ArtifactDetailDrawer({ artifact, block, onClose }: { artifact: AnalysisArtifact; block: AnalysisBlock; onClose: () => void; }) { const blockFacts = getEvidenceBlockFacts(block); return ( ); } function evidenceBlockKindLabel(block: AnalysisBlock) { if (block.kind === "chart") return block.chartType === "line" ? "趋势图" : "对比图"; if (block.kind === "process-flow") return "流程证据"; if (block.kind === "data-table") return "明细数据"; if (block.kind === "narrative") return "分析说明"; return "指标摘要"; } function getEvidenceBlockFacts(block: AnalysisBlock): Array<{ label: string; value: string }> { if (block.kind === "chart") { return [ { label: "数据序列", value: `${block.series.length} 组` }, { label: "时间 / 类别点", value: `${block.categories.length} 个` }, { label: "计量单位", value: block.unit }, { label: "图表类型", value: block.chartType === "line" ? "趋势曲线" : "对比柱图" } ]; } if (block.kind === "data-table") { return [ { label: "证据记录", value: `${block.rows.length} 条` }, { label: "数据字段", value: `${block.columns.length} 个` }, { label: "展示方式", value: "明细表格" }, { label: "证据状态", value: "已纳入结论" } ]; } if (block.kind === "process-flow") { return [ { label: "流程步骤", value: `${block.nodes.length} 项` }, { label: "已完成", value: `${block.nodes.filter((node) => node.status === "complete").length} 项` }, { label: "当前进行", value: `${block.nodes.filter((node) => node.status === "active").length} 项` }, { label: "展示方式", value: "处置流程" } ]; } if (block.kind === "narrative") { return [ { label: "分析段落", value: `${block.paragraphs.length} 段` }, { label: "展示方式", value: "分析说明" }, { label: "证据状态", value: "已纳入结论" }, { label: "内容来源", value: "Agent 生成" } ]; } return [ { label: "关键指标", value: `${block.metrics.length} 项` }, { label: "展示方式", value: "指标摘要" }, { label: "证据状态", value: "已纳入结论" }, { label: "内容来源", value: "Agent 生成" } ]; } function formatAnalyticalTimeRange(range: AnalysisArtifact["analyticalTimeRange"]) { const formatter = new Intl.DateTimeFormat("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false }); return `${formatter.format(new Date(range.start))} 至 ${formatter.format(new Date(range.end))}`; } function ArtifactActionBar({ artifact, surfaceMode, mapSplitAvailable, detailOpen, onToggleDetail, onSetView, onCollapse, onDestroy }: { artifact: AnalysisArtifact; surfaceMode: WorkbenchSurfaceMode; mapSplitAvailable: boolean; detailOpen: boolean; onToggleDetail: () => void; onSetView: (view: ArtifactRequestedView) => void; onCollapse: () => void; onDestroy: () => void; }) { return (
{artifact.mapRelation !== "none" ? ( ) : null}
{artifact.actions.slice(0, 1).map((action) => )}
); } function ArtifactPrimaryAction({ action, artifact }: { action: AnalysisArtifactAction; artifact: AnalysisArtifact }) { return ( ); } function MetricTile({ metric }: { metric: AnalysisMetric }) { return (

{metric.value}

{metric.detail}

); } function LifecycleBadge({ lifecycle }: { lifecycle: AnalysisArtifact["lifecycle"] }) { const labels = { draft: "草稿", reviewed: "已复核", published: "已发布", archived: "已归档" } as const; return {labels[lifecycle]}; } function artifactTypeLabel(type: AnalysisArtifact["type"]) { if (type === "diagnosis") return "异常诊断"; if (type === "simulation") return "方案模拟"; return "指标分析"; } function mapRelationLabel(relation: AnalysisArtifact["mapRelation"]) { if (relation === "required") return "强关联"; if (relation === "optional") return "可选"; return "无空间依赖"; } function createChartOption(block: Extract) { return { animationDuration: 260, animationEasing: "cubicOut", color: block.series.map((series) => series.color), grid: { top: 40, right: 18, bottom: 34, left: 52, containLabel: false }, legend: { top: 0, right: 0, itemWidth: 12, itemHeight: 7, textStyle: { color: "#475569", fontSize: 11 } }, tooltip: { trigger: "axis", confine: true, borderWidth: 0, backgroundColor: "rgba(15,23,42,0.92)", textStyle: { color: "#f8fafc", fontSize: 12 } }, xAxis: { type: "category", data: block.categories, boundaryGap: block.chartType === "bar", axisLine: { lineStyle: { color: "#cbd5e1" } }, axisTick: { show: false }, axisLabel: { color: "#64748b", fontSize: 11 } }, yAxis: { type: "value", name: block.unit, nameTextStyle: { color: "#64748b", fontSize: 11, padding: [0, 0, 0, -26] }, splitLine: { lineStyle: { color: "rgba(148,163,184,0.18)" } }, axisLabel: { color: "#64748b", fontSize: 11 } }, series: block.series.map((series) => ({ name: series.name, type: series.type ?? block.chartType, data: series.values, smooth: (series.type ?? block.chartType) === "line" ? 0.28 : undefined, symbol: "circle", symbolSize: 6, barMaxWidth: 24, lineStyle: { width: 2.5 }, itemStyle: { borderRadius: (series.type ?? block.chartType) === "bar" ? [3, 3, 0, 0] : undefined } })) }; } function metricToneClass(tone: AnalysisMetric["tone"]) { if (tone === "danger") return "bg-rose-500"; if (tone === "warning") return "bg-amber-500"; if (tone === "success") return "bg-emerald-500"; if (tone === "info") return "bg-blue-600"; return "bg-slate-400"; }