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 && (
)}
)}
{/* ============ 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"} />
)}
);
}