feat(ssa): Complete Phase 2A frontend integration - multi-step workflow end-to-end

Phase 2A: WorkflowPlannerService, WorkflowExecutorService, Python data quality, 6 bug fixes, DescriptiveResultView, multi-step R code/Word export, MVP UI reuse. V11 UI: Gemini-style, multi-task, single-page scroll, Word export. Architecture: Block-based rendering consensus (4 block types). New R tools: chi_square, correlation, descriptive, logistic_binary, mann_whitney, t_test_paired. Docs: dev summary, block-based plan, status updates, task list v2.0.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-20 23:09:27 +08:00
parent 23b422f758
commit 428a22adf2
62 changed files with 15416 additions and 299 deletions

View File

@@ -0,0 +1,233 @@
/**
* 综合结论报告组件
*
* Phase 2A: 多步骤工作流执行完成后的综合结论展示
*/
import React, { useState } from 'react';
import type { ConclusionReport as ConclusionReportType, WorkflowStepResult } from '../types';
interface ConclusionReportProps {
report: ConclusionReportType;
stepResults?: WorkflowStepResult[];
}
interface StepResultDetailProps {
stepSummary: ConclusionReportType['step_summaries'][0];
stepResult?: WorkflowStepResult;
}
const StepResultDetail: React.FC<StepResultDetailProps> = ({ stepSummary, stepResult }) => {
const [expanded, setExpanded] = useState(false);
return (
<div className={`step-result-detail ${expanded ? 'expanded' : ''}`}>
<div className="detail-header" onClick={() => setExpanded(!expanded)}>
<div className="header-left">
<span className="step-badge"> {stepSummary.step_number}</span>
<span className="tool-name">{stepSummary.tool_name}</span>
{stepSummary.p_value !== undefined && (
<span
className="p-value-badge"
style={{
backgroundColor: stepSummary.is_significant ? '#ecfdf5' : '#f8fafc',
color: stepSummary.is_significant ? '#059669' : '#64748b',
}}
>
p = {stepSummary.p_value < 0.001 ? '< 0.001' : stepSummary.p_value.toFixed(4)}
{stepSummary.is_significant && ' *'}
</span>
)}
</div>
<span className={`expand-arrow ${expanded ? 'rotated' : ''}`}></span>
</div>
<div className="detail-summary">{stepSummary.summary}</div>
{expanded && stepResult?.result && (
<div className="detail-content">
{/* 结果表格 */}
{stepResult.result.result_table && (
<div className="result-table-wrapper">
<table className="result-table">
<thead>
<tr>
{stepResult.result.result_table.headers.map((header, idx) => (
<th key={idx}>{header}</th>
))}
</tr>
</thead>
<tbody>
{stepResult.result.result_table.rows.map((row, rowIdx) => (
<tr key={rowIdx}>
{row.map((cell, cellIdx) => (
<td key={cellIdx}>{typeof cell === 'number' ? cell.toFixed(4) : cell}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)}
{/* 图表 */}
{stepResult.result.plots && stepResult.result.plots.length > 0 && (
<div className="result-plots">
{stepResult.result.plots.map((plot, idx) => (
<div key={idx} className="plot-item">
<div className="plot-title">{plot.title}</div>
<img
src={plot.imageBase64.startsWith('data:') ? plot.imageBase64 : `data:image/png;base64,${plot.imageBase64}`}
alt={plot.title}
className="plot-image"
/>
</div>
))}
</div>
)}
{/* 详细解释 */}
{stepResult.result.interpretation && (
<div className="interpretation-box">
<span className="interpretation-label">💡 :</span>
<p>{stepResult.result.interpretation}</p>
</div>
)}
</div>
)}
</div>
);
};
export const ConclusionReport: React.FC<ConclusionReportProps> = ({ report, stepResults = [] }) => {
const [showFullReport, setShowFullReport] = useState(true);
const getStepResult = (stepNumber: number): WorkflowStepResult | undefined => {
return stepResults.find(r => r.step_number === stepNumber);
};
return (
<div className="conclusion-report">
{/* 报告头部 */}
<div className="report-header">
<h2 className="report-title">📋 {report.title}</h2>
<span className="generated-time">
{new Date(report.generated_at).toLocaleString('zh-CN')}
</span>
</div>
{/* AI 总结摘要 - 始终显示 */}
<div className="executive-summary">
<div className="summary-header">
<span className="summary-icon">🤖</span>
<span className="summary-label">AI </span>
</div>
<div className="summary-content">
{report.executive_summary}
</div>
</div>
{/* 主要发现 */}
{report.key_findings.length > 0 && (
<div className="key-findings">
<div className="section-header">
<span className="section-icon">🎯</span>
<span className="section-title"></span>
</div>
<ul className="findings-list">
{report.key_findings.map((finding, idx) => (
<li key={idx}>{finding}</li>
))}
</ul>
</div>
)}
{/* 统计概览 */}
<div className="stats-overview">
<div className="stat-card">
<span className="stat-icon">📊</span>
<span className="stat-value">{report.statistical_summary.total_tests}</span>
<span className="stat-label"></span>
</div>
<div className="stat-card significant">
<span className="stat-icon"></span>
<span className="stat-value">{report.statistical_summary.significant_results}</span>
<span className="stat-label"></span>
</div>
<div className="stat-card">
<span className="stat-icon">🔬</span>
<span className="stat-value">{report.statistical_summary.methods_used.length}</span>
<span className="stat-label"></span>
</div>
</div>
{/* 展开/折叠按钮 */}
<button
className="toggle-details-btn"
onClick={() => setShowFullReport(!showFullReport)}
>
{showFullReport ? '收起详细结果 ▲' : '展开详细结果 ▼'}
</button>
{/* 详细步骤结果 */}
{showFullReport && (
<div className="step-results-section">
<div className="section-header">
<span className="section-icon">📝</span>
<span className="section-title"></span>
</div>
<div className="step-results-list">
{report.step_summaries.map((stepSummary) => (
<StepResultDetail
key={stepSummary.step_number}
stepSummary={stepSummary}
stepResult={getStepResult(stepSummary.step_number)}
/>
))}
</div>
</div>
)}
{/* 建议 */}
{report.recommendations.length > 0 && (
<div className="recommendations-section">
<div className="section-header">
<span className="section-icon">💡</span>
<span className="section-title"></span>
</div>
<ul className="recommendations-list">
{report.recommendations.map((rec, idx) => (
<li key={idx}>{rec}</li>
))}
</ul>
</div>
)}
{/* 局限性 */}
{report.limitations.length > 0 && (
<div className="limitations-section">
<div className="section-header">
<span className="section-icon"></span>
<span className="section-title"></span>
</div>
<ul className="limitations-list">
{report.limitations.map((lim, idx) => (
<li key={idx}>{lim}</li>
))}
</ul>
</div>
)}
{/* 使用的方法列表 */}
<div className="methods-used">
<span className="methods-label">使:</span>
<div className="methods-tags">
{report.statistical_summary.methods_used.map((method, idx) => (
<span key={idx} className="method-tag">{method}</span>
))}
</div>
</div>
</div>
);
};
export default ConclusionReport;