Files
AIclinicalresearch/frontend-v2/src/modules/ssa/components/ConclusionReport.tsx
HaHafeng 428a22adf2 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>
2026-02-20 23:09:27 +08:00

234 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 综合结论报告组件
*
* 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;