Files
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

546 lines
36 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSA-Pro 智能统计工作台 V12.0 (Agentic Pipeline)</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; background-color: #f8fafc; color: #334155; overflow: hidden; }
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
.fade-in { animation: fadeIn 0.4s ease-out forwards; opacity: 0; }
.slide-up { animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards; opacity: 0; transform: translateY(15px); }
.pop-in { animation: popIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; opacity: 0; transform: scale(0.95); }
@keyframes fadeIn { to { opacity: 1; } }
@keyframes slideUp { to { transform: translateY(0); opacity: 1; } }
@keyframes popIn { to { transform: scale(1); opacity: 1; } }
/* 科学表格样式 (V9 风格) */
.sci-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.sci-table th { border-top: 2px solid #1e293b; border-bottom: 1px solid #1e293b; padding: 10px 12px; text-align: left; font-weight: 600; color: #1e293b; }
.sci-table td { border-bottom: 1px solid #e2e8f0; padding: 10px 12px; color: #475569; }
.sci-table tr:last-child td { border-bottom: 2px solid #1e293b; }
.status-dot { height: 8px; width: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; }
.status-success { background-color: #22c55e; box-shadow: 0 0 0 2px #dcfce7; }
pre code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 0.85rem; line-height: 1.5; }
.artifact-shadow { box-shadow: 0 4px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); }
/* Agent 工作流连线 */
.agent-line { position: absolute; left: 11px; top: 24px; bottom: -12px; width: 2px; background-color: #e2e8f0; z-index: 0; }
.agent-step:last-child .agent-line { display: none; }
/* 物理垫片:彻底解决底部输入框遮挡问题 */
.scroll-spacer { height: 220px; width: 100%; flex-shrink: 0; }
</style>
</head>
<body class="h-screen flex text-slate-800">
<!-- 全局 Toast 容器 -->
<div id="toast-container" class="fixed top-6 left-1/2 transform -translate-x-1/2 z-50 flex flex-col gap-2 pointer-events-none"></div>
<!-- 左侧:抽屉侧边栏 (V11/V10 风格) -->
<aside id="sidebar" class="w-16 bg-slate-50 border-r border-slate-200 flex flex-col z-30 flex-shrink-0 transition-all duration-300 ease-in-out relative">
<div class="h-16 flex items-center px-3 flex-shrink-0 overflow-hidden whitespace-nowrap border-b border-slate-200 bg-white">
<button onclick="toggleSidebar()" class="w-10 h-10 rounded-lg hover:bg-slate-200 flex items-center justify-center text-slate-600 transition flex-shrink-0">
<i class="fa-solid fa-bars text-lg"></i>
</button>
<div class="sidebar-text hidden ml-3 flex items-center gap-2 fade-in">
<div class="w-7 h-7 bg-slate-800 rounded flex items-center justify-center text-white shadow-sm">
<i class="fa-solid fa-brain text-xs"></i>
</div>
<span class="font-bold text-slate-800 tracking-tight text-lg">SSA-V12</span>
</div>
</div>
<div class="px-3 mt-4 flex flex-col gap-3">
<button id="new-chat-btn" class="w-10 h-10 bg-white border border-slate-200 hover:border-blue-300 shadow-sm rounded-lg flex items-center justify-center text-slate-700 transition-all duration-300 overflow-hidden whitespace-nowrap group">
<i class="fa-solid fa-plus text-blue-600"></i>
<span class="sidebar-text hidden ml-3 font-medium text-sm text-slate-700 group-hover:text-blue-600 transition">新建智能分析</span>
</button>
<button id="history-icon-only" onclick="toggleSidebar()" class="w-10 h-10 rounded-lg hover:bg-slate-200 flex items-center justify-center text-slate-500 transition-all duration-300" title="查看历史记录">
<i class="fa-solid fa-clock-rotate-left"></i>
</button>
</div>
<div id="history-list" class="flex-1 overflow-y-auto px-3 mt-6 hidden opacity-0 transition-opacity duration-300 pb-4">
<div class="px-2 py-1 text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-2">近期分析</div>
<div class="space-y-1">
<button class="w-full text-left px-3 py-2.5 rounded-lg bg-white border border-slate-200 text-blue-700 font-medium text-sm truncate flex items-center gap-3 shadow-sm">
<i class="fa-solid fa-network-wired text-blue-500"></i> 血压疗效研究
</button>
</div>
</div>
</aside>
<!-- 动态主容器 -->
<div class="flex-1 flex overflow-hidden relative bg-white" id="main-container">
<!-- 左半部分:沉浸式居中对话区 -->
<section id="chat-pane" class="flex-1 flex flex-col h-full transition-all duration-500 ease-in-out relative bg-[#f8fafc]">
<!-- Chat Header -->
<header class="h-16 flex items-center justify-between px-6 z-10 border-b border-slate-100 bg-white/90 backdrop-blur flex-shrink-0 absolute top-0 left-0 right-0">
<div class="font-semibold text-slate-800 text-lg flex items-center gap-2">
血压下降疗效分析
<span class="text-[10px] bg-slate-100 text-slate-500 px-2 py-0.5 rounded border border-slate-200 uppercase tracking-wide">Multi-Agent</span>
</div>
<div class="flex items-center gap-2 text-xs font-medium text-slate-500 bg-slate-50 px-3 py-1.5 rounded-full border border-slate-100 shadow-sm">
<span class="status-dot status-success animate-pulse"></span> Pipeline Ready
</div>
</header>
<!-- Chat Messages (包含物理垫片) -->
<div id="chat-container" class="flex-1 overflow-y-auto px-4 md:px-8 pt-24 flex flex-col items-center">
<div class="w-full max-w-3xl space-y-8 min-h-full">
<!-- 欢迎语 -->
<div class="flex gap-4 slide-up">
<div class="w-8 h-8 rounded-full bg-slate-800 flex items-center justify-center flex-shrink-0 mt-0.5"><i class="fa-solid fa-robot text-white text-xs"></i></div>
<div class="bg-white text-slate-700 p-4 rounded-2xl rounded-tl-none text-sm leading-relaxed border border-slate-200 shadow-sm">
你好!我是 <b>SSA-Pro 智能流水线</b><br>我由「意图理解」、「数据诊断」、「路径规划」等多个专家智能体组成。请点击下方 📎 <b>上传数据文件</b> 并描述需求,我们将协同为您服务。
</div>
</div>
<!-- 会话内容锚点 -->
<div id="chat-anchor"></div>
<!-- 终极武器:物理垫片,确保滚动到底部时内容不会被悬浮输入框遮挡 -->
<div class="scroll-spacer"></div>
</div>
</div>
<!-- Chat Input (悬浮输入框,已修复透明墙遮挡问题) -->
<!-- 修复点:外层 pointer-events-none内层 pointer-events-auto -->
<div class="flex-shrink-0 w-full flex justify-center pb-6 px-4 absolute bottom-0 bg-gradient-to-t from-[#f8fafc] via-[#f8fafc] to-transparent pt-16 z-20 pointer-events-none">
<div class="w-full max-w-3xl relative pointer-events-auto">
<!-- 包裹挂载区和输入框的容器 -->
<div class="shadow-lg rounded-2xl bg-white border border-slate-200 focus-within:border-blue-400 focus-within:ring-4 focus-within:ring-blue-50 transition-all flex flex-col p-2">
<!-- 数据挂载区 (嵌入式) -->
<div id="data-mount-zone" class="hidden items-center mb-1 pl-1 pop-in w-full">
<div class="bg-slate-50 border border-slate-200 rounded-lg px-2 py-1.5 flex items-center gap-3 w-max transition-all hover:border-blue-300">
<div class="w-8 h-8 rounded bg-green-50 flex items-center justify-center border border-green-100">
<i class="fa-solid fa-file-csv text-green-600 text-sm"></i>
</div>
<div class="flex flex-col pr-2">
<span class="text-xs font-bold text-slate-700">BP_Trial_Data.csv</span>
</div>
<div class="h-6 w-px bg-slate-200 mx-1"></div>
<button onclick="removeData(event)" class="text-slate-400 hover:text-red-500 transition px-1" title="移除数据">
<i class="fa-solid fa-xmark text-sm"></i>
</button>
</div>
</div>
<!-- 文本输入行 -->
<div class="relative flex items-end w-full">
<button onclick="simulateAgentPipeline()" id="btn-upload" class="absolute left-1 bottom-1 text-slate-400 hover:text-blue-600 transition w-9 h-9 flex items-center justify-center rounded-lg hover:bg-slate-100 z-10" title="上传数据">
<i class="fa-solid fa-paperclip text-lg"></i>
</button>
<textarea id="user-input" class="w-full bg-transparent py-2.5 pl-11 pr-12 focus:outline-none resize-none text-sm text-slate-700 placeholder-slate-400" rows="1" placeholder="发送消息,或点击回形针 📎 启动多智能体协作流..." style="min-height: 44px;"></textarea>
<button onclick="simulateAgentPipeline()" class="absolute right-1 bottom-1 w-9 h-9 bg-slate-800 text-white rounded-lg hover:bg-slate-700 transition flex items-center justify-center shadow-sm z-10">
<i class="fa-solid fa-arrow-up text-sm"></i>
</button>
</div>
</div>
</div>
</div>
</section>
<!-- 右半部分:动态工作区 (Workspace Pane) -->
<section id="workspace-pane" class="w-0 overflow-hidden bg-slate-50 transition-all duration-500 ease-in-out flex flex-col flex-shrink-0 border-l border-transparent relative">
<div class="w-full min-w-[600px] h-full flex flex-col absolute inset-0">
<!-- 顶部工具栏 -->
<header class="h-14 flex items-center justify-between px-4 border-b border-slate-200 bg-slate-100/50 flex-shrink-0">
<div class="flex items-center gap-2">
<span id="workspace-badge" class="px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-purple-100 text-purple-700 border border-purple-200">
Execution
</span>
<span id="workspace-title" class="text-sm font-semibold text-slate-700">流程执行器 (Pipeline Executor)</span>
</div>
<div class="flex gap-2">
<button onclick="closeWorkspace()" class="w-8 h-8 bg-white border border-slate-200 text-slate-500 rounded-lg hover:bg-slate-100 hover:text-slate-800 transition shadow-sm flex items-center justify-center" title="收起工作区">
<i class="fa-solid fa-right-to-bracket"></i>
</button>
</div>
</header>
<!-- 工作区主画布 -->
<div class="flex-1 p-4 md:p-6 overflow-y-auto">
<div class="bg-white rounded-2xl artifact-shadow border border-slate-200/60 min-h-full flex flex-col relative overflow-hidden">
<!-- 视图:执行与结论生成 (Executor & Critic) -->
<div id="view-execution" class="p-8 w-full flex flex-col h-full relative">
<!-- 执行引擎头部 -->
<div class="flex items-center justify-between mb-6 pb-4 border-b border-slate-100">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-slate-900 rounded-lg flex items-center justify-center text-white"><i class="fa-solid fa-gears"></i></div>
<div>
<h2 class="text-lg font-bold text-slate-800">R 编排引擎运行中</h2>
<p class="text-xs text-slate-500" id="exec-subtitle">正在按 SAP 计划逐步执行统计检验...</p>
</div>
</div>
<div id="exec-spinner" class="text-blue-600 text-2xl"><i class="fa-solid fa-circle-notch fa-spin"></i></div>
</div>
<!-- 步骤追踪区 -->
<div class="flex-1">
<div class="bg-slate-50 rounded-xl p-6 border border-slate-200 font-mono text-sm text-slate-600 relative overflow-hidden min-h-[250px]">
<div class="absolute left-[39px] top-8 bottom-8 w-px bg-slate-300"></div>
<div id="trace-logs" class="space-y-6 relative z-10">
<!-- 日志动态插入 -->
</div>
</div>
</div>
<!-- 结论生成器区域 (初始隐藏) -->
<div id="critic-section" class="mt-6 pt-6 border-t border-slate-100 hidden">
<div class="flex items-center gap-2 mb-4 text-purple-700 font-bold">
<i class="fa-solid fa-pen-nib"></i> 结论生成器 (Critic Agent) 工作区
</div>
<!-- 综合报告骨架屏/最终结果 -->
<div class="bg-gradient-to-r from-purple-50 to-blue-50 border border-purple-100 p-5 rounded-xl shadow-sm">
<div id="report-typing" class="text-sm text-slate-700 leading-relaxed">
<i class="fa-solid fa-pen text-purple-400 mr-2 animate-bounce"></i> 正在根据 R 执行结果 (P=0.002) 和临床解读模板合成综合报告...
</div>
<div id="report-final" class="hidden">
<h4 class="font-bold text-slate-900 mb-2">📄 综合统计分析报告</h4>
<p class="text-sm text-slate-700 mb-4 leading-relaxed">
本研究纳入了 120 例样本。由于数据未能通过 Shapiro-Wilk 正态性检验P < 0.05采用非参数的 Wilcoxon 秩和检验进行差异推断
结果显示新药组的血压下降幅度中位数 14.5显著高于对照组中位数 8.2差异具有统计学意义 (<span class="text-red-600 font-bold bg-red-50 px-1 rounded">P = 0.002</span>)。
</p>
<!-- 三线表 -->
<table class="sci-table bg-white border border-slate-200 rounded overflow-hidden">
<thead>
<tr class="bg-slate-50">
<th>组别</th>
<th>N</th>
<th>下降值 (Median [IQR])</th>
<th>W 统计量</th>
<th>P 值</th>
</tr>
</thead>
<tbody>
<tr>
<td>新药组</td>
<td>60</td>
<td>14.5 [12.1 - 16.8]</td>
<td rowspan="2" class="align-middle border-l border-slate-100">2845.5</td>
<td rowspan="2" class="align-middle border-l border-slate-100 text-red-600 font-bold">< 0.01 **</td>
</tr>
<tr>
<td>对照组</td>
<td>60</td>
<td>8.2 [6.5 - 10.4]</td>
</tr>
</tbody>
</table>
<div class="mt-4 flex gap-2">
<button class="px-3 py-1.5 bg-white border border-slate-200 text-slate-600 text-xs font-medium rounded hover:bg-slate-50 transition shadow-sm"><i class="fa-solid fa-chart-bar text-blue-500 mr-1"></i> 查看分布图</button>
<button class="px-3 py-1.5 bg-slate-800 text-white text-xs font-medium rounded hover:bg-slate-700 transition shadow-sm"><i class="fa-solid fa-download mr-1"></i> 下载完整报告</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<script>
const chatAnchor = document.getElementById('chat-anchor');
const workspacePane = document.getElementById('workspace-pane');
// 侧边栏切换
let isSidebarExpanded = false;
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const texts = document.querySelectorAll('.sidebar-text');
const historyList = document.getElementById('history-list');
const newChatBtn = document.getElementById('new-chat-btn');
const historyIconBtn = document.getElementById('history-icon-only');
isSidebarExpanded = !isSidebarExpanded;
if(isSidebarExpanded) {
sidebar.classList.replace('w-16', 'w-64');
texts.forEach(el => el.classList.remove('hidden'));
newChatBtn.classList.replace('w-10', 'w-full');
newChatBtn.classList.replace('justify-center', 'justify-start');
newChatBtn.classList.add('px-3');
historyIconBtn.classList.add('hidden');
historyList.classList.remove('hidden');
setTimeout(() => historyList.classList.replace('opacity-0', 'opacity-100'), 150);
} else {
sidebar.classList.replace('w-64', 'w-16');
texts.forEach(el => el.classList.add('hidden'));
newChatBtn.classList.replace('w-full', 'w-10');
newChatBtn.classList.replace('justify-start', 'justify-center');
newChatBtn.classList.remove('px-3');
historyList.classList.add('hidden');
historyList.classList.replace('opacity-100', 'opacity-0');
historyIconBtn.classList.remove('hidden');
}
}
// 工作区拉出与收起
function openWorkspace() {
workspacePane.classList.remove('w-0', 'border-transparent');
workspacePane.classList.add('w-[60%]', 'border-slate-200');
setTimeout(scrollToBottom, 300);
}
function closeWorkspace() {
workspacePane.classList.remove('w-[60%]', 'border-slate-200');
workspacePane.classList.add('w-0', 'border-transparent');
}
// ==========================================
// 核心流程:多智能体协作 Pipeline
// ==========================================
function simulateAgentPipeline() {
// 0. UI 预处理
const mountZone = document.getElementById('data-mount-zone');
mountZone.classList.remove('hidden');
mountZone.classList.add('flex');
document.getElementById('btn-upload').classList.add('opacity-50', 'pointer-events-none');
document.getElementById('user-input').value = "比较新药组和对照组的血压下降值。";
// 插入用户的对话
const userMsg = `
<div class="flex gap-4 flex-row-reverse slide-up w-full">
<div class="w-8 h-8 rounded-full bg-slate-200 flex items-center justify-center flex-shrink-0 mt-0.5"><i class="fa-solid fa-user text-slate-500 text-xs"></i></div>
<div class="bg-blue-600 text-white p-4 rounded-2xl rounded-tr-none text-sm shadow-md">比较新药组和对照组的血压下降值。</div>
</div>`;
chatAnchor.insertAdjacentHTML('beforebegin', userMsg);
scrollToBottom();
// 1. 生成 Agent 网络工作流容器
setTimeout(() => {
const agentContainerId = 'agent-flow-' + Date.now();
const agentFlowHTML = `
<div class="flex gap-4 slide-up w-full" id="${agentContainerId}">
<div class="w-8 h-8 rounded-full bg-slate-800 flex items-center justify-center flex-shrink-0 mt-0.5"><i class="fa-solid fa-network-wired text-white text-xs"></i></div>
<div class="w-full max-w-2xl bg-white border border-slate-200 p-5 rounded-2xl rounded-tl-none shadow-sm">
<div class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-5 flex items-center gap-2">
<i class="fa-solid fa-microchip"></i> Agent Pipeline 启动
</div>
<div class="relative space-y-5" id="${agentContainerId}-steps">
<!-- 动态插入各个 Agent 节点 -->
</div>
</div>
</div>`;
chatAnchor.insertAdjacentHTML('beforebegin', agentFlowHTML);
scrollToBottom();
const stepsBox = document.getElementById(`${agentContainerId}-steps`);
// Step 1: 意图理解器
setTimeout(() => {
const step1 = `
<div class="agent-step relative pl-8 slide-up">
<div class="agent-line"></div>
<div class="absolute left-0 top-0.5 w-6 h-6 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center z-10"><i class="fa-solid fa-brain text-[10px]"></i></div>
<div class="text-sm font-bold text-slate-800 mb-1">意图理解器 (Intent Parser) <span class="text-xs font-normal text-slate-400 ml-2">LLM + 分类</span></div>
<div class="bg-slate-50 border border-slate-100 p-2.5 rounded-lg flex gap-2 text-xs text-slate-600">
<span class="bg-white px-2 py-0.5 rounded border border-slate-200">Goal: <b class="text-blue-600">差异比较</b></span>
<span class="bg-white px-2 py-0.5 rounded border border-slate-200">X: <b class="text-slate-800">Group</b></span>
<span class="bg-white px-2 py-0.5 rounded border border-slate-200">Y: <b class="text-slate-800">BP_Change</b></span>
<span class="bg-white px-2 py-0.5 rounded border border-slate-200">Design: <b class="text-slate-800">独立两组</b></span>
</div>
</div>`;
stepsBox.insertAdjacentHTML('beforeend', step1);
scrollToBottom();
}, 800);
// Step 2: 数据诊断器
setTimeout(() => {
const step2 = `
<div class="agent-step relative pl-8 slide-up">
<div class="agent-line"></div>
<div class="absolute left-0 top-0.5 w-6 h-6 rounded-full bg-green-100 text-green-600 flex items-center justify-center z-10"><i class="fa-solid fa-stethoscope text-[10px]"></i></div>
<div class="text-sm font-bold text-slate-800 mb-1">数据诊断器 (Data Profiler) <span class="text-xs font-normal text-slate-400 ml-2">R 统计检验</span></div>
<div class="bg-slate-50 border border-slate-100 p-2.5 rounded-lg text-xs text-slate-600 space-y-1">
<div class="flex justify-between"><span>样本量: N=120</span> <span class="text-green-600"><i class="fa-solid fa-check"></i> 无缺失值</span></div>
<div class="flex justify-between"><span>Group: 分类 (2 Levels)</span> <span>BP_Change: 连续数值</span></div>
</div>
</div>`;
stepsBox.insertAdjacentHTML('beforeend', step2);
scrollToBottom();
}, 2000);
// Step 3: 路径规划器 & 最终 SAP 卡片
setTimeout(() => {
const step3 = `
<div class="agent-step relative pl-8 slide-up">
<div class="absolute left-0 top-0.5 w-6 h-6 rounded-full bg-purple-100 text-purple-600 flex items-center justify-center z-10"><i class="fa-solid fa-route text-[10px]"></i></div>
<div class="text-sm font-bold text-slate-800 mb-2">路径规划器 (Planner) <span class="text-xs font-normal text-slate-400 ml-2">决策表匹配</span></div>
<!-- SAP 卡片:展示 workflow_steps[] -->
<div class="bg-white border border-purple-200 rounded-xl overflow-hidden shadow-sm">
<div class="bg-purple-50 px-3 py-2 border-b border-purple-100 text-xs font-bold text-purple-800 flex items-center gap-2">
<i class="fa-solid fa-clipboard-list"></i> 统计分析计划书 (SAP Workflow)
</div>
<div class="p-4 space-y-3">
<div class="flex gap-2 items-start text-sm">
<div class="w-5 h-5 rounded bg-slate-100 text-slate-500 flex items-center justify-center text-xs font-bold shrink-0">1</div>
<div><span class="font-semibold text-slate-700">数据核验:</span> 诊断缺失值与异常分布。</div>
</div>
<div class="flex gap-2 items-start text-sm">
<div class="w-5 h-5 rounded bg-slate-100 text-slate-500 flex items-center justify-center text-xs font-bold shrink-0">2</div>
<div><span class="font-semibold text-slate-700">统计护栏:</span> 执行 Shapiro-Wilk 正态性检验。</div>
</div>
<div class="flex gap-2 items-start text-sm bg-blue-50 p-2 rounded border border-blue-100">
<div class="w-5 h-5 rounded bg-blue-600 text-white flex items-center justify-center text-xs font-bold shrink-0">3</div>
<div>
<span class="font-semibold text-blue-800">核心推断:</span> 独立样本 T 检验。<br>
<span class="text-xs text-blue-600">※ 降级策略:若正态性检验 P<0.05,则使用 Wilcoxon 秩和检验。</span>
</div>
</div>
</div>
<div class="p-3 bg-slate-50 border-t border-slate-100 flex justify-end">
<button onclick="runExecutorPipeline(this)" class="bg-purple-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-purple-700 transition flex items-center gap-2 shadow-sm">
<i class="fa-solid fa-play text-xs"></i> 确认并执行此流水线
</button>
</div>
</div>
</div>`;
stepsBox.insertAdjacentHTML('beforeend', step3);
scrollToBottom();
}, 3500);
}, 500);
}
// ==========================================
// 阶段 4 & 5: 执行器与结论生成
// ==========================================
function runExecutorPipeline(btn) {
// 左侧按钮状态变更
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> 调度引擎中...';
btn.classList.replace('bg-purple-600', 'bg-slate-400');
btn.classList.replace('hover:bg-purple-700', 'hover:bg-slate-400');
btn.disabled = true;
// 展开右侧工作区
openWorkspace();
// 模拟 Executor 的 step_results[] 动态输出
const logBox = document.getElementById('trace-logs');
logBox.innerHTML = '';
setTimeout(() => { addTraceLog(logBox, 'text-slate-400', 'fa-database', '[Step 1] 读取并加载预处理数据...', 'done'); }, 600);
setTimeout(() => { addTraceLog(logBox, 'text-blue-500', 'fa-shield', '[Step 2] 护栏: 执行 Shapiro-Wilk 检验...', 'spin'); }, 1500);
setTimeout(() => {
logBox.lastElementChild.querySelector('.fa-spin').classList.replace('fa-spinner', 'fa-times-circle');
logBox.lastElementChild.querySelector('.fa-spin').classList.remove('fa-spin');
logBox.lastElementChild.querySelector('.fa-times-circle').classList.replace('text-blue-500', 'text-red-500');
logBox.lastElementChild.querySelector('.log-text').innerHTML = '[Step 2] 护栏: 正态性检验未通过 <span class="bg-red-100 text-red-600 px-1 rounded ml-1 font-bold">P=0.002</span>';
addTraceLog(logBox, 'text-amber-500', 'fa-code-branch', '[Router] 触发流程编排降级机制: 指向 Wilcoxon 方法', 'done');
}, 2800);
setTimeout(() => { addTraceLog(logBox, 'text-blue-500', 'fa-calculator', '[Step 3] 核心: 运行 wilcox.test()...', 'spin'); }, 3800);
setTimeout(() => {
logBox.lastElementChild.querySelector('.fa-spin').classList.replace('fa-spinner', 'fa-check-circle');
logBox.lastElementChild.querySelector('.fa-spin').classList.remove('fa-spin');
logBox.lastElementChild.querySelector('.fa-check-circle').classList.replace('text-blue-500', 'text-green-500');
logBox.lastElementChild.querySelector('.log-text').innerHTML = '[Step 3] 核心: wilcox.test() 计算成功 (W=2845.5, P<0.001)';
document.getElementById('exec-spinner').innerHTML = '<i class="fa-solid fa-check-circle text-green-500"></i>';
document.getElementById('exec-subtitle').innerText = "编排引擎执行完毕,等待结论生成...";
// 启动结论生成器 (Step 5)
startCriticAgent(btn);
}, 5000);
}
function addTraceLog(container, colorClass, iconClass, text, state) {
const div = document.createElement('div');
div.className = 'flex items-center gap-4 fade-in bg-white p-2 rounded-lg border border-slate-100 shadow-sm';
let iconState = state === 'spin' ? 'fa-spinner fa-spin' : iconClass;
div.innerHTML = `
<div class="w-6 h-6 rounded-full bg-slate-50 flex items-center justify-center z-10 border border-slate-200">
<i class="fa-solid ${iconState} ${colorClass} text-[10px]"></i>
</div>
<span class="log-text font-medium text-slate-700">${text}</span>
`;
container.appendChild(div);
}
function startCriticAgent(btn) {
const criticSection = document.getElementById('critic-section');
criticSection.classList.remove('hidden');
criticSection.classList.add('fade-in');
// 模拟 LLM 思考生成的时间
setTimeout(() => {
document.getElementById('report-typing').classList.add('hidden');
document.getElementById('report-final').classList.remove('hidden');
document.getElementById('report-final').classList.add('fade-in');
// 左侧对话区反馈完成
btn.innerHTML = '<i class="fa-solid fa-check"></i> 流程已闭环';
btn.classList.replace('bg-slate-400', 'bg-green-500');
btn.classList.replace('hover:bg-slate-400', 'hover:bg-green-600');
const finalMsg = `
<div class="flex gap-4 slide-up w-full mt-4 border-t border-slate-100 pt-6">
<div class="w-8 h-8 rounded-full bg-slate-800 flex items-center justify-center flex-shrink-0 mt-0.5"><i class="fa-solid fa-check-double text-white text-xs"></i></div>
<div class="bg-green-50 border border-green-200 text-green-800 p-4 rounded-2xl rounded-tl-none shadow-sm text-sm w-full">
<b>综合报告已生成!</b><br>
5 大智能体协作完毕。请在右侧工作区查看包含方法学说明的完整论文级报告。
</div>
</div>`;
chatAnchor.insertAdjacentHTML('beforebegin', finalMsg);
scrollToBottom();
// 更新右侧 Badge
document.getElementById('workspace-badge').innerText = "Completed";
document.getElementById('workspace-badge').className = "px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-green-100 text-green-700 border border-green-200";
}, 2500);
}
function removeData(e) {
e.stopPropagation();
document.getElementById('data-mount-zone').classList.add('hidden');
document.getElementById('data-mount-zone').classList.remove('flex');
document.getElementById('btn-upload').classList.remove('opacity-50', 'pointer-events-none');
}
function scrollToBottom() {
setTimeout(() => {
const c = document.getElementById('chat-container');
c.scrollTo({ top: c.scrollHeight, behavior: 'smooth' });
}, 50);
}
</script>
</body>
</html>