import { useMemo, useState } from "react"; import { Activity, AlertTriangle, BarChart3, CheckCircle2, Database, Download, FileText, GitCompare, LineChart, ShieldCheck, Sparkles, Users, } from "lucide-react"; import { Card, CardHeader, Stat } from "@/components/ui/Card"; import { Badge } from "@/components/ui/Badge"; import { Button } from "@/components/ui/Button"; import { PriorPosteriorChart } from "@/components/charts/PriorPosteriorChart"; import { KDEChart } from "@/components/charts/KDEChart"; import { SensitivityChart } from "@/components/charts/SensitivityChart"; import { CorrelationHeatmap } from "@/components/charts/CorrelationHeatmap"; import { ExpertsRadarChart } from "@/components/charts/ExpertsRadarChart"; import { ForestPlot } from "@/components/charts/ForestPlot"; import { HistogramChart } from "@/components/charts/HistogramChart"; import { MarkdownReport } from "@/components/bayesian/MarkdownReport"; import { ManualOverride } from "@/components/bayesian/ManualOverride"; import { triggerDownload } from "@/lib/download"; import type { ArtifactEvent, AutoPipelineSummary, BatchParametersSummary, CorrelationMatrixSummary, DataQualitySummary, DatasetSummary, ExpertDisagreementsSummary, FusedPosteriorSummary, KDEPoint, KendallWSummary, NormalityTestSummary, OutliersSummary, PosteriorSummary, PriorSummary, ReportGeneratedSummary, ScenarioComparisonSummary, SensitivitySummary, } from "@/lib/types"; interface Props { artifacts: ArtifactEvent[]; } interface LatestState { dataset?: DatasetSummary; datasetValues?: number[]; prior?: PriorSummary; kde?: KDEPoint[]; posterior?: PosteriorSummary; sensitivity?: SensitivitySummary; outliers?: OutliersSummary; normality?: NormalityTestSummary; dataQuality?: DataQualitySummary; autoPipeline?: AutoPipelineSummary; fusedPosterior?: FusedPosteriorSummary; fusedExperts?: Array<{ name: string; judgments: number[] }>; kendallW?: KendallWSummary; disagreements?: ExpertDisagreementsSummary; batch?: BatchParametersSummary; correlation?: CorrelationMatrixSummary; scenarioCompare?: ScenarioComparisonSummary; report?: ReportGeneratedSummary; reportMarkdown?: string; } function reduceArtifacts(arts: ArtifactEvent[]): LatestState { const s: LatestState = {}; for (const a of arts) { switch (a.type) { case "dataset_loaded": s.dataset = a.summary; if (a.values && a.values.length > 0) s.datasetValues = a.values; break; case "prior_built": s.prior = a.summary; s.kde = a.kde; break; case "posterior_computed": s.posterior = a.summary; break; case "sensitivity": s.sensitivity = a.summary; break; case "outliers": s.outliers = a.summary; break; case "normality_test": s.normality = a.summary; break; case "data_quality": s.dataQuality = a.summary; break; case "auto_pipeline": s.autoPipeline = a.summary; break; case "fused_posterior": s.fusedPosterior = a.summary; // also reconstruct an "experts" array for the radar chart // (we don't have individual judgments in the summary unless disagreements artifact came) break; case "kendall_w": s.kendallW = a.summary; break; case "expert_disagreements": s.disagreements = a.summary; s.fusedExperts = a.summary.levels[0]?.individual_judgments.map((j) => ({ name: j.expert, judgments: a.summary.levels.map((lv) => lv.individual_judgments.find((x) => x.expert === j.expert)?.judgment_idx ?? 2 ), })); break; case "batch_parameters": s.batch = a.summary; break; case "correlation_matrix": s.correlation = a.summary; break; case "scenario_comparison": s.scenarioCompare = a.summary; break; case "report_generated": s.report = a.summary; s.reportMarkdown = a.markdown; break; } } return s; } const LEVEL_LABELS_ZH = ["极低", "低", "中等", "高", "极高"]; const JUDGMENT_LABELS_ZH = ["极不可能", "不太可能", "难以判断", "比较可能", "极有可能"]; export function Workspace({ artifacts }: Props) { const st = useMemo(() => reduceArtifacts(artifacts), [artifacts]); const [reportExpanded, setReportExpanded] = useState(true); const [manualResult, setManualResult] = useState<{ mean: number; std: number } | null>(null); const isEmpty = Object.values(st).every((v) => v === undefined); if (isEmpty) { return (

工作台

当 Agent 拉数据、做预处理、构建先验、计算后验、对比情景或生成报告时,对应图表和摘要会实时显示在这里。

); } const labels = st.prior?.level_labels_zh ?? LEVEL_LABELS_ZH; const unit = st.dataset?.unit; const downloadHandler = async (url: string, filename: string) => { try { await triggerDownload(url, filename); } catch (e) { alert(`下载失败: ${(e as Error).message}\n\n如果浏览器仍未触发下载,可在新标签页打开链接:${url}`); } }; return (
{/* ============ Report (top, includes inline preview) ============ */} {st.report && ( 分析报告 — {st.report.scenario_name} } subtitle={`${st.report.markdown_chars} 字符 Markdown · 三种格式可下载`} right={就绪} />
{(["docx", "pdf", "html"] as const).map((kind) => { const info = st.report!.downloads[kind]; return ( ); })}
{st.reportMarkdown && (
{reportExpanded && (
)}
)}
)} {/* ============ Dataset ============ */} {st.dataset && ( {st.dataset.name || st.dataset.indicator_name || st.dataset.variable_name || "Dataset"} } subtitle={ {st.dataset.source && 来源 · {st.dataset.source}} {st.dataset.period && {st.dataset.period}} {st.dataset.unit && {st.dataset.unit}} } right={n = {st.dataset.n ?? st.dataset.n_used ?? "-"}} /> {(st.dataset.min !== undefined || st.dataset.max !== undefined) && (
)} {/* Inline histogram of raw data */} {st.datasetValues && st.datasetValues.length >= 8 && (
原始数据分布(直方图 + KDE)
)}
)} {/* ============ Data quality score ============ */} {st.dataQuality && ( 数据质量评分(专利 §2.8.7) } right={ = 75 ? "green" : st.dataQuality.score >= 60 ? "amber" : "red"}> {st.dataQuality.score} / 100 · {st.dataQuality.grade} } />
{st.dataQuality.issues.length > 0 && (
发现问题
    {st.dataQuality.issues.map((s, i) =>
  • {s}
  • )}
)}
)} {/* ============ Auto preprocessing pipeline audit ============ */} {st.autoPipeline && ( 自动预处理决策管线(专利 §2.8.8) } subtitle={`推荐先验方法:${st.autoPipeline.recommended_prior_method} · 质量分 ${st.autoPipeline.quality_score}/100`} right={st.autoPipeline.winsorization_applied ? 已 Winsorize : 原始数据} />
{st.autoPipeline.audit_trail.map((step, i) => (
step {String(step.step)} · {String(step.name)}
))}
👉 {st.autoPipeline.next_step}
)} {/* ============ Outliers ============ */} {st.outliers && !st.autoPipeline && ( 异常值检测} subtitle={`方法:${st.outliers.method} · 边界 [${st.outliers.lower_bound.toFixed(2)}, ${st.outliers.upper_bound.toFixed(2)}]`} right={ 10 ? "red" : st.outliers.count > 0 ? "amber" : "green"}> {st.outliers.count} 个 ({st.outliers.percentage.toFixed(1)}%) } /> )} {/* ============ Normality test ============ */} {st.normality && ( 正态性检验:{st.normality.test}} right={{st.normality.is_normal ? "近似正态" : "偏离正态"}} />

{st.normality.interpretation}

)} {/* ============ Prior KDE ============ */} {st.prior && st.kde && ( 先验分布(KDE + 5 分位点)} subtitle={`mean = ${st.prior.mean.toFixed(3)} · std = ${st.prior.std.toFixed(3)} · skew = ${st.prior.skewness.toFixed(2)} · kurt = ${st.prior.kurtosis.toFixed(2)}`} /> )} {/* ============ Posterior (with manual override) ============ */} {st.posterior && st.prior && ( 先验 vs 后验} subtitle={`R = ${st.posterior.R} · ${st.posterior.judgments.map((j, i) => `${labels[i]}: ${JUDGMENT_LABELS_ZH[j]}`).join(" · ")}`} right={单专家} />
= 0 ? "+" : ""}${st.posterior.shift_mean.toFixed(3)}`} tone={st.posterior.shift_mean >= 0 ? "accent" : "warn"} />
{st.posterior.judgment_rationale && (
Agent 的判断理由
{st.posterior.judgment_rationale}
)} {/* Manual override — runs locally, no Agent needed */} {st.datasetValues && ( setManualResult({ mean: r.posterior.stats.mean, std: r.posterior.stats.std }) } /> )}
)} {/* ============ Multi-expert fused posterior ============ */} {st.fusedPosterior && st.prior && ( 多专家融合后验({st.fusedPosterior.method} · n={st.fusedPosterior.n_experts})} subtitle={`专家:${st.fusedPosterior.expert_names.join(" · ")}`} right={st.fusedPosterior.panel_agreement_index !== undefined && ( 0.6 ? "green" : "amber"}> Panel Agreement {st.fusedPosterior.panel_agreement_index.toFixed(2)} )} />
)} {/* ============ Experts Radar Chart ============ */} {st.fusedExperts && st.fusedExperts.length >= 2 && ( 专家判断雷达图} subtitle="每位专家在 5 个分位水平上的判断轮廓" /> )} {/* ============ Kendall's W ============ */} {st.kendallW && ( Kendall's W 一致性} right={= 0.7 ? "green" : st.kendallW.W >= 0.5 ? "amber" : "red"}>{st.kendallW.interpretation}} />
)} {/* ============ Expert disagreements ============ */} {st.disagreements && ( 专家分歧水平} right={{st.disagreements.n_flagged} 个高分歧水平} />
{st.disagreements.levels.map((lv) => (
{lv.level_label_zh} range={lv.range} · σ={lv.std}
{lv.individual_judgments.map((j, i) => ( {j.expert}: {j.judgment_label_zh} ))}
))}

{st.disagreements.advice}

)} {/* ============ Sensitivity ============ */} {st.sensitivity && ( 稳健性分析} subtitle={`R ∈ [${Math.min(...st.sensitivity.r_values)}, ${Math.max(...st.sensitivity.r_values)}] · 均值波动 ${st.sensitivity.mean_swing_pct.toFixed(1)}%`} /> )} {/* ============ Batch parameters + Forest plot ============ */} {st.batch && ( 多参数 Forest plot({st.batch.n_parameters} 个)} subtitle="横向 95% 置信区间 · 实心方块 = 后验均值" /> p.posterior_mean !== undefined && p.posterior_ci95) .map((p) => ({ label: p.name + (p.unit ? ` (${p.unit})` : ""), mean: p.posterior_mean!, ci95Lower: p.posterior_ci95![0], ci95Upper: p.posterior_ci95![1], }))} /> )} {/* ============ Correlation matrix ============ */} {st.correlation && (st.correlation.pearson || st.correlation.spearman) && ( 参数相关性矩阵} /> {st.correlation.pearson && ( )} {st.correlation.spearman && (
)}
)} {/* ============ Scenario comparison ============ */} {st.scenarioCompare && ( 情景对比 · {st.scenarioCompare.label_a} vs {st.scenarioCompare.label_b}} right={{st.scenarioCompare.interpretation}} />
= 0 ? "+" : "") + st.scenarioCompare.delta_mean.toFixed(3)} tone={st.scenarioCompare.delta_mean >= 0 ? "accent" : "warn"} />
)}
); }