feat(ssa): Complete V11 UI development and frontend-backend integration - Pixel-perfect V11 UI, multi-task support, Word export, input overlay fix, code cleanup. MVP Phase 1 core 95% complete.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
# SSA智能统计分析模块 - 当前状态与开发指南
|
||||
|
||||
> **文档版本:** v1.5
|
||||
> **文档版本:** v1.6
|
||||
> **创建日期:** 2026-02-18
|
||||
> **最后更新:** 2026-02-19
|
||||
> **最后更新:** 2026-02-20
|
||||
> **维护者:** 开发团队
|
||||
> **当前状态:** 🎉 **T 检验端到端测试通过!MVP Phase 1 核心完成 85%**
|
||||
> **当前状态:** 🎉 **V11 UI 前后端联调测试通过!MVP Phase 1 核心完成 95%**
|
||||
> **文档目的:** 快速了解SSA模块状态,为新AI助手提供上下文
|
||||
>
|
||||
> **🎉 里程碑(2026-02-18):**
|
||||
@@ -21,6 +21,15 @@
|
||||
> - ✅ **文件名动态生成**:`{toolName}_{dataName}_{MMDD}_{HHmm}.R`
|
||||
> - ✅ **前端模块激活**:智能统计分析入口可用
|
||||
> - ✅ **用户会话隔离**:不同用户数据正确隔离
|
||||
>
|
||||
> **🎉 V11 UI 前后端联调测试通过(2026-02-20):**
|
||||
> - ✅ **🎉 V11 UI 像素级还原**:Gemini 风格对话界面,全屏沉浸式体验
|
||||
> - ✅ **多任务支持**:单会话内可执行多个分析任务,独立管理状态
|
||||
> - ✅ **单页滚动布局**:分析计划 → 执行日志 → 分析结果,步骤进度条导航
|
||||
> - ✅ **Word 报告导出**:完整统计报告,包含数据描述、方法、结果、图表、结论
|
||||
> - ✅ **输入框遮挡修复**:Scroll Spacer 方案,解决 Flexbox padding-bottom 问题
|
||||
> - ✅ **代码清理完成**:删除旧版 V8/V9 组件,精简代码结构
|
||||
> - ✅ **前后端完整联调**:数据上传 → 计划生成 → 执行分析 → 结果展示 → 报告导出
|
||||
>
|
||||
> **🆕 v1.5 新增特性(专家配置体系):**
|
||||
> - 🆕 **统计决策表**:(Goal, Y, X, Design) 四维匹配精准选工具,替代简单 RAG
|
||||
@@ -42,7 +51,7 @@
|
||||
| **商业价值** | ⭐⭐⭐⭐⭐ 极高 |
|
||||
| **独立性** | ⭐⭐⭐⭐ 高(可独立使用,也可与其他模块协同) |
|
||||
| **目标用户** | 临床研究人员、生物统计师 |
|
||||
| **开发状态** | 🚀 **MVP Phase 1 开发中(Week 1 完成 ~80%)** |
|
||||
| **开发状态** | 🚀 **MVP Phase 1 开发中(Week 1 完成 ~95%)** |
|
||||
|
||||
### 核心目标
|
||||
|
||||
|
||||
102
docs/03-业务模块/SSA-智能统计分析/03-UI设计/SSA-Pro UX方案对比与技术选型报告.md
Normal file
102
docs/03-业务模块/SSA-智能统计分析/03-UI设计/SSA-Pro UX方案对比与技术选型报告.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# **SSA-Pro 智能工作台 V7 vs V8 深度对比与技术选型报告**
|
||||
|
||||
**分析对象:** V7 (单流沉浸式 \- 方案A) vs V8 (双屏工作台 \- 方案C)
|
||||
|
||||
**编写目的:** 为前端研发团队提供 UI/UX 选型依据与技术实现路径指导。
|
||||
|
||||
**核心结论:** **强烈推荐采用 V8(双屏版)** 作为 SSA-Pro 的最终形态,并**深度复用 AIA 模块已有的双屏组件资产**。
|
||||
|
||||
## **1\. 核心观点与倾向性建议**
|
||||
|
||||
**我个人的强烈倾向是:选择 V8(双屏版)。**
|
||||
|
||||
### **为什么 V8 更符合用户需求?**
|
||||
|
||||
1. **科研场景的天然属性**:医生做统计分析,不是为了“聊天”,而是为了“要结果(图/表/代码)”。V7 会把复杂的统计图表挤在狭窄的聊天气泡里,阅读体验极差;而 V8 给了结果最大的展示空间(右侧 60% 屏幕)。
|
||||
2. **多线程工作能力**:在 V8 中,用户可以看着右侧的“分析计划书 (SAP)”,在左侧继续对 AI 提出修改意见(“把正态性检验去掉”)。而在 V7 中,一旦继续聊天,计划书就被顶到上面看不见了。
|
||||
3. **专业信任感**:V8 的布局极具“现代桌面软件”的既视感(类似于 RStudio、VS Code 或 Notion),这能极大地提升医生对该系统“严谨性”的信任。
|
||||
|
||||
## **2\. 体验与需求契合度对比 (UX Comparison)**
|
||||
|
||||
| 维度 | V7 (单流沉浸式 \- 类似 ChatGPT) | V8 (双屏工作台 \- 类似 Claude Artifacts) | 胜出者 |
|
||||
| :---- | :---- | :---- | :---- |
|
||||
| **空间利用率** | 低。大量屏幕两侧留白,宽表必须横向滚动。 | **极高**。完美适配 1080p 以上宽屏,图表展示极其舒展。 | 🏆 V8 |
|
||||
| **上下文记忆** | 差。旧卡片会被新消息顶出屏幕。 | **极佳**。右侧工作区状态独立,左侧聊天随便滚,右侧不动。 | 🏆 V8 |
|
||||
| **代码/文档阅读** | 差。需弹窗或在狭窄气泡内阅读。 | **极佳**。右侧面板就是完美的文档/代码阅读器。 | 🏆 V8 |
|
||||
| **移动端适配** | **极佳**。天然的移动端流式布局。 | 差。双屏在手机上无法并排显示。 | 🏆 V7 |
|
||||
| **学习成本** | 极低(所有人都会用微信)。 | 较低(需理解左右协同逻辑)。 | 🏆 V7 |
|
||||
|
||||
*结论:除非我们首发主打手机端(这在医学统计场景几乎不可能),否则 PC 端的 V8 完胜。*
|
||||
|
||||
## **3\. 实现难度与技术路径差异 (Implementation Complexity)**
|
||||
|
||||
最初评估时,V8 的代价是前端开发难度直线上升。**但鉴于本平台 AIA 模块(Protocol Agent)已成功交付类似架构,V8 的实际落地成本已大幅降低。**
|
||||
|
||||
### **3.1 状态管理 (State Management) 的差异**
|
||||
|
||||
* **V7 (简单)**:状态是局部的、线性的。整个页面就是一个 MessageList\[\] 数组。每个 AI 回复的卡片组件,只需要接收自己那一条的 props 即可渲染,组件之间不需要通信。
|
||||
* **V8 (复杂但已攻克)**:状态是全局的、分离的。左侧聊天框里的点击动作必须触发右侧面板的状态切换(跨面板状态同步)。目前可直接复用平台已有的状态管理最佳实践。
|
||||
|
||||
### **3.2 渲染机制 (Rendering Logic) 的差异**
|
||||
|
||||
* **V7 (流式)**:新消息永远 push 到列表最下方,浏览器自动处理滚动。
|
||||
* **V8 (插槽式)**:右侧是一个动态插槽(Dynamic Slot / View)。需要维护一个 ActiveArtifact 状态,根据左侧的焦点,不断替换右侧挂载的 React 组件(是渲染 SAPViewer 还是 ResultViewer?)。
|
||||
|
||||
## **4\. V8 架构所需的核心技术栈与路线 (Tech Stack Support)**
|
||||
|
||||
要完美实现 V8,前端团队必须引入以下技术路线:
|
||||
|
||||
### **4.1 全局状态管理库 (必须)**
|
||||
|
||||
* **推荐**:Zustand 或 Redux Toolkit。
|
||||
* **用途**:定义一个全局 Store,包含:
|
||||
interface WorkspaceStore {
|
||||
chatHistory: Message\[\]; // 控制左侧
|
||||
activePane: 'empty' | 'sap' | 'execution' | 'result'; // 控制右侧视图
|
||||
currentArtifactData: any; // 右侧正在展示的数据 (JSON)
|
||||
setPane: (pane, data) \=\> void; // 左侧按钮调用的方法
|
||||
}
|
||||
|
||||
### **4.2 布局伸缩库 (推荐与复用)**
|
||||
|
||||
* **推荐**:直接复用 AIA 模块已实现的 ResizableSplitPane 组件。
|
||||
* **用途**:允许用户拖拽中间的分割线,自由调整左侧(Chat)和右侧(Workspace)的宽度比例(这是高级桌面工具的标配)。
|
||||
|
||||
### **4.3 渲染优化技术 (关键)**
|
||||
|
||||
* **推荐**:React.memo / useMemo。
|
||||
* **用途**:隔离左右两侧的渲染。当左侧用户正在打字时,绝不能导致右侧包含几千个 DOM 节点的复杂数据表格发生不必要的重渲染(Re-render),否则输入会严重卡顿。
|
||||
|
||||
## **5\. 跨模块资产复用分析:AIA Protocol Agent 带来的架构红利**
|
||||
|
||||
通过审查 00-模块当前状态与开发指南.md,我们发现 **Protocol Agent 的 V3.1 MVP 已经完美实现了“聊天+文档”的双屏形态**。这为 SSA-Pro 的 V8 方案提供了极其宝贵的基础设施。
|
||||
|
||||
### **5.1 Protocol Agent 布局与 SSA V8 布局的对比度**
|
||||
|
||||
| 核心特性 | Protocol Agent (AIA) 现有实现 | SSA-Pro (V8) 需求期望 | 一致性评估 |
|
||||
| :---- | :---- | :---- | :---- |
|
||||
| **整体架构** | 左侧 Chat \+ 右侧 DocumentPanel (A4纸张预览) | 左侧 Chat \+ 右侧 Artifacts (图表/表格/代码) | 🟢 **高度一致**。同属 "Left Brain, Right Hand" 模式。 |
|
||||
| **面板控制** | ResizableSplitPane 动态双面板布局,可拖拽调整 | 需要中间可拖拽的分割线调整视窗大小 | 🟢 **完全吻合**。可 100% 照搬该组件。 |
|
||||
| **状态联动** | 5阶段流程状态面板折叠/展开 | 计划/执行/结果等阶段状态栏的切换 | 🟡 **逻辑相似**。基于 Zustand 的联动机制可复用。 |
|
||||
| **结果导出** | Word 导出服务集成 \+ 一键下载 | 代码下载 \+ 报告导出 | 🟢 **直接复用**。Python 的 pypandoc 导出服务可直接为 SSA 的 SAP 文档导出服务。 |
|
||||
|
||||
### **5.2 复用策略与哪个“更好”?**
|
||||
|
||||
**结论:它们不是互斥的选项,而是“前辈”与“后继者”的关系。复用 Protocol Agent 的架构来实现 V8 是最优解。**
|
||||
|
||||
1. **底层 Layout 无缝复用**:前端团队**无需从零开发**左右分屏和拖拽逻辑,直接将 Protocol Agent 中经过 V3.1 验证的 ResizableSplitPane 提取到 frontend-v2/src/shared/components/Layout 供全局使用。
|
||||
2. **右侧面板 (Right Pane) 的多态化扩展**:
|
||||
* Protocol Agent 的右侧是 DocumentPanel(渲染 Markdown 和 A4 纸效果)。
|
||||
* SSA-Pro V8 只需要在此基础上扩展右侧面板的内容类型:除了渲染 SAP 文档,新增 ChartPanel(渲染 Base64 图表)和 TablePanel(渲染三线表)即可。
|
||||
3. **消除技术债务与风险**:原本 V8 最大的风险在于“复杂的交互状态管理和窗口重绘性能”,既然 AIA 团队已经在 \~8500 行代码的迭代中解决了滚动条显示、状态联动等 Bug,SSA 团队直接站在巨人的肩膀上,**将原本需要 2 周的基建时间压缩至 2 天**。
|
||||
|
||||
## **6\. 最终开发实施建议**
|
||||
|
||||
1. **确立 V8 为唯一方向**:全面转向左右双屏架构,放弃气泡内展示复杂图表的念头。
|
||||
2. **组件下沉与复用 (Day 1 任务)**:前端负责同学第一步应与 AIA 团队对齐,将 ResizableSplitPane 等双屏布局基建下沉为系统级 Common 组件。
|
||||
3. **组件解耦开发**:
|
||||
* 同学 A 专门负责开发右侧的 ResultTable、PlotViewer、ExecutionTree 组件(只需接收 JSON props 即可,纯展示组件)。
|
||||
* 同学 B 复用 AIStreamChat 组件负责左侧的指令解析。
|
||||
* 最后通过全局 Store 将两人拼装在一起。
|
||||
|
||||
**总结:V8 难在起步的架构搭建,但由于 Protocol Agent 已经替我们蹚平了道路,我们获得了巨大的“后发优势”。实施 V8 架构不仅风险极低,而且能保证整个 AI Clinical Research 平台(从写方案到做统计)的 UI 体验高度统一、专业感拉满。**
|
||||
600
docs/03-业务模块/SSA-智能统计分析/03-UI设计/V11.html
Normal file
600
docs/03-业务模块/SSA-智能统计分析/03-UI设计/V11.html
Normal file
@@ -0,0 +1,600 @@
|
||||
<!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 智能统计工作台 V11.0 (V9 Visuals + V10 Features)</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 阴影 (V9 风格) */
|
||||
.artifact-shadow { box-shadow: 0 4px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); }
|
||||
|
||||
/* 模态框背景模糊 */
|
||||
.modal-backdrop { backdrop-filter: blur(4px); }
|
||||
</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>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- 左侧:Gemini 交互 + V9 视觉的抽屉侧边栏 -->
|
||||
<!-- ========================================== -->
|
||||
<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">
|
||||
|
||||
<!-- 顶部:汉堡菜单与 Logo -->
|
||||
<div class="h-16 flex items-center px-3 flex-shrink-0 overflow-hidden whitespace-nowrap">
|
||||
<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-chart-simple text-xs"></i>
|
||||
</div>
|
||||
<span class="font-bold text-slate-800 tracking-tight text-lg">SSA-Pro</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-regular fa-message text-blue-500"></i> 心血管药物研究
|
||||
</button>
|
||||
<button class="w-full text-left px-3 py-2.5 rounded-lg hover:bg-slate-200 text-slate-600 text-sm truncate flex items-center gap-3 transition">
|
||||
<i class="fa-regular fa-message text-slate-400"></i> 生存分析方案咨询
|
||||
</button>
|
||||
<button class="w-full text-left px-3 py-2.5 rounded-lg hover:bg-slate-200 text-slate-600 text-sm truncate flex items-center gap-3 transition">
|
||||
<i class="fa-regular fa-message text-slate-400"></i> 基线特征 Table 1
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部设置图标 -->
|
||||
<div class="mt-auto p-3 flex-shrink-0 border-t border-slate-200">
|
||||
<button id="settings-btn" class="w-10 h-10 rounded-lg hover:bg-slate-200 flex items-center justify-center text-slate-500 transition-all duration-300 overflow-hidden whitespace-nowrap">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
<span class="sidebar-text hidden ml-3 font-medium text-sm">配置中心</span>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- 动态主容器 (Chat + Workspace) -->
|
||||
<!-- ========================================== -->
|
||||
<div class="flex-1 flex overflow-hidden relative bg-white" id="main-container">
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- 左半部分 (或全屏):对话区 (Chat Pane) -->
|
||||
<!-- 采用了 V10 的居中布局,V9 的视觉卡片 -->
|
||||
<!-- ========================================== -->
|
||||
<section id="chat-pane" class="flex-1 flex flex-col h-full transition-all duration-500 ease-in-out relative bg-[#f8fafc]">
|
||||
|
||||
<!-- Chat Header (V9 风格) -->
|
||||
<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">心血管药物疗效研究</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> R Engine 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 pb-6">
|
||||
<!-- 内部定宽容器,保证阅读体验 -->
|
||||
<div class="w-full max-w-3xl space-y-8 min-h-full pb-32">
|
||||
|
||||
<!-- 欢迎语 (V9 风格) -->
|
||||
<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">
|
||||
你好!我是 SSA-Pro 智能统计助手。<br>你可以直接描述研究目标,我将为你生成分析方案;或者点击下方 📎 <b>上传数据文件</b>,我们将直接开始分析。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Msg (V9 风格) -->
|
||||
<div class="flex gap-4 flex-row-reverse slide-up" style="animation-delay: 0.1s">
|
||||
<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 leading-relaxed shadow-md">
|
||||
我在研究一种新药,收集了实验组和对照组患者的血压下降值数据。我想知道新药是否有效。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Msg (无数据纯咨询) -->
|
||||
<div class="flex gap-4 slide-up" style="animation-delay: 0.2s">
|
||||
<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 border border-slate-200 text-slate-700 p-4 rounded-2xl rounded-tl-none text-sm leading-relaxed shadow-sm">
|
||||
理解了。针对“两组独立样本疗效对比”,如果是连续数值型变量,通常推荐使用 <b>独立样本 T 检验 (T-Test)</b>。<br><br>
|
||||
💡 <b>请上传数据文件</b>,我将根据实际列名生成可执行的方案并展示在右侧工作区。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chat-anchor"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Input (V9 质感的悬浮输入框) -->
|
||||
<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-12 z-20">
|
||||
<div class="w-full max-w-3xl relative">
|
||||
|
||||
<!-- V9 风格:包裹挂载区和输入框的容器 -->
|
||||
<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>
|
||||
<span class="text-[10px] text-slate-400 mt-0.5 leading-none">120 rows • 45KB</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="simulateUpload()" 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 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 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 class="text-center mt-2">
|
||||
<span class="text-[10px] text-slate-400">AI 可能会犯错,请核实生成的统计结论。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- 右半部分:动态工作区 (Workspace Pane) - 保持 V9 完美体验 -->
|
||||
<!-- ========================================== -->
|
||||
<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-blue-100 text-blue-700 border border-blue-200">
|
||||
Planning
|
||||
</span>
|
||||
<span id="workspace-title" class="text-sm font-semibold text-slate-700">统计分析计划 (SAP)</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button id="btn-code" class="hidden w-8 h-8 bg-white border border-slate-200 text-slate-500 rounded-lg hover:text-blue-600 transition shadow-sm flex items-center justify-center" title="查看源代码" onclick="openModal('code-modal')">
|
||||
<i class="fa-brands fa-r-project"></i>
|
||||
</button>
|
||||
<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">
|
||||
|
||||
<!-- 视图 1:SAP 文档 -->
|
||||
<div id="view-sap" class="p-8 w-full fade-in">
|
||||
<h1 class="text-xl font-bold text-slate-900 border-b border-slate-200 pb-4 mb-6">研究课题:新药治疗高血压疗效对比</h1>
|
||||
|
||||
<div class="space-y-6">
|
||||
<section>
|
||||
<h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">1. 推荐统计方法</h3>
|
||||
<div class="border border-blue-100 rounded-xl overflow-hidden">
|
||||
<div class="bg-blue-50 px-4 py-3 border-b border-blue-100 font-medium text-blue-800 text-sm flex items-center gap-2">
|
||||
<i class="fa-solid fa-star text-amber-500"></i> 首选:独立样本 T 检验 (Independent T-Test)
|
||||
</div>
|
||||
<div class="p-4 text-sm text-slate-700 bg-white grid grid-cols-2 gap-4">
|
||||
<div class="bg-slate-50 p-3 rounded-lg border border-slate-100">
|
||||
<div class="text-xs text-slate-400 mb-1">自变量 (X)</div>
|
||||
<div class="font-mono bg-slate-200/50 px-1 rounded inline-block">Group</div> <span class="text-xs text-slate-500 ml-1">(分类)</span>
|
||||
</div>
|
||||
<div class="bg-slate-50 p-3 rounded-lg border border-slate-100">
|
||||
<div class="text-xs text-slate-400 mb-1">因变量 (Y)</div>
|
||||
<div class="font-mono bg-slate-200/50 px-1 rounded inline-block">BP_Change</div> <span class="text-xs text-slate-500 ml-1">(数值)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-2">2. 统计护栏与执行策略</h3>
|
||||
<ul class="text-sm text-slate-700 space-y-3 pl-1">
|
||||
<li class="flex items-start gap-2 bg-slate-50 p-3 rounded-lg border border-slate-100">
|
||||
<i class="fa-solid fa-shield text-blue-500 mt-1"></i>
|
||||
<div>
|
||||
<b>正态性假设检验 (Shapiro-Wilk)</b><br>
|
||||
<span class="text-xs text-slate-500 mt-1 block">系统将在核心计算前执行检查。若 P < 0.05,将触发降级策略,自动改用 <b>Wilcoxon 秩和检验</b> 以保证结果严谨性。</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- 执行按钮区 -->
|
||||
<div class="mt-8 pt-6 border-t border-slate-100 flex justify-end">
|
||||
<button onclick="runAnalysis(this)" id="btn-run" class="bg-blue-600 text-white px-6 py-2.5 rounded-lg font-medium hover:bg-blue-700 transition shadow-md flex items-center gap-2">
|
||||
<i class="fa-solid fa-play text-xs"></i> 开始执行分析
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视图 2:动态执行日志 -->
|
||||
<div id="view-execution" class="absolute inset-0 bg-white p-8 hidden flex-col items-center justify-center">
|
||||
<div class="w-full max-w-lg">
|
||||
<div class="flex items-center gap-3 mb-6 justify-center">
|
||||
<i class="fa-solid fa-circle-notch fa-spin text-blue-600 text-2xl"></i>
|
||||
<h3 class="font-bold text-slate-800 text-lg">正在调用云端 R 引擎...</h3>
|
||||
</div>
|
||||
<!-- 酷炫的终端样式 -->
|
||||
<div class="bg-[#0f172a] rounded-xl p-5 font-mono text-xs text-slate-300 shadow-2xl relative overflow-hidden h-64 border border-slate-700">
|
||||
<div class="absolute left-[31px] top-6 bottom-6 w-px bg-slate-700"></div>
|
||||
<div id="trace-logs" class="space-y-4 relative z-10">
|
||||
<!-- 动态插入日志 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视图 3:结果报告 -->
|
||||
<div id="view-result" class="p-8 w-full hidden">
|
||||
<!-- 核心结论 -->
|
||||
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 p-5 rounded-xl mb-8 flex gap-4 items-start shadow-sm">
|
||||
<i class="fa-solid fa-lightbulb text-blue-500 text-xl mt-0.5"></i>
|
||||
<div>
|
||||
<h4 class="font-bold text-slate-800 text-sm mb-1">AI 统计解读</h4>
|
||||
<p class="text-sm text-slate-700 leading-relaxed">
|
||||
结果表明,新药组(Drug)的血压下降幅度显著大于对照组(Placebo)。差异具有统计学意义 (<span class="text-red-600 font-bold">P < 0.001</span>)。
|
||||
<br><span class="text-xs text-slate-500 mt-1 inline-block">* 注:因数据未通过正态性检验,本结果由自动降级后的 Wilcoxon 秩和检验得出。</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="mb-8">
|
||||
<h4 class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3">Table 1. 血压下降值比较</h4>
|
||||
<div class="border border-slate-200 rounded-lg overflow-hidden">
|
||||
<table class="sci-table bg-white">
|
||||
<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 class="hover:bg-slate-50">
|
||||
<td>Drug</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.001 **</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td>Placebo</td>
|
||||
<td>60</td>
|
||||
<td>8.2 [6.5 - 10.4]</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表 -->
|
||||
<div>
|
||||
<h4 class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3">Figure 1. 分布可视化</h4>
|
||||
<div class="border border-slate-200 rounded-xl p-6 bg-slate-50 flex justify-center shadow-sm">
|
||||
<div class="relative w-full max-w-sm h-56 flex items-end justify-center gap-16 pb-8 border-b-2 border-l-2 border-slate-400">
|
||||
<div class="w-16 h-40 bg-blue-500/80 border-2 border-blue-600 rounded-sm shadow-sm relative"><span class="absolute -bottom-6 w-full text-center text-xs font-bold text-slate-600">Drug</span></div>
|
||||
<div class="w-16 h-20 bg-slate-300/80 border-2 border-slate-400 rounded-sm shadow-sm relative"><span class="absolute -bottom-6 w-full text-center text-xs font-bold text-slate-600">Placebo</span></div>
|
||||
<div class="absolute top-6 left-[30%] right-[30%] h-3 border-t-2 border-l-2 border-r-2 border-slate-800"></div>
|
||||
<div class="absolute top-0 w-full text-center text-sm font-bold text-slate-800">P < 0.001</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- R代码模态框 (保留 V9 风格) -->
|
||||
<div id="code-modal" class="fixed inset-0 z-50 hidden flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm" onclick="closeModal('code-modal')"></div>
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[80vh] flex flex-col relative z-10 pop-in m-4 border border-slate-200">
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100 bg-slate-50 rounded-t-2xl">
|
||||
<h3 class="font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-brands fa-r-project text-blue-600 text-xl"></i> R 源代码交付
|
||||
</h3>
|
||||
<button onclick="closeModal('code-modal')" class="text-slate-400 hover:text-slate-700 w-8 h-8 rounded-full hover:bg-slate-200 transition flex items-center justify-center">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6 overflow-y-auto flex-1 bg-white">
|
||||
<pre class="bg-slate-900 text-slate-50 p-5 rounded-xl overflow-x-auto shadow-inner text-xs font-mono leading-relaxed"><code><span class="text-slate-500"># ------------------------------------------------</span>
|
||||
<span class="text-slate-500"># SSA-Pro 生成代码: 独立样本差异分析</span>
|
||||
<span class="text-slate-500"># ------------------------------------------------</span>
|
||||
<span class="text-pink-400">library</span>(ggplot2)
|
||||
|
||||
<span class="text-slate-500"># 1. 加载数据</span>
|
||||
df <- <span class="text-blue-300">read.csv</span>(<span class="text-green-300">"BP_Trial_Data.csv"</span>)
|
||||
|
||||
<span class="text-slate-500"># 2. 核心计算 (自动降级至 Wilcoxon)</span>
|
||||
res <- <span class="text-blue-300">wilcox.test</span>(BP_Change ~ Group, data = df)
|
||||
<span class="text-blue-300">print</span>(res)</code></pre>
|
||||
</div>
|
||||
<div class="px-6 py-4 border-t border-slate-100 flex justify-end gap-3 bg-slate-50 rounded-b-2xl">
|
||||
<button class="px-4 py-2 text-sm font-medium text-white bg-slate-800 rounded-lg hover:bg-slate-700 transition shadow-sm" onclick="showToast('R 脚本已下载', 'success'); closeModal('code-modal');">
|
||||
<i class="fa-solid fa-download mr-1"></i> 下载 .R 文件
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const chatContainer = document.getElementById('chat-container');
|
||||
const anchor = document.getElementById('chat-anchor');
|
||||
const workspacePane = document.getElementById('workspace-pane');
|
||||
let isSidebarExpanded = false;
|
||||
|
||||
// 1. 抽屉菜单切换
|
||||
function toggleSidebar() {
|
||||
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');
|
||||
const settingsBtn = document.getElementById('settings-btn');
|
||||
|
||||
isSidebarExpanded = !isSidebarExpanded;
|
||||
|
||||
if(isSidebarExpanded) {
|
||||
// 展开 w-64
|
||||
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');
|
||||
|
||||
settingsBtn.classList.replace('w-10', 'w-full');
|
||||
settingsBtn.classList.replace('justify-center', 'justify-start');
|
||||
settingsBtn.classList.add('px-3');
|
||||
|
||||
historyIconBtn.classList.add('hidden');
|
||||
historyList.classList.remove('hidden');
|
||||
setTimeout(() => historyList.classList.replace('opacity-0', 'opacity-100'), 150);
|
||||
|
||||
} else {
|
||||
// 收缩 w-16
|
||||
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');
|
||||
|
||||
settingsBtn.classList.replace('w-full', 'w-10');
|
||||
settingsBtn.classList.replace('justify-start', 'justify-center');
|
||||
settingsBtn.classList.remove('px-3');
|
||||
|
||||
historyList.classList.add('hidden');
|
||||
historyList.classList.replace('opacity-100', 'opacity-0');
|
||||
historyIconBtn.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 工作区拉出与收起
|
||||
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');
|
||||
}
|
||||
|
||||
// 3. 模拟上传与执行交互
|
||||
function simulateUpload() {
|
||||
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');
|
||||
showToast('数据读取成功,正在分析结构...', 'success');
|
||||
|
||||
setTimeout(() => {
|
||||
const aiResponseHTML = `
|
||||
<div class="flex gap-4 slide-up w-full">
|
||||
<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 border border-slate-200 text-slate-700 p-4 rounded-2xl rounded-tl-none shadow-sm flex-1 max-w-2xl">
|
||||
<div class="text-sm leading-relaxed mb-3">
|
||||
<i class="fa-solid fa-bolt text-amber-500 mr-1"></i> <b>数据已挂载</b>。我已经为您规划好了统计分析计划书 (SAP)。
|
||||
</div>
|
||||
<button onclick="openWorkspace()" class="bg-blue-50 text-blue-700 border border-blue-200 px-4 py-2.5 rounded-xl w-full flex items-center justify-between hover:bg-blue-100 transition">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-white rounded-lg flex items-center justify-center text-blue-600 shadow-sm"><i class="fa-solid fa-file-signature"></i></div>
|
||||
<div class="text-left">
|
||||
<div class="text-sm font-bold">查看分析计划 (SAP)</div>
|
||||
<div class="text-[10px] text-blue-500">参数映射完成,等待执行</div>
|
||||
</div>
|
||||
</div>
|
||||
<i class="fa-solid fa-arrow-right-long text-blue-400"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
anchor.insertAdjacentHTML('beforebegin', aiResponseHTML);
|
||||
scrollToBottom();
|
||||
openWorkspace();
|
||||
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
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');
|
||||
closeWorkspace();
|
||||
}
|
||||
|
||||
function runAnalysis(btn) {
|
||||
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> 执行中...';
|
||||
btn.classList.replace('bg-blue-600', 'bg-slate-400');
|
||||
btn.classList.replace('hover:bg-blue-700', 'hover:bg-slate-400');
|
||||
btn.disabled = true;
|
||||
|
||||
document.getElementById('view-sap').classList.add('hidden');
|
||||
document.getElementById('view-execution').classList.remove('hidden');
|
||||
document.getElementById('view-execution').classList.add('flex');
|
||||
|
||||
document.getElementById('workspace-badge').innerText = 'Running';
|
||||
document.getElementById('workspace-badge').className = 'px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider bg-amber-100 text-amber-700 border border-amber-200';
|
||||
document.getElementById('workspace-title').innerText = '执行日志 Trace';
|
||||
|
||||
const execMsg = `
|
||||
<div class="flex gap-4 slide-up w-full">
|
||||
<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-amber-50 border border-amber-200 text-amber-800 p-3 rounded-2xl rounded-tl-none shadow-sm text-sm">
|
||||
<i class="fa-solid fa-circle-notch fa-spin mr-1"></i> 正在调用 R 引擎执行计算...
|
||||
</div>
|
||||
</div>`;
|
||||
anchor.insertAdjacentHTML('beforebegin', execMsg);
|
||||
scrollToBottom();
|
||||
|
||||
const logBox = document.getElementById('trace-logs');
|
||||
logBox.innerHTML = '';
|
||||
|
||||
setTimeout(() => { addLog(logBox, 'text-green-400', 'fa-check-circle', '[System] 读取数据 BP_Trial_Data.csv (N=120) 成功'); }, 500);
|
||||
setTimeout(() => { addLog(logBox, 'text-blue-400', 'fa-spinner fa-spin', '[Guardrail] 正在进行 Shapiro-Wilk 正态性检验...'); }, 1200);
|
||||
setTimeout(() => {
|
||||
logBox.lastElementChild.innerHTML = '<i class="fa-solid fa-times-circle text-red-400 bg-[#0f172a] z-10 relative"></i> <span class="ml-2">[Guardrail] 正态性检验未通过 (P=0.002 < 0.05)</span>';
|
||||
addLog(logBox, 'text-amber-400', 'fa-arrow-turn-down', '[Action] 触发护栏:自动降级为 Wilcoxon 秩和检验');
|
||||
}, 2500);
|
||||
setTimeout(() => { addLog(logBox, 'text-blue-400', 'fa-spinner fa-spin', '[Compute] 执行 wilcox.test()...'); }, 3200);
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('view-execution').classList.remove('flex');
|
||||
document.getElementById('view-execution').classList.add('hidden');
|
||||
document.getElementById('view-result').classList.remove('hidden');
|
||||
document.getElementById('view-result').classList.add('fade-in');
|
||||
|
||||
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';
|
||||
document.getElementById('workspace-title').innerText = '分析结果报告';
|
||||
document.getElementById('btn-code').classList.remove('hidden');
|
||||
|
||||
const finalMsg = `
|
||||
<div class="flex gap-4 slide-up w-full">
|
||||
<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-green-50 border border-green-200 text-green-800 p-3 rounded-2xl rounded-tl-none shadow-sm text-sm">
|
||||
<i class="fa-solid fa-check-circle mr-1"></i> 分析完成!👉 <b>请在右侧面板查看详细的结果报告和图表。</b>
|
||||
</div>
|
||||
</div>`;
|
||||
anchor.insertAdjacentHTML('beforebegin', finalMsg);
|
||||
scrollToBottom();
|
||||
|
||||
}, 4500);
|
||||
}
|
||||
|
||||
function addLog(container, color, icon, text) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex items-center fade-in text-slate-300';
|
||||
div.innerHTML = `<i class="fa-solid ${icon} ${color} bg-[#0f172a] z-10 relative"></i> <span class="ml-3">${text}</span>`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
setTimeout(() => {
|
||||
const c = document.getElementById('chat-container');
|
||||
c.scrollTo({ top: c.scrollHeight, behavior: 'smooth' });
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function openModal(id) { document.getElementById(id).classList.remove('hidden'); }
|
||||
function closeModal(id) { document.getElementById(id).classList.add('hidden'); }
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const container = document.getElementById('toast-container');
|
||||
const toast = document.createElement('div');
|
||||
let icon = '<i class="fa-solid fa-info-circle text-blue-500"></i>';
|
||||
if(type === 'success') icon = '<i class="fa-solid fa-check-circle text-green-500"></i>';
|
||||
toast.className = `bg-white border border-slate-200 shadow-lg rounded-full px-5 py-3 text-sm font-medium text-slate-700 flex items-center gap-3 slide-up`;
|
||||
toast.innerHTML = `${icon} <span>${message}</span>`;
|
||||
container.appendChild(toast);
|
||||
setTimeout(() => {
|
||||
toast.style.opacity = '0';
|
||||
toast.style.transform = 'translateY(-10px)';
|
||||
toast.style.transition = 'all 0.3s ease';
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 2500);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
470
docs/03-业务模块/SSA-智能统计分析/03-UI设计/产品原型图V8双屏版.html
Normal file
470
docs/03-业务模块/SSA-智能统计分析/03-UI设计/产品原型图V8双屏版.html
Normal file
@@ -0,0 +1,470 @@
|
||||
<!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 智能统计工作台 V8.0 (Dual-Pane Workspace)</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: #f1f5f9; 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; } }
|
||||
|
||||
/* 科学表格样式 */
|
||||
.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 20px -2px rgba(0, 0, 0, 0.05), 0 0 3px rgba(0,0,0,0.02); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="h-screen flex text-slate-800">
|
||||
|
||||
<!-- 最左侧:极简全局导航 (Icon only) -->
|
||||
<nav class="w-16 bg-slate-900 flex flex-col items-center py-4 z-30 flex-shrink-0">
|
||||
<div class="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center text-white mb-8 shadow-md">
|
||||
<i class="fa-solid fa-chart-simple"></i>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 w-full px-2">
|
||||
<button class="w-full aspect-square rounded-xl bg-slate-800 text-white flex items-center justify-center hover:bg-slate-700 transition" title="新建分析">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
</button>
|
||||
<button class="w-full aspect-square rounded-xl text-slate-400 flex items-center justify-center hover:bg-slate-800 hover:text-white transition" title="历史记录">
|
||||
<i class="fa-solid fa-clock-rotate-left"></i>
|
||||
</button>
|
||||
<button class="w-full aspect-square rounded-xl text-slate-400 flex items-center justify-center hover:bg-slate-800 hover:text-white transition" title="配置中心">
|
||||
<i class="fa-solid fa-gear"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 左半屏:对话控制区 (Chat Pane) -->
|
||||
<section class="w-[400px] lg:w-[450px] flex flex-col bg-white border-r border-slate-200 z-20 flex-shrink-0 shadow-[4px_0_24px_rgba(0,0,0,0.02)]">
|
||||
<!-- Chat Header -->
|
||||
<header class="h-16 flex items-center justify-between px-5 border-b border-slate-100 flex-shrink-0">
|
||||
<div class="font-semibold text-slate-800 text-sm">心血管药物疗效研究</div>
|
||||
<div class="flex items-center gap-2 text-[10px] font-medium text-slate-500 bg-slate-50 px-2.5 py-1 rounded-full border border-slate-200">
|
||||
<span class="status-dot status-success"></span> Engine Ready
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Chat Messages -->
|
||||
<div id="chat-container" class="flex-1 overflow-y-auto p-5 space-y-6">
|
||||
<!-- 欢迎语 -->
|
||||
<div class="flex gap-3">
|
||||
<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-slate-50 text-slate-700 p-3.5 rounded-2xl rounded-tl-none text-sm leading-relaxed border border-slate-100">
|
||||
你好!你可以直接描述研究目标,我将为你生成分析方案;或者上传数据,我们将直接开始智能分析。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Msg -->
|
||||
<div class="flex gap-3 flex-row-reverse slide-up" style="animation-delay: 0.1s">
|
||||
<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-3.5 rounded-2xl rounded-tr-none text-sm leading-relaxed shadow-sm">
|
||||
我在研究一种新药,收集了实验组和对照组患者的血压下降值数据。我想知道新药是否有效。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Msg (生成 SAP 触发器) -->
|
||||
<div class="flex gap-3 slide-up" style="animation-delay: 0.2s">
|
||||
<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 border border-slate-200 text-slate-700 p-3.5 rounded-2xl rounded-tl-none text-sm leading-relaxed shadow-sm flex flex-col gap-3">
|
||||
<span>理解了,针对“两组独立样本疗效对比”,我为您制定了统计分析计划(SAP)。</span>
|
||||
<!-- 交互触发卡片 -->
|
||||
<button class="bg-blue-50 text-blue-700 border border-blue-100 p-2.5 rounded-xl flex items-center gap-3 hover:bg-blue-100 transition text-left">
|
||||
<div class="w-8 h-8 bg-white rounded flex items-center justify-center text-blue-600 shadow-sm"><i class="fa-solid fa-file-lines"></i></div>
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold text-xs">初步分析方案.sap</div>
|
||||
<div class="text-[10px] text-blue-500/80">点击在右侧工作区查看</div>
|
||||
</div>
|
||||
<i class="fa-solid fa-angle-right text-blue-400"></i>
|
||||
</button>
|
||||
<span class="text-xs text-slate-500">确认方案无误后,请上传数据文件。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chat-anchor"></div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Input -->
|
||||
<div class="p-4 bg-white border-t border-slate-100 flex-shrink-0">
|
||||
<!-- 嵌入式挂载区 -->
|
||||
<div id="data-mount-zone" class="hidden items-center mb-2 pop-in">
|
||||
<div class="bg-slate-50 border border-slate-200 rounded-lg px-2 py-1.5 flex items-center gap-2 w-max">
|
||||
<i class="fa-solid fa-file-csv text-green-600 text-sm ml-1"></i>
|
||||
<div class="flex flex-col pr-2">
|
||||
<span class="text-xs font-bold text-slate-700">BP_Trial_Data.csv</span>
|
||||
<span class="text-[10px] text-slate-400 leading-none mt-0.5">120 rows • 45KB</span>
|
||||
</div>
|
||||
<button onclick="removeData(event)" class="text-slate-400 hover:text-red-500 px-1"><i class="fa-solid fa-xmark text-xs"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative flex items-end shadow-sm border border-slate-200 rounded-2xl bg-slate-50 focus-within:bg-white focus-within:border-blue-400 focus-within:ring-4 focus-within:ring-blue-50 transition-all p-1">
|
||||
<button onclick="simulateUpload()" id="btn-upload" class="flex-shrink-0 w-9 h-9 flex items-center justify-center rounded-full text-slate-400 hover:text-blue-600 hover:bg-blue-50 transition" title="上传数据">
|
||||
<i class="fa-solid fa-paperclip text-lg"></i>
|
||||
</button>
|
||||
<textarea class="flex-1 bg-transparent py-2.5 px-2 focus:outline-none resize-none text-sm text-slate-700" rows="1" placeholder="发送消息或上传数据..." style="min-height: 40px;"></textarea>
|
||||
<button class="flex-shrink-0 w-9 h-9 bg-slate-800 text-white rounded-full hover:bg-slate-700 transition flex items-center justify-center shadow-sm">
|
||||
<i class="fa-solid fa-arrow-up text-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 右半屏:工作区 (Artifacts Workspace) -->
|
||||
<section class="flex-1 flex flex-col relative bg-[#f1f5f9] overflow-hidden" id="workspace">
|
||||
|
||||
<!-- 空状态背景 -->
|
||||
<div id="workspace-empty" class="absolute inset-0 flex flex-col items-center justify-center text-slate-400 z-0 fade-in">
|
||||
<i class="fa-solid fa-layer-group text-6xl mb-4 opacity-20"></i>
|
||||
<p class="text-sm font-medium">分析方案、执行过程与图表将在这里展示</p>
|
||||
</div>
|
||||
|
||||
<!-- 动态 Artifact 容器 -->
|
||||
<div id="artifact-container" class="absolute inset-0 p-6 flex flex-col z-10 hidden">
|
||||
|
||||
<!-- 头部栏 (通用) -->
|
||||
<header class="flex items-center justify-between mb-4 flex-shrink-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<div id="artifact-icon" class="w-8 h-8 rounded-lg bg-blue-100 text-blue-600 flex items-center justify-center"><i class="fa-solid fa-file-lines"></i></div>
|
||||
<h2 id="artifact-title" class="text-lg font-bold text-slate-800">统计分析计划书 (SAP)</h2>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button id="btn-code" class="hidden px-3 py-1.5 bg-white border border-slate-200 text-slate-600 text-xs font-medium rounded-lg hover:bg-slate-50 transition shadow-sm"><i class="fa-brands fa-r-project mr-1 text-blue-600"></i> 查看 R 源码</button>
|
||||
<button class="px-3 py-1.5 bg-white border border-slate-200 text-slate-600 text-xs font-medium rounded-lg hover:bg-slate-50 transition shadow-sm"><i class="fa-solid fa-download mr-1"></i> 下载</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 渲染区域 (白板) -->
|
||||
<div class="flex-1 bg-white rounded-2xl artifact-shadow border border-slate-200/60 overflow-y-auto flex flex-col relative">
|
||||
|
||||
<!-- 视图 1:SAP 文档 (默认显示) -->
|
||||
<div id="view-sap" class="p-8 md:p-12 max-w-4xl mx-auto w-full fade-in">
|
||||
<h1 class="text-2xl font-bold text-slate-900 border-b-2 border-slate-900 pb-3 mb-6">研究课题:新药治疗高血压疗效对比</h1>
|
||||
|
||||
<div class="space-y-8">
|
||||
<section>
|
||||
<h3 class="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-3">1. 研究目的与假设</h3>
|
||||
<p class="text-sm text-slate-700 leading-relaxed bg-slate-50 p-4 rounded-lg border border-slate-100">探究新药在降低患者血压方面是否优于传统安慰剂/对照药。假设:两组的血压下降值存在显著差异。</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-3">2. 推荐统计方法</h3>
|
||||
<div class="border border-blue-100 rounded-lg overflow-hidden">
|
||||
<div class="bg-blue-50 px-4 py-2 border-b border-blue-100 font-medium text-blue-800 text-sm"><i class="fa-solid fa-star text-amber-500 mr-2"></i>首选:独立样本 T 检验 (Independent T-Test)</div>
|
||||
<div class="p-4 text-sm text-slate-600 space-y-2">
|
||||
<div class="flex"><span class="w-24 font-medium text-slate-500">自变量 (X):</span> <span>分组 (分类变量,2个水平)</span></div>
|
||||
<div class="flex"><span class="w-24 font-medium text-slate-500">因变量 (Y):</span> <span>血压下降值 (连续数值)</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-[10px] font-bold text-slate-400 uppercase tracking-wider mb-3">3. 统计护栏 (执行前置条件)</h3>
|
||||
<ul class="text-sm text-slate-700 space-y-3 pl-1">
|
||||
<li class="flex items-start gap-2">
|
||||
<i class="fa-solid fa-check text-green-500 mt-1"></i>
|
||||
<div><b>独立性假设:</b> 样本需相互独立(由研究设计保证)。</div>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<i class="fa-solid fa-scale-balanced text-amber-500 mt-1"></i>
|
||||
<div><b>正态性假设检验:</b> 将执行 Shapiro-Wilk 检验。若 P < 0.05 (拒绝正态),将自动降级使用 <b>Wilcoxon 秩和检验</b>。</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视图 2:执行追踪 (隐藏) -->
|
||||
<div id="view-execution" class="absolute inset-0 bg-white p-8 hidden flex-col items-center justify-center">
|
||||
<div class="w-full max-w-xl">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 class="font-bold text-slate-700">正在执行云端计算...</h3>
|
||||
<i class="fa-solid fa-circle-notch fa-spin text-blue-500 text-xl"></i>
|
||||
</div>
|
||||
<div class="bg-slate-900 rounded-xl p-5 font-mono text-xs text-slate-300 shadow-inner relative overflow-hidden h-64">
|
||||
<div class="absolute left-[31px] top-6 bottom-6 w-px bg-slate-700"></div>
|
||||
<div id="trace-logs" class="space-y-4 relative z-10">
|
||||
<!-- 动态插入日志 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视图 3:最终结果报告 (隐藏) -->
|
||||
<div id="view-result" class="p-8 md:p-12 max-w-4xl mx-auto w-full hidden">
|
||||
|
||||
<!-- 核心结论提示 -->
|
||||
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 p-5 rounded-xl mb-8 flex gap-4 items-start shadow-sm">
|
||||
<i class="fa-solid fa-lightbulb text-blue-500 text-xl mt-0.5"></i>
|
||||
<div>
|
||||
<h4 class="font-bold text-slate-800 text-sm mb-1">AI 统计解读</h4>
|
||||
<p class="text-sm text-slate-700 leading-relaxed">
|
||||
结果表明,新药组(Drug)的血压下降幅度显著大于对照组(Placebo)。差异具有统计学意义 (<span class="text-red-600 font-bold">P < 0.001</span>)。
|
||||
<i>注:因数据未通过正态性检验,本结果由自动降级后的 Wilcoxon 秩和检验得出。</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结果表格 -->
|
||||
<div class="mb-10">
|
||||
<h4 class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3">Table 1. 血压下降值组间比较</h4>
|
||||
<div class="border border-slate-200 rounded-lg overflow-hidden">
|
||||
<table class="sci-table bg-white">
|
||||
<thead>
|
||||
<tr class="bg-slate-50">
|
||||
<th>组别 (Group)</th>
|
||||
<th>样本量 (N)</th>
|
||||
<th>下降值 (Median [IQR])</th>
|
||||
<th>W 统计量</th>
|
||||
<th>P 值</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td>Drug (新药组)</td>
|
||||
<td>60</td>
|
||||
<td>14.5 [12.1 - 16.8]</td>
|
||||
<td rowspan="2" class="align-middle">2845.5</td>
|
||||
<td rowspan="2" class="align-middle text-red-600 font-bold">< 0.001 **</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td>Placebo (对照组)</td>
|
||||
<td>60</td>
|
||||
<td>8.2 [6.5 - 10.4]</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<div>
|
||||
<h4 class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3">Figure 1. 分布可视化</h4>
|
||||
<div class="border border-slate-200 rounded-xl p-6 bg-white flex justify-center shadow-sm">
|
||||
<!-- 纯 CSS 模拟的精美图表 -->
|
||||
<div class="relative w-full max-w-md h-64 flex items-end justify-center gap-16 pb-8 border-b-2 border-l-2 border-slate-800 ml-8">
|
||||
<!-- Y轴刻度 -->
|
||||
<div class="absolute -left-8 top-0 text-[10px] text-slate-500">20</div>
|
||||
<div class="absolute -left-8 top-1/2 -translate-y-1/2 text-[10px] text-slate-500">10</div>
|
||||
<div class="absolute -left-6 bottom-6 text-[10px] text-slate-500">0</div>
|
||||
|
||||
<!-- 箱线图 Drug -->
|
||||
<div class="relative w-16 h-48 bg-blue-500/20 border-2 border-blue-600 rounded-sm flex justify-center items-center group cursor-pointer hover:bg-blue-500/30 transition">
|
||||
<div class="absolute w-full h-[2px] bg-blue-800"></div> <!-- 中位数 -->
|
||||
<div class="absolute -top-4 w-px h-4 bg-blue-600"></div> <!-- 上触须 -->
|
||||
<div class="absolute -bottom-6 w-px h-6 bg-blue-600"></div> <!-- 下触须 -->
|
||||
<div class="absolute -top-4 w-6 h-[2px] bg-blue-600"></div>
|
||||
<div class="absolute -bottom-6 w-6 h-[2px] bg-blue-600"></div>
|
||||
<div class="absolute -bottom-14 text-xs font-bold text-slate-700">Drug</div>
|
||||
</div>
|
||||
|
||||
<!-- 箱线图 Placebo -->
|
||||
<div class="relative w-16 h-24 bg-red-500/20 border-2 border-red-600 rounded-sm flex justify-center items-center group cursor-pointer hover:bg-red-500/30 transition">
|
||||
<div class="absolute w-full h-[2px] bg-red-800"></div> <!-- 中位数 -->
|
||||
<div class="absolute -top-6 w-px h-6 bg-red-600"></div> <!-- 上触须 -->
|
||||
<div class="absolute -bottom-4 w-px h-4 bg-red-600"></div> <!-- 下触须 -->
|
||||
<div class="absolute -top-6 w-6 h-[2px] bg-red-600"></div>
|
||||
<div class="absolute -bottom-4 w-6 h-[2px] bg-red-600"></div>
|
||||
<div class="absolute -bottom-14 text-xs font-bold text-slate-700">Placebo</div>
|
||||
</div>
|
||||
|
||||
<!-- 显著性标记 -->
|
||||
<div class="absolute top-2 left-[30%] right-[30%] h-4 border-t-2 border-l-2 border-r-2 border-slate-700"></div>
|
||||
<div class="absolute -top-3 w-full text-center text-sm font-bold text-slate-800">P < 0.001 ***</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 模态框:R 代码预览 (在方案C中,代码也可以直接在右侧面板展示,但为了保持报告整洁,保留模态框) -->
|
||||
<div id="code-modal" class="fixed inset-0 z-50 hidden flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-slate-900/40 modal-backdrop" onclick="closeModal('code-modal')"></div>
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[80vh] flex flex-col relative z-10 pop-in m-4">
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
||||
<h3 class="font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="fa-brands fa-r-project text-blue-600 text-xl"></i> 可复现 R 源码
|
||||
</h3>
|
||||
<button onclick="closeModal('code-modal')" class="text-slate-400 hover:text-slate-700 w-8 h-8 rounded-full hover:bg-slate-100 flex items-center justify-center">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6 overflow-y-auto flex-1 bg-slate-50">
|
||||
<pre class="bg-slate-900 text-slate-50 p-4 rounded-xl overflow-x-auto shadow-inner text-xs"><code><span class="text-slate-500"># SSA-Pro 生成代码: 独立样本差异分析</span>
|
||||
<span class="text-pink-400">library</span>(ggplot2)
|
||||
df <- <span class="text-blue-300">read.csv</span>(<span class="text-green-300">"BP_Trial_Data.csv"</span>)
|
||||
df$Group <- <span class="text-blue-300">as.factor</span>(df$Group)
|
||||
df$BP_Change <- <span class="text-blue-300">as.numeric</span>(df$BP_Change)
|
||||
|
||||
<span class="text-slate-500"># 核心计算 (自动降级至非参数检验)</span>
|
||||
res <- <span class="text-blue-300">wilcox.test</span>(BP_Change ~ Group, data = df)
|
||||
<span class="text-blue-300">print</span>(res)</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const chatAnchor = document.getElementById('chat-anchor');
|
||||
const workspaceEmpty = document.getElementById('workspace-empty');
|
||||
const artifactContainer = document.getElementById('artifact-container');
|
||||
const vSap = document.getElementById('view-sap');
|
||||
const vExec = document.getElementById('view-execution');
|
||||
const vRes = document.getElementById('view-result');
|
||||
const artTitle = document.getElementById('artifact-title');
|
||||
const artIcon = document.getElementById('artifact-icon');
|
||||
const btnCode = document.getElementById('btn-code');
|
||||
|
||||
// 初始化显示 SAP
|
||||
setTimeout(() => {
|
||||
workspaceEmpty.classList.add('hidden');
|
||||
artifactContainer.classList.remove('hidden');
|
||||
}, 500);
|
||||
|
||||
// 模拟上传数据
|
||||
function simulateUpload() {
|
||||
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');
|
||||
|
||||
// 聊天区增加对话
|
||||
setTimeout(() => {
|
||||
const aiHTML = `
|
||||
<div class="flex gap-3 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 border border-slate-200 text-slate-700 p-4 rounded-2xl rounded-tl-none shadow-sm w-full">
|
||||
<div class="text-sm font-medium mb-3 border-b border-slate-100 pb-2">
|
||||
<i class="fa-solid fa-link text-blue-500 mr-1"></i> 数据已挂载,参数映射完成:
|
||||
</div>
|
||||
<div class="text-xs space-y-2 mb-4">
|
||||
<div class="flex justify-between"><span class="text-slate-500">分组变量:</span> <span class="font-mono bg-slate-100 px-1 rounded">Group</span></div>
|
||||
<div class="flex justify-between"><span class="text-slate-500">检验变量:</span> <span class="font-mono bg-slate-100 px-1 rounded">BP_Change</span></div>
|
||||
</div>
|
||||
<button onclick="runAnalysis(this)" class="w-full py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition shadow-sm text-sm font-medium flex items-center justify-center gap-2">
|
||||
<i class="fa-solid fa-play text-xs"></i> 确认执行分析
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
chatAnchor.insertAdjacentHTML('beforebegin', aiHTML);
|
||||
scrollToBottom();
|
||||
}, 600);
|
||||
}
|
||||
|
||||
// 移除数据
|
||||
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 runAnalysis(btn) {
|
||||
// 1. 左侧按钮状态变更
|
||||
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> 执行中...';
|
||||
btn.classList.replace('bg-blue-600', 'bg-slate-400');
|
||||
btn.classList.replace('hover:bg-blue-700', 'hover:bg-slate-400');
|
||||
btn.disabled = true;
|
||||
|
||||
// 2. 右侧切到执行日志视图
|
||||
vSap.classList.add('hidden');
|
||||
vExec.classList.remove('hidden');
|
||||
vExec.classList.add('flex');
|
||||
artTitle.innerText = "执行日志 (Engine Trace)";
|
||||
artIcon.innerHTML = '<i class="fa-solid fa-terminal"></i>';
|
||||
artIcon.className = "w-8 h-8 rounded-lg bg-slate-800 text-white flex items-center justify-center";
|
||||
|
||||
// 3. 模拟打印日志
|
||||
const logBox = document.getElementById('trace-logs');
|
||||
logBox.innerHTML = '';
|
||||
|
||||
setTimeout(() => { addLog(logBox, 'text-green-400', 'fa-check-circle', '[System] 读取数据 BP_Trial_Data.csv (N=120) 成功'); }, 500);
|
||||
setTimeout(() => { addLog(logBox, 'text-blue-400', 'fa-spinner fa-spin', '[Guardrail] 正在进行 Shapiro-Wilk 正态性检验...'); }, 1200);
|
||||
setTimeout(() => {
|
||||
logBox.lastElementChild.innerHTML = '<i class="fa-solid fa-times-circle text-red-400 bg-slate-900 z-10 relative"></i> <span class="ml-2">[Guardrail] 正态性检验未通过 (P=0.002 < 0.05)</span>';
|
||||
addLog(logBox, 'text-amber-400', 'fa-arrow-turn-down', '[Action] 触发护栏:自动降级为 Wilcoxon 秩和检验');
|
||||
}, 2500);
|
||||
setTimeout(() => { addLog(logBox, 'text-blue-400', 'fa-spinner fa-spin', '[Compute] 执行 wilcox.test()...'); }, 3200);
|
||||
|
||||
// 4. 完成,展示结果
|
||||
setTimeout(() => {
|
||||
// 左侧按钮更新
|
||||
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');
|
||||
|
||||
// 右侧切到结果视图
|
||||
vExec.classList.remove('flex');
|
||||
vExec.classList.add('hidden');
|
||||
vRes.classList.remove('hidden');
|
||||
vRes.classList.add('fade-in');
|
||||
|
||||
// 更新右侧头部
|
||||
artTitle.innerText = "分析结果:血压变化值差异检验";
|
||||
artIcon.innerHTML = '<i class="fa-solid fa-chart-pie"></i>';
|
||||
artIcon.className = "w-8 h-8 rounded-lg bg-indigo-100 text-indigo-600 flex items-center justify-center";
|
||||
|
||||
// 显示代码按钮
|
||||
btnCode.classList.remove('hidden');
|
||||
btnCode.onclick = () => { document.getElementById('code-modal').classList.remove('hidden') };
|
||||
|
||||
// 左侧追加提示
|
||||
setTimeout(() => {
|
||||
const aiFinal = `
|
||||
<div class="flex gap-3 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-slate-50 text-slate-700 p-3.5 rounded-2xl rounded-tl-none text-sm leading-relaxed border border-slate-200">
|
||||
分析已完成!👉 <b>请在右侧面板查看详细的结果报告和图表。</b>
|
||||
</div>
|
||||
</div>`;
|
||||
chatAnchor.insertAdjacentHTML('beforebegin', aiFinal);
|
||||
scrollToBottom();
|
||||
}, 500);
|
||||
|
||||
}, 4500);
|
||||
}
|
||||
|
||||
function addLog(container, color, icon, text) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex items-center fade-in text-slate-300';
|
||||
div.innerHTML = `<i class="fa-solid ${icon} ${color} bg-slate-900 z-10 relative"></i> <span class="ml-3">${text}</span>`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
const c = document.getElementById('chat-container');
|
||||
c.scrollTo({ top: c.scrollHeight, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function closeModal(id) {
|
||||
document.getElementById(id).classList.add('hidden');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
796
docs/03-业务模块/SSA-智能统计分析/04-开发计划/05-前端UI改进开发计划-V8双屏版.md
Normal file
796
docs/03-业务模块/SSA-智能统计分析/04-开发计划/05-前端UI改进开发计划-V8双屏版.md
Normal file
@@ -0,0 +1,796 @@
|
||||
# SSA-Pro 前端 UI 改进开发计划 - V8 双屏版
|
||||
|
||||
> **文档版本:** v1.1
|
||||
> **创建日期:** 2026-02-20
|
||||
> **最后更新:** 2026-02-20(根据审查反馈更新)
|
||||
> **预计工时:** 3-4 天(前端)
|
||||
> **依赖文档:**
|
||||
> - `03-UI设计/产品原型图V8双屏版.html`
|
||||
> - `03-UI设计/SSA-Pro UX方案对比与技术选型报告.md`
|
||||
> - `docs/03-业务模块/AIA-AI智能问答/00-模块当前状态与开发指南.md`
|
||||
> - `frontend-v2/src/shared/components/Chat/README.md`(流式响应能力)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
当前 SSA 前端已实现基础功能(Phase 1 完成 85%),但 UI/UX 存在以下问题:
|
||||
|
||||
| 问题 | 影响 |
|
||||
|------|------|
|
||||
| 单列布局 | 统计结果(表格、图表)展示空间狭窄 |
|
||||
| 对话被结果顶开 | 用户无法同时查看计划和结果 |
|
||||
| 缺乏专业感 | 不像 RStudio/SPSS 等专业统计软件 |
|
||||
| 交互割裂 | 数据上传、分析、结果分散在不同卡片 |
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
采用 **V8 双屏工作台** 架构(类似 Claude Artifacts),实现:
|
||||
|
||||
1. **左右分屏**:左侧对话控制 + 右侧工作区(60%+ 空间)
|
||||
2. **上下文保持**:对话和结果独立展示,互不干扰
|
||||
3. **专业信任感**:类似 RStudio 的布局,提升医生信任
|
||||
4. **完整流程可视化**:SAP → 执行日志 → 结果报告
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 整体布局
|
||||
|
||||
```
|
||||
┌────────┬─────────────────────────┬──────────────────────────────────────┐
|
||||
│ 64px │ 400-450px │ 剩余空间 │
|
||||
│ │ │ │
|
||||
│ 全局 │ 对话控制区 │ 工作区 (Artifacts) │
|
||||
│ 导航 │ (ChatPane) │ (ArtifactPane) │
|
||||
│ │ │ │
|
||||
│ ┌───┐ │ ┌─────────────────────┐ │ ┌─────────────────────────────────┐ │
|
||||
│ │新建│ │ │ AI 欢迎语 │ │ │ 视图标题 + 操作按钮 │ │
|
||||
│ └───┘ │ │ 用户消息 │ │ └─────────────────────────────────┘ │
|
||||
│ ┌───┐ │ │ AI 回复 + 交互卡片 │ │ ┌─────────────────────────────────┐ │
|
||||
│ │历史│ │ │ 数据挂载确认 │ │ │ │ │
|
||||
│ └───┘ │ │ 执行状态提示 │ │ │ 动态内容区域 │ │
|
||||
│ ┌───┐ │ └─────────────────────┘ │ │ - SAP 文档 │ │
|
||||
│ │设置│ │ │ │ - 执行日志 │ │
|
||||
│ └───┘ │ ┌─────────────────────┐ │ │ - 结果报告(表格 + 图表) │ │
|
||||
│ │ │ 📎 数据挂载区 │ │ │ │ │
|
||||
│ │ │ ────────────────── │ │ └─────────────────────────────────┘ │
|
||||
│ │ │ 输入框 + 发送按钮 │ │ │
|
||||
│ │ └─────────────────────┘ │ │
|
||||
└────────┴─────────────────────────┴──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 右侧工作区三种视图
|
||||
|
||||
| 视图 | 触发条件 | 内容 |
|
||||
|------|---------|------|
|
||||
| **empty** | 初始状态 | 空状态提示(图标 + 文案) |
|
||||
| **sap** | 生成 SAP 后 | 研究目的、推荐方法、统计护栏 |
|
||||
| **execution** | 执行分析时 | 实时日志(护栏检查、自动降级) |
|
||||
| **result** | 执行完成后 | AI 解读、三线表、图表、R 代码 |
|
||||
|
||||
### 2.3 状态管理设计
|
||||
|
||||
```typescript
|
||||
// stores/ssaStore.ts 扩展
|
||||
interface SSAWorkspaceState {
|
||||
// 现有状态
|
||||
mode: SSAMode;
|
||||
currentSession: SSASession | null;
|
||||
currentPlan: AnalysisPlan | null;
|
||||
executionResult: ExecutionResult | null;
|
||||
traceSteps: TraceStep[];
|
||||
|
||||
// 🆕 新增:右侧面板状态
|
||||
activePane: 'empty' | 'sap' | 'execution' | 'result';
|
||||
currentArtifact: {
|
||||
type: 'sap' | 'trace' | 'result';
|
||||
data: any;
|
||||
} | null;
|
||||
|
||||
// 🆕 新增:数据挂载状态
|
||||
mountedFile: {
|
||||
name: string;
|
||||
size: number;
|
||||
rowCount: number;
|
||||
} | null;
|
||||
|
||||
// 🆕 新增:动作
|
||||
setActivePane: (pane: 'empty' | 'sap' | 'execution' | 'result') => void;
|
||||
setArtifact: (artifact: { type: string; data: any } | null) => void;
|
||||
setMountedFile: (file: { name: string; size: number; rowCount: number } | null) => void;
|
||||
|
||||
// 🆕 新增:历史记录状态恢复(审查反馈 - 盲区二)
|
||||
hydrateFromHistory: (session: SSASession) => void;
|
||||
}
|
||||
```
|
||||
|
||||
**历史记录状态恢复逻辑**:
|
||||
|
||||
```typescript
|
||||
// hydrateFromHistory 实现
|
||||
hydrateFromHistory: (session: SSASession) => {
|
||||
// 根据 session 状态恢复右侧面板
|
||||
if (session.executionResult) {
|
||||
set({
|
||||
activePane: 'result',
|
||||
currentArtifact: { type: 'result', data: session.executionResult }
|
||||
});
|
||||
} else if (session.currentPlan) {
|
||||
set({
|
||||
activePane: 'sap',
|
||||
currentArtifact: { type: 'sap', data: session.currentPlan }
|
||||
});
|
||||
} else {
|
||||
set({ activePane: 'empty', currentArtifact: null });
|
||||
}
|
||||
|
||||
// 恢复数据挂载状态
|
||||
if (session.dataSchema) {
|
||||
set({
|
||||
mountedFile: {
|
||||
name: session.title,
|
||||
size: 0,
|
||||
rowCount: session.dataSchema.rowCount,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 组件复用分析
|
||||
|
||||
### 3.1 可复用的 AIA 组件
|
||||
|
||||
| 组件 | 来源路径 | 复用程度 | 改造需求 |
|
||||
|------|---------|----------|----------|
|
||||
| **ResizableSplitPane** | `aia/protocol-agent/components/` | 🟢 100% | 直接复用 |
|
||||
| **AIStreamChat** | `shared/components/Chat/` | 🟢 90% | 定制欢迎语 |
|
||||
| **ThinkingBlock** | `shared/components/Chat/` | 🟢 100% | 直接复用 |
|
||||
| **ViewSwitcher** | `aia/protocol-agent/components/` | 🟡 70% | 修改视图选项 |
|
||||
| **MarkdownContent** | `aia/protocol-agent/components/` | 🟢 100% | SAP 文档渲染 |
|
||||
|
||||
### 3.2 SSA 现有组件保留/改造
|
||||
|
||||
| 组件 | 当前路径 | 处理方式 | 说明 |
|
||||
|------|---------|---------|------|
|
||||
| **ModeSwitch** | `ssa/components/` | ⏸️ 暂时移除 | V8 聚焦智能分析模式 |
|
||||
| **PlanCard** | `ssa/components/` | 🔄 改造 | 移入 ChatPane 作为交互卡片 |
|
||||
| **ExecutionTrace** | `ssa/components/` | 🔄 改造 | 移入 ArtifactPane |
|
||||
| **ResultCard** | `ssa/components/` | 🔄 重构 | 拆分为独立组件 |
|
||||
| **APATable** | `ssa/components/` | ✅ 保留 | 三线表核心组件 |
|
||||
|
||||
### 3.3 需要新开发的组件
|
||||
|
||||
| 组件 | 说明 | 预估工时 |
|
||||
|------|------|----------|
|
||||
| **SSAWorkspace** | 双屏布局主页面 | 4h |
|
||||
| **GlobalNav** | 左侧全局导航栏 | 2h |
|
||||
| **ChatPane** | 左侧对话控制区 | 3h |
|
||||
| **DataMountZone** | 数据挂载区(嵌入式) | 2h |
|
||||
| **ArtifactPane** | 右侧工作区容器 | 2h |
|
||||
| **SAPViewer** | SAP 文档视图 | 3h |
|
||||
| **ExecutionViewer** | 执行日志视图 | 2h |
|
||||
| **ResultViewer** | 结果报告视图 | 4h |
|
||||
| **SciTable** | 三线表组件(科学格式) | 2h |
|
||||
| **ChartViewer** | 图表展示组件 | 2h |
|
||||
| **CodeModal** | R 代码预览模态框 | 1h |
|
||||
|
||||
---
|
||||
|
||||
## 4. 详细开发任务
|
||||
|
||||
### 4.1 Day 1: 架构搭建 + 组件下沉
|
||||
|
||||
| 任务 | 预估 | 说明 |
|
||||
|------|------|------|
|
||||
| 下沉 `ResizableSplitPane` 到 shared | 1h | 提取为系统级 Common 组件 |
|
||||
| 创建 `SSAWorkspace` 骨架 | 2h | 双屏布局 + 状态联动 |
|
||||
| 改造 `ssaStore.ts` | 1.5h | 新增 activePane、artifact 状态 |
|
||||
| 创建 `GlobalNav` 组件 | 1.5h | 新建/历史/设置图标 |
|
||||
| 样式文件创建 | 1h | `ssa-workspace.css` |
|
||||
|
||||
**Day 1 交付物:**
|
||||
- [ ] 双屏布局可拖拽
|
||||
- [ ] 左侧空白区域 + 右侧空白区域
|
||||
- [ ] 全局状态管理就绪
|
||||
|
||||
### 4.2 Day 2: 左侧对话区 + 流式联动
|
||||
|
||||
| 任务 | 预估 | 说明 |
|
||||
|------|------|------|
|
||||
| 创建 `ChatPane` 组件 | 2h | 消息列表 + 输入区 |
|
||||
| 创建 `DataMountZone` 组件 | 2h | 嵌入式数据挂载 |
|
||||
| 改造 `PlanCard` 为交互卡片 | 1.5h | 点击触发右侧 SAP 展示 |
|
||||
| 集成 `AIStreamChat` | 1.5h | 咨询模式对话 |
|
||||
| 🆕 实现流式 Artifact 标记解析 | 2h | **审查反馈 - 盲区一** |
|
||||
| 左侧区域样式精调 | 1h | 参考原型图 |
|
||||
|
||||
**Day 2 交付物:**
|
||||
- [ ] 数据上传 → 挂载区显示文件信息
|
||||
- [ ] 输入分析需求 → 生成 SAP
|
||||
- [ ] 点击 SAP 卡片 → 右侧展示详情
|
||||
- [ ] 🆕 AI 流式输出中解析 Artifact 标记,自动切换右侧面板
|
||||
|
||||
### 4.3 Day 3: 右侧工作区
|
||||
|
||||
| 任务 | 预估 | 说明 |
|
||||
|------|------|------|
|
||||
| 创建 `ArtifactPane` 容器 | 1h | 动态视图切换 |
|
||||
| 创建 `SAPViewer` 组件 | 2.5h | 研究目的、推荐方法、护栏 |
|
||||
| 创建 `ExecutionViewer` 组件 | 2h | 实时日志 + 动画 |
|
||||
| 创建 `SciTable` 组件 | 2h | 三线表科学格式 |
|
||||
| 创建 `ChartViewer` 组件 | 1.5h | Base64 图片展示 |
|
||||
|
||||
**Day 3 交付物:**
|
||||
- [ ] SAP 文档视图完整
|
||||
- [ ] 执行日志实时展示
|
||||
- [ ] 三线表科学格式
|
||||
|
||||
### 4.4 Day 4: 结果展示 + 联调
|
||||
|
||||
| 任务 | 预估 | 说明 |
|
||||
|------|------|------|
|
||||
| 创建 `ResultViewer` 组件 | 2.5h | AI 解读 + 表格 + 图表 |
|
||||
| 创建 `CodeModal` 组件 | 1h | R 代码预览模态框 |
|
||||
| 下载功能完善 | 1.5h | 代码下载 + 报告下载 |
|
||||
| 端到端联调测试 | 2h | 完整流程验证 |
|
||||
| UI 样式精调 | 1h | 对齐原型图 |
|
||||
|
||||
**Day 4 交付物:**
|
||||
- [ ] 结果报告完整展示
|
||||
- [ ] R 代码一键下载
|
||||
- [ ] 端到端流程通过
|
||||
|
||||
---
|
||||
|
||||
## 5. 组件详细设计
|
||||
|
||||
### 5.1 ResizableSplitPane(复用)
|
||||
|
||||
直接从 `aia/protocol-agent/components/ResizableSplitPane.tsx` 复制到 `shared/components/Layout/`。
|
||||
|
||||
```typescript
|
||||
// 使用方式
|
||||
<ResizableSplitPane
|
||||
defaultLeftRatio={35} // 左侧 35%
|
||||
minLeftRatio={25}
|
||||
maxLeftRatio={50}
|
||||
enableDrag={true}
|
||||
storageKey="ssa-split-ratio"
|
||||
leftPanel={<ChatPane />}
|
||||
rightPanel={<ArtifactPane />}
|
||||
/>
|
||||
```
|
||||
|
||||
### 5.2 SSAWorkspace(主页面)
|
||||
|
||||
```tsx
|
||||
// modules/ssa/SSAWorkspace.tsx
|
||||
const SSAWorkspace: React.FC = () => {
|
||||
const { activePane, setActivePane } = useSSAStore();
|
||||
|
||||
return (
|
||||
<div className="ssa-workspace">
|
||||
{/* 全局导航 */}
|
||||
<GlobalNav />
|
||||
|
||||
{/* 主工作区 */}
|
||||
<main className="ssa-main">
|
||||
<ResizableSplitPane
|
||||
leftPanel={<ChatPane />}
|
||||
rightPanel={<ArtifactPane activePane={activePane} />}
|
||||
defaultLeftRatio={35}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 5.3 GlobalNav(全局导航)
|
||||
|
||||
```tsx
|
||||
// modules/ssa/components/GlobalNav.tsx
|
||||
const GlobalNav: React.FC = () => {
|
||||
return (
|
||||
<nav className="ssa-global-nav">
|
||||
<div className="nav-logo">
|
||||
<BarChart3 size={24} />
|
||||
</div>
|
||||
<div className="nav-actions">
|
||||
<button title="新建分析"><Plus /></button>
|
||||
<button title="历史记录"><History /></button>
|
||||
<button title="配置中心"><Settings /></button>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 5.4 ChatPane(对话控制区)
|
||||
|
||||
```tsx
|
||||
// modules/ssa/components/ChatPane.tsx
|
||||
const ChatPane: React.FC = () => {
|
||||
const { mountedFile, setMountedFile } = useSSAStore();
|
||||
|
||||
return (
|
||||
<div className="ssa-chat-pane">
|
||||
{/* 头部 */}
|
||||
<header className="chat-header">
|
||||
<span className="session-title">新的统计分析</span>
|
||||
<StatusBadge status="ready" />
|
||||
</header>
|
||||
|
||||
{/* 消息列表 */}
|
||||
<div className="chat-messages">
|
||||
{/* AI 欢迎语 */}
|
||||
{/* 用户消息 */}
|
||||
{/* AI 回复 + 交互卡片 */}
|
||||
</div>
|
||||
|
||||
{/* 输入区 */}
|
||||
<div className="chat-input-area">
|
||||
{/* 数据挂载区 */}
|
||||
{mountedFile && <DataMountZone file={mountedFile} />}
|
||||
|
||||
{/* 输入框 */}
|
||||
<div className="input-container">
|
||||
<UploadButton />
|
||||
<TextArea placeholder="描述您的分析需求..." />
|
||||
<SendButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 5.5 DataMountZone(数据挂载区)
|
||||
|
||||
```tsx
|
||||
// modules/ssa/components/DataMountZone.tsx
|
||||
interface DataMountZoneProps {
|
||||
file: { name: string; size: number; rowCount: number };
|
||||
onRemove: () => void;
|
||||
}
|
||||
|
||||
const DataMountZone: React.FC<DataMountZoneProps> = ({ file, onRemove }) => {
|
||||
return (
|
||||
<div className="data-mount-zone">
|
||||
<FileIcon type="csv" />
|
||||
<div className="file-info">
|
||||
<span className="file-name">{file.name}</span>
|
||||
<span className="file-meta">{file.rowCount} rows • {formatFileSize(file.size)}</span>
|
||||
</div>
|
||||
<button className="remove-btn" onClick={onRemove}>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 5.6 ArtifactPane(工作区容器)
|
||||
|
||||
```tsx
|
||||
// modules/ssa/components/ArtifactPane.tsx
|
||||
interface ArtifactPaneProps {
|
||||
activePane: 'empty' | 'sap' | 'execution' | 'result';
|
||||
}
|
||||
|
||||
const ArtifactPane: React.FC<ArtifactPaneProps> = ({ activePane }) => {
|
||||
const { currentArtifact } = useSSAStore();
|
||||
|
||||
// 空状态
|
||||
if (activePane === 'empty') {
|
||||
return (
|
||||
<div className="artifact-empty">
|
||||
<Layers size={64} />
|
||||
<p>分析方案、执行过程与图表将在这里展示</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="artifact-pane">
|
||||
{/* 头部 */}
|
||||
<header className="artifact-header">
|
||||
<ArtifactIcon type={activePane} />
|
||||
<h2>{getArtifactTitle(activePane)}</h2>
|
||||
<div className="artifact-actions">
|
||||
{activePane === 'result' && <CodeButton />}
|
||||
<DownloadButton />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 内容区 */}
|
||||
<div className="artifact-content">
|
||||
{activePane === 'sap' && <SAPViewer data={currentArtifact?.data} />}
|
||||
{activePane === 'execution' && <ExecutionViewer />}
|
||||
{activePane === 'result' && <ResultViewer data={currentArtifact?.data} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 5.7 SciTable(三线表)
|
||||
|
||||
```tsx
|
||||
// modules/ssa/components/SciTable.tsx
|
||||
interface SciTableProps {
|
||||
headers: string[];
|
||||
rows: (string | number)[][];
|
||||
caption?: string;
|
||||
}
|
||||
|
||||
const SciTable: React.FC<SciTableProps> = ({ headers, rows, caption }) => {
|
||||
return (
|
||||
<div className="sci-table-container">
|
||||
{caption && <caption>{caption}</caption>}
|
||||
<table className="sci-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{headers.map((h, i) => <th key={i}>{h}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{row.map((cell, j) => <td key={j}>{cell}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**三线表样式(科学格式):**
|
||||
|
||||
```css
|
||||
.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;
|
||||
}
|
||||
|
||||
.sci-table td {
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.sci-table tr:last-child td {
|
||||
border-bottom: 2px solid #1e293b;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 与后端对接
|
||||
|
||||
### 6.1 现有 API(无需修改)
|
||||
|
||||
| API | 说明 |
|
||||
|-----|------|
|
||||
| `POST /api/v1/ssa/sessions` | 创建会话(上传数据) |
|
||||
| `POST /api/v1/ssa/sessions/:id/plan` | 生成分析计划 |
|
||||
| `POST /api/v1/ssa/sessions/:id/execute` | 执行分析 |
|
||||
| `GET /api/v1/ssa/sessions/:id/code` | 下载 R 代码 |
|
||||
|
||||
### 6.2 返回数据结构适配
|
||||
|
||||
```typescript
|
||||
// 执行结果返回结构(后端已实现)
|
||||
interface ExecutionResult {
|
||||
success: boolean;
|
||||
tool_code: string;
|
||||
method_used: string;
|
||||
|
||||
// 表格数据(用于 SciTable)
|
||||
result_table: {
|
||||
headers: string[];
|
||||
rows: (string | number)[][];
|
||||
};
|
||||
|
||||
// 图表数据(用于 ChartViewer)
|
||||
plots?: {
|
||||
type: 'boxplot' | 'histogram' | 'scatter';
|
||||
data: string; // Base64
|
||||
}[];
|
||||
|
||||
// AI 解读
|
||||
interpretation: string;
|
||||
|
||||
// R 代码
|
||||
reproducible_code: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6.3 🆕 流式响应与数据流技术方案(审查反馈 - 盲区一、四)
|
||||
|
||||
### 已有能力
|
||||
|
||||
| 能力 | 位置 | 状态 |
|
||||
|------|------|------|
|
||||
| **useAIStream Hook** | `shared/components/Chat/hooks/useAIStream.ts` | ✅ 已实现 |
|
||||
| **StreamingService** | `backend/src/common/streaming/` | ✅ 已实现 |
|
||||
| **traceSteps 状态管理** | `ssaStore.ts` | ✅ 已实现 |
|
||||
|
||||
### 流式 Artifact 标记解析
|
||||
|
||||
AI 在对话流中输出结构化标记,前端实时解析并触发右侧面板切换:
|
||||
|
||||
```typescript
|
||||
// AI 输出示例
|
||||
"我已为您生成分析方案:<Artifact type='sap' id='plan-001' />"
|
||||
|
||||
// useAIStream 扩展:解析 Artifact 标记
|
||||
const handleChunk = (chunk: string) => {
|
||||
// 正常内容追加
|
||||
setContent(prev => prev + chunk);
|
||||
|
||||
// 检测 Artifact 标记
|
||||
const artifactMatch = chunk.match(/<Artifact type='(\w+)' id='([^']+)' \/>/);
|
||||
if (artifactMatch) {
|
||||
const [_, type, id] = artifactMatch;
|
||||
ssaStore.setActivePane(type as 'sap' | 'execution' | 'result');
|
||||
ssaStore.loadArtifact(type, id);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 执行日志数据流方案
|
||||
|
||||
| 方案 | 说明 | MVP 采用 |
|
||||
|------|------|---------|
|
||||
| **方案 A** | 对话流内嵌执行状态(复用 useAIStream) | ✅ 推荐 |
|
||||
| **方案 B** | 独立 SSE 端点 | Phase 2 |
|
||||
| **方案 C** | 同步返回 + 伪进度 | 备选 |
|
||||
|
||||
**推荐方案 A 示例**:
|
||||
|
||||
```
|
||||
// AI 对话流中输出执行状态
|
||||
data: {"choices":[{"delta":{"content":"🔍 正在检验正态性..."}}]}
|
||||
data: {"choices":[{"delta":{"content":"✅ Shapiro-Wilk p=0.32,符合正态分布"}}]}
|
||||
data: {"choices":[{"delta":{"content":"📊 正在执行独立样本 T 检验..."}}]}
|
||||
data: {"choices":[{"delta":{"content":"<Artifact type='result' id='xxx' />"}}]}
|
||||
```
|
||||
|
||||
**优势**:直接复用 `AIStreamChat` 组件和 `useAIStream` Hook,无需额外开发 SSE 端点。
|
||||
|
||||
---
|
||||
|
||||
## 7. 样式规范
|
||||
|
||||
### 7.1 颜色方案
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 主色 */
|
||||
--ssa-primary: #4F46E5; /* Indigo */
|
||||
--ssa-primary-light: #EEF2FF;
|
||||
|
||||
/* 状态色 */
|
||||
--ssa-success: #22C55E;
|
||||
--ssa-warning: #F59E0B;
|
||||
--ssa-error: #EF4444;
|
||||
|
||||
/* 中性色 */
|
||||
--ssa-bg: #F1F5F9;
|
||||
--ssa-card: #FFFFFF;
|
||||
--ssa-border: #E2E8F0;
|
||||
--ssa-text: #334155;
|
||||
--ssa-text-secondary: #64748B;
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 间距规范
|
||||
|
||||
```css
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
```
|
||||
|
||||
### 7.3 动画规范
|
||||
|
||||
```css
|
||||
/* 淡入 */
|
||||
.fade-in {
|
||||
animation: fadeIn 0.4s ease-out forwards;
|
||||
}
|
||||
|
||||
/* 滑入 */
|
||||
.slide-up {
|
||||
animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
/* 弹出 */
|
||||
.pop-in {
|
||||
animation: popIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 验收标准
|
||||
|
||||
### 8.1 功能验收
|
||||
|
||||
| 功能 | 验收标准 |
|
||||
|------|---------|
|
||||
| 双屏布局 | 左右面板可拖拽调整,比例记忆 |
|
||||
| 数据上传 | 上传后在挂载区显示文件信息 |
|
||||
| SAP 生成 | 点击卡片在右侧展示完整 SAP |
|
||||
| 执行日志 | 实时展示护栏检查、自动降级 |
|
||||
| 结果展示 | AI 解读 + 三线表 + 图表 |
|
||||
| 代码下载 | 点击按钮下载 .R 文件 |
|
||||
|
||||
### 8.2 UI 验收
|
||||
|
||||
- [ ] 100% 还原 V8 原型图布局
|
||||
- [ ] 三线表符合科学论文格式
|
||||
- [ ] 动画流畅(60fps)
|
||||
- [ ] 响应式(1280px+ 宽屏)
|
||||
|
||||
### 8.3 性能验收
|
||||
|
||||
- [ ] 首屏加载 < 2s
|
||||
- [ ] 拖拽无卡顿
|
||||
- [ ] 大表格(1000行)渲染 < 500ms
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险与应对
|
||||
|
||||
| 风险 | 概率 | 应对策略 |
|
||||
|------|------|---------|
|
||||
| ResizableSplitPane 复用不兼容 | 低 | 预留 1h 调试时间 |
|
||||
| 三线表样式与原型有差异 | 中 | 参考科学论文格式,与设计师沟通 |
|
||||
| 大数据量表格性能问题 | 中 | 使用虚拟滚动(react-window) |
|
||||
| 后端返回数据结构不一致 | 低 | 已确认现有 API 结构 |
|
||||
|
||||
### 9.1 🆕 Phase 2 延后任务(审查反馈 - 盲区三)
|
||||
|
||||
**响应式降级策略**:MVP 阶段聚焦 PC 端宽屏(1280px+),小屏幕降级方案延后至 Phase 2。
|
||||
|
||||
| 屏幕宽度 | MVP 处理 | Phase 2 计划 |
|
||||
|---------|---------|-------------|
|
||||
| ≥ 1280px | ✅ 正常双屏 | - |
|
||||
| 1024-1280px | ⚠️ 可用但略挤 | 调整比例 |
|
||||
| < 1024px | ❌ 不支持 | Drawer/Modal 降级 |
|
||||
|
||||
**Phase 2 降级方案预留设计**:
|
||||
|
||||
```css
|
||||
@media (max-width: 1024px) {
|
||||
.ssa-workspace {
|
||||
.artifact-pane { display: none; }
|
||||
.chat-pane { width: 100%; }
|
||||
}
|
||||
|
||||
.artifact-drawer {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
width: 90vw;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 相关文档
|
||||
|
||||
| 文档 | 路径 |
|
||||
|------|------|
|
||||
| 产品原型图 V8 | `03-UI设计/产品原型图V8双屏版.html` |
|
||||
| UX 对比报告 | `03-UI设计/SSA-Pro UX方案对比与技术选型报告.md` |
|
||||
| AIA 组件文档 | `docs/03-业务模块/AIA-AI智能问答/00-模块当前状态与开发指南.md` |
|
||||
| Chat 组件文档 | `frontend-v2/src/shared/components/Chat/README.md` |
|
||||
| 任务清单 | `04-开发计划/01-任务清单与进度追踪.md` |
|
||||
| 🆕 审查报告 | `06-开发记录/SSA-Pro 前端 UI 改进计划审查报告.md` |
|
||||
| 🆕 审查回应 | `06-开发记录/SSA-Pro 前端UI改进计划-审查回应.md` |
|
||||
|
||||
---
|
||||
|
||||
## 11. 附录:文件结构(V11 最终版)
|
||||
|
||||
```
|
||||
frontend-v2/src/modules/ssa/
|
||||
├── index.tsx # 模块入口
|
||||
├── SSAWorkspace.tsx # V11 主工作区(全屏 Gemini 风格)
|
||||
├── components/
|
||||
│ ├── SSASidebar.tsx # 左侧抽屉栏(可展开/收起)
|
||||
│ ├── SSAChatPane.tsx # 中间对话区
|
||||
│ ├── SSAWorkspacePane.tsx # 右侧工作区(SAP/执行/结果视图)
|
||||
│ ├── SSACodeModal.tsx # R 代码模态框
|
||||
│ ├── SSAToast.tsx # Toast 通知
|
||||
│ ├── TypeWriter.tsx # 打字机效果组件
|
||||
│ └── index.ts # 组件导出
|
||||
├── stores/
|
||||
│ └── ssaStore.ts # Zustand 状态管理
|
||||
├── hooks/
|
||||
│ ├── useAnalysis.ts # 分析相关 API 调用
|
||||
│ ├── useArtifactParser.ts # 流式 Artifact 标记解析
|
||||
│ └── index.ts
|
||||
├── styles/
|
||||
│ └── ssa-workspace.css # V11 完整样式(~1900 行)
|
||||
└── types/
|
||||
└── index.ts # 类型定义
|
||||
```
|
||||
|
||||
## 12. 端到端测试清单
|
||||
|
||||
### 12.1 基本功能测试
|
||||
|
||||
| 序号 | 测试项 | 预期结果 | 状态 |
|
||||
|------|--------|---------|------|
|
||||
| 1 | 打开 SSA 模块 | 全屏显示,左侧收起的抽屉栏,中间欢迎消息 | ⬜ |
|
||||
| 2 | 点击汉堡菜单 | 左侧抽屉栏展开,显示 Logo、新建按钮、历史记录 | ⬜ |
|
||||
| 3 | 点击回形针上传数据 | 显示上传进度,成功后显示文件信息 | ⬜ |
|
||||
| 4 | 输入分析查询并发送 | 显示 AI 响应,右侧工作区自动打开 SAP 视图 | ⬜ |
|
||||
| 5 | 点击"开始执行分析" | 切换到执行视图,显示实时日志 | ⬜ |
|
||||
| 6 | 执行完成 | 切换到结果视图,显示统计表格、图表、AI 解读 | ⬜ |
|
||||
| 7 | 点击"查看代码" | 弹出代码模态框,显示 R 代码 | ⬜ |
|
||||
| 8 | 点击"下载 .R 文件" | 下载真实的分析代码文件 | ⬜ |
|
||||
| 9 | 点击"导出报告" | 下载 HTML 格式的分析报告 | ⬜ |
|
||||
| 10 | 按 Esc 键 | 关闭代码模态框/工作区 | ⬜ |
|
||||
| 11 | 按 Ctrl+B | 切换左侧抽屉栏 | ⬜ |
|
||||
| 12 | 按 Ctrl+N | 新建分析(重置状态) | ⬜ |
|
||||
| 13 | 点击返回按钮 | 返回上一页 | ⬜ |
|
||||
|
||||
### 12.2 错误处理测试
|
||||
|
||||
| 序号 | 测试项 | 预期结果 | 状态 |
|
||||
|------|--------|---------|------|
|
||||
| 1 | 上传无效文件格式 | 显示错误提示卡片 | ⬜ |
|
||||
| 2 | R 服务不可用时执行 | 显示执行失败视图,可点击重试 | ⬜ |
|
||||
| 3 | 网络断开时操作 | 显示错误 Toast | ⬜ |
|
||||
|
||||
### 12.3 UI/UX 测试
|
||||
|
||||
| 序号 | 测试项 | 预期结果 | 状态 |
|
||||
|------|--------|---------|------|
|
||||
| 1 | 对话区滚动 | 内容可滚动,输入框不遮挡按钮 | ⬜ |
|
||||
| 2 | AI 消息打字机效果 | 文字逐字显示,带闪烁光标 | ⬜ |
|
||||
| 3 | 动态引擎状态 | 根据状态显示 Ready/Processing/Running | ⬜ |
|
||||
| 4 | 历史记录加载 | 展开抽屉时加载历史,显示加载状态 | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 13. 版本更新记录
|
||||
|
||||
| 版本 | 日期 | 更新内容 |
|
||||
|------|------|---------|
|
||||
| v1.0 | 2026-02-20 | 初始版本 |
|
||||
| v1.1 | 2026-02-20 | 根据审查反馈更新:<br>• 盲区一:新增流式 Artifact 标记解析任务<br>• 盲区二:补充 hydrateFromHistory 状态恢复逻辑<br>• 盲区三:风险章节注明响应式降级延后至 Phase 2<br>• 盲区四:新增 6.3 节,说明流式响应技术方案 |
|
||||
| v2.0 | 2026-02-20 | **V11 升级:**<br>• 全屏 Gemini 风格布局<br>• 删除 22 个旧版组件<br>• 新增 6 个 V11 核心组件<br>• 完善报告/代码导出功能<br>• 实现流式 Artifact 标记解析框架<br>• 更新文件结构和测试清单 |
|
||||
|
||||
---
|
||||
|
||||
**文档维护者:** SSA 开发团队
|
||||
**最后更新:** 2026-02-20
|
||||
**审查状态:** ✅ 已通过(2026-02-20)
|
||||
103
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 前端 UI 改进计划审查报告.md
Normal file
103
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 前端 UI 改进计划审查报告.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# **SSA-Pro 前端 UI 改进计划 (V8版) 深度审查与风险排雷报告**
|
||||
|
||||
**审查对象:** 05-前端UI改进开发计划-V8双屏版.md
|
||||
|
||||
**审查时间:** 2026-02-19
|
||||
|
||||
**审查结论:** 🟢 **A- (方向精准,复用策略极佳,但缺乏复杂状态联动与降级方案细节)**
|
||||
|
||||
## **1\. 总体评价与亮点 (Highlights)**
|
||||
|
||||
这份开发计划有 3 个非常出色的地方,必须予以肯定:
|
||||
|
||||
1. **跨模块资产复用意识极强**:果断决定将 AIA 模块的 ResizableSplitPane 下沉到 shared/components,这能省去至少 2 天的拖拽和边界计算开发时间。
|
||||
2. **组件职责拆分清晰**:ArtifactPane 作为容器,内部按状态分发 SAPViewer、ExecutionViewer、ResultViewer,这是非常标准的策略模式(Strategy Pattern),易于后期扩展(比如新增 MLViewer)。
|
||||
3. **Zustand 状态管理切入点准确**:意识到了左右双屏需要全局状态管理来解耦。
|
||||
|
||||
## **2\. 技术盲区与改进建议 (Critical Blind Spots)**
|
||||
|
||||
结合 SSA-Pro 的实际业务需求(如流式响应、护栏动画、历史记录),当前计划在以下 5 个维度存在缺失,需要**立即补充到架构设计中**:
|
||||
|
||||
### **🚨 盲区一:左侧流式输出与右侧面板的“精准握手” (Stream-to-Artifact Sync)**
|
||||
|
||||
* **问题描述**:计划中未说明左侧聊天框 (ChatPane) 在接收大模型**流式响应 (Streaming)** 时,如何触发右侧面板 (ArtifactPane) 的切换。
|
||||
* **业务场景**:AI 正在打字输出:“我为您生成了分析方案...”,此时右侧应该**立刻**切换到 SAPViewer,而不是等 AI 把整段话说完才切换。
|
||||
* **技术挑战**:如果复用 AIA 的 AIStreamChat,它是如何解析带有结构化意图(如生成卡片指令)的 Markdown 流的?
|
||||
* **改进建议**:
|
||||
* 采用 **"特定标记解析"** 或 **"Tool Calling"** 机制。
|
||||
* 必须在前端实现一个 Stream Interceptor(流拦截器)。当流中出现类似 \<Artifact type="sap"\> 的占位符时,立刻触发 ssaStore.setActivePane('sap')。
|
||||
|
||||
### **🚨 盲区二:历史记录回溯的“状态水合” (History Hydration)**
|
||||
|
||||
* **问题描述**:双屏模式下,如果用户点击了侧边栏的“历史记录(比如昨天的 T 检验)”,右侧面板该显示什么?
|
||||
* **业务场景**:用户点击历史记录,不仅左侧要加载聊天记录,右侧还要**瞬间恢复**到当时最终的 ResultViewer 状态和图表数据。
|
||||
* **改进建议**:
|
||||
* ssaStore 中的状态必须是**可序列化和反序列化的**。
|
||||
* 后端返回的历史会话接口 GET /sessions/:id 中,除了 messages 数组,必须包含 current\_artifact\_state。
|
||||
* 在前端计划中增加任务:**"实现历史会话切换时的 Artifact 状态恢复逻辑"**。
|
||||
|
||||
### **🚨 盲区三:响应式降级策略完全缺失 (Responsive Downgrade)**
|
||||
|
||||
* **问题描述**:V8 计划只考虑了宽屏 PC。但如果用户在 13 寸小笔记本(屏幕宽度 \< 1200px)或平板上打开,双屏会极其拥挤,三线表会挤变形。
|
||||
* **技术挑战**:如何让双屏设计在小屏幕下依然可用?
|
||||
* **改进建议**:
|
||||
* 引入 CSS 媒体查询 (@media) 或 Tailwind 的 lg: 断点。
|
||||
* **降级方案**:当屏幕宽度小于 1024px 时,隐藏右侧 ArtifactPane,将其转化为一个 **全屏抽屉 (Drawer)** 或 **浮动模态框 (Modal)**。左侧气泡中增加一个明显的按钮:“点击查看结果面板”。
|
||||
|
||||
### **🚨 盲区四:ExecutionViewer 的动态数据流机制**
|
||||
|
||||
* **问题描述**:计划中提到了 ExecutionViewer(执行日志视图),但没有说明它的数据是怎么来的。
|
||||
* **业务场景**:点击“执行”后,R 服务可能要跑 3-5 秒。这期间,护栏检查(正态性、方差)的状态是逐条出来的。
|
||||
* **改进建议**:
|
||||
* 明确采用 **轮询 (Polling)** 还是 **服务器推送 (SSE)**。
|
||||
* 如果 MVP 阶段后端是同步阻塞返回(没有 SSE),前端 ExecutionViewer 必须实现一套 **“伪进度/骨架屏动画” (Simulated Progress)** 机制来安抚用户等待情绪,直到真实数据返回后再一次性渲染。
|
||||
|
||||
### **🚨 盲区五:前端数据 Schema 解析前置 (Client-side Schema Extraction)**
|
||||
|
||||
* **问题描述**:计划中提到了 DataMountZone,但未说明数据是如何解析的。
|
||||
* **业务场景**:为了保护隐私,SSA-Pro 架构要求**真实数据不传给大模型,只传列名 (Schema)**。
|
||||
* **改进建议**:
|
||||
* 前端在 DataMountZone 组件中,必须集成轻量级的解析库(如 papaparse 处理 CSV,或使用 FileReader 提取前两行)。
|
||||
* 在文件上传前,**前端先提取表头**,拼接到用户的 Prompt 中发送给 Planner。
|
||||
|
||||
## **3\. 架构师建议的 Store 结构补充**
|
||||
|
||||
为了支撑上述复杂的交互,建议在开发计划的 ssaStore.ts 部分,补充以下具体的 State 结构定义:
|
||||
|
||||
// 推荐的 ssaStore.ts 核心结构
|
||||
interface SsaState {
|
||||
// 1\. 全局模式与上下文
|
||||
sessionId: string | null;
|
||||
mode: 'consult' | 'analysis';
|
||||
|
||||
// 2\. 数据挂载状态
|
||||
mountedData: {
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
schema: string\[\]; // 表头数组 (关键)
|
||||
ossKey?: string; // 上传后的引用
|
||||
} | null;
|
||||
|
||||
// 3\. 右侧工作区状态 (The Artifacts)
|
||||
activeArtifact: 'empty' | 'sap' | 'execution' | 'result';
|
||||
artifactData: {
|
||||
sap?: SapPlan; // AI 生成的计划
|
||||
execution?: TraceLog\[\]; // 执行过程日志
|
||||
result?: AnalysisResult; // 最终图表和三线表
|
||||
};
|
||||
|
||||
// 4\. Actions
|
||||
setMountedData: (data: any) \=\> void;
|
||||
setActiveArtifact: (type: 'empty' | 'sap' | 'execution' | 'result', data?: any) \=\> void;
|
||||
hydrateFromHistory: (sessionData: Session) \=\> void; // 解决盲区二
|
||||
}
|
||||
|
||||
## **4\. 行动指南 (Next Steps)**
|
||||
|
||||
请前端负责人(Tech Lead)基于此报告,对原 05-前端UI改进开发计划-V8双屏版.md 进行如下补充:
|
||||
|
||||
1. **Phase 1 基建期**:增加对 ResizableSplitPane 小屏幕降级 (Drawer) 的设计。
|
||||
2. **Phase 2 左侧期**:明确 DataMountZone 的纯前端解析表头逻辑。
|
||||
3. **Phase 3 右侧期**:补充左右屏状态联动的联调计划,特别是流式响应过程中的触发时机。
|
||||
|
||||
**结论:** 计划非常优秀,只要把这几个“状态联动”的坑提前填平,这个 V8 版本的智能工作台将成为整个平台最惊艳的功能模块。准予在补充细节后启动开发!🚀
|
||||
244
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 前端UI改进计划-审查回应.md
Normal file
244
docs/03-业务模块/SSA-智能统计分析/06-开发记录/SSA-Pro 前端UI改进计划-审查回应.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# SSA-Pro 前端 UI 改进计划 - 审查回应
|
||||
|
||||
> **回应日期:** 2026-02-20
|
||||
> **回应人:** SSA 开发团队
|
||||
> **审查文档:** SSA-Pro 前端 UI 改进计划审查报告.md
|
||||
> **原计划文档:** 05-前端UI改进开发计划-V8双屏版.md
|
||||
|
||||
---
|
||||
|
||||
## 📋 总体评价
|
||||
|
||||
感谢审查团队的细致审核,总体评价 **A-** 及三个亮点(组件复用、职责拆分、Zustand 状态管理)的肯定,我们深感认可。
|
||||
|
||||
以下是对 5 个盲区的逐条回应:
|
||||
|
||||
---
|
||||
|
||||
## 盲区一:流式输出与右侧面板的"精准握手"
|
||||
|
||||
### 审查意见
|
||||
> AI 正在打字输出时,右侧应该立刻切换到 SAPViewer,建议采用"特定标记解析"或"Tool Calling"机制。
|
||||
|
||||
### 回应:✅ 认可,补充到计划
|
||||
|
||||
**我们的方案**:采用 **Artifact 标记解析** 机制
|
||||
|
||||
```typescript
|
||||
// AI 输出流中嵌入结构化标记
|
||||
"我已为您生成分析方案:<Artifact type='sap' id='plan-001' />"
|
||||
|
||||
// useAIStream Hook 中增加解析逻辑
|
||||
const handleChunk = (chunk: string) => {
|
||||
const artifactMatch = chunk.match(/<Artifact type='(\w+)' id='([^']+)' \/>/);
|
||||
if (artifactMatch) {
|
||||
const [_, type, id] = artifactMatch;
|
||||
ssaStore.setActivePane(type as 'sap' | 'execution' | 'result');
|
||||
ssaStore.loadArtifact(type, id);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**已有能力支撑**:
|
||||
- `useAIStream` Hook 已支持流式解析(见 `shared/components/Chat/hooks/useAIStream.ts`)
|
||||
- 后端 `StreamingService` 已支持 OpenAI Compatible 格式
|
||||
|
||||
**计划更新**:在 Day 2 任务中新增"流式标记解析器"开发(预计 2h)
|
||||
|
||||
---
|
||||
|
||||
## 盲区二:历史记录回溯的"状态水合"
|
||||
|
||||
### 审查意见
|
||||
> 用户点击历史记录时,右侧要瞬间恢复到当时最终的 ResultViewer 状态。
|
||||
|
||||
### 回应:✅ 部分认可,MVP 简化处理
|
||||
|
||||
**MVP 方案**:
|
||||
|
||||
| 步骤 | 处理方式 |
|
||||
|------|---------|
|
||||
| 1. 用户点击历史 | 左侧加载聊天记录 |
|
||||
| 2. 右侧初始状态 | 显示 `empty`(空状态) |
|
||||
| 3. 自动恢复 | 根据 session 数据判断最终状态,自动切换 |
|
||||
|
||||
**Store 扩展**:
|
||||
|
||||
```typescript
|
||||
// ssaStore.ts 新增
|
||||
hydrateFromHistory: (session: SSASession) => {
|
||||
// 根据 session 状态恢复右侧面板
|
||||
if (session.executionResult) {
|
||||
set({ activePane: 'result', currentArtifact: { type: 'result', data: session.executionResult } });
|
||||
} else if (session.currentPlan) {
|
||||
set({ activePane: 'sap', currentArtifact: { type: 'sap', data: session.currentPlan } });
|
||||
} else {
|
||||
set({ activePane: 'empty', currentArtifact: null });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**后端配合**:现有 `GET /api/v1/ssa/sessions/:id` 已返回完整 session 数据(含 plan、result),无需修改。
|
||||
|
||||
**计划更新**:在 Store 设计章节补充 `hydrateFromHistory` action。
|
||||
|
||||
---
|
||||
|
||||
## 盲区三:响应式降级策略
|
||||
|
||||
### 审查意见
|
||||
> 小屏幕(<1024px)双屏会拥挤,建议降级为抽屉或模态框。
|
||||
|
||||
### 回应:✅ 认可,但延后至 Phase 2
|
||||
|
||||
**理由**:
|
||||
1. V8 原型图明确定位 **PC 端宽屏**(1280px+)
|
||||
2. 目标用户为医院研究人员,主要使用 PC 办公
|
||||
3. MVP 聚焦核心体验,响应式作为增强功能
|
||||
|
||||
**Phase 2 降级方案**(预留设计):
|
||||
|
||||
```css
|
||||
/* 小屏幕降级:右侧变为全屏 Drawer */
|
||||
@media (max-width: 1024px) {
|
||||
.ssa-workspace {
|
||||
/* 隐藏右侧面板 */
|
||||
.artifact-pane { display: none; }
|
||||
|
||||
/* 左侧全宽 */
|
||||
.chat-pane { width: 100%; }
|
||||
}
|
||||
|
||||
/* 结果以 Drawer 形式弹出 */
|
||||
.artifact-drawer {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
width: 90vw;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**计划更新**:在"风险与应对"章节注明响应式降级作为 Phase 2 任务。
|
||||
|
||||
---
|
||||
|
||||
## 盲区四:ExecutionViewer 的动态数据流机制
|
||||
|
||||
### 审查意见
|
||||
> 未说明 ExecutionViewer 数据是轮询还是 SSE,建议明确技术方案。
|
||||
|
||||
### 回应:⚠️ 审查者对现有能力不了解
|
||||
|
||||
**现有能力**:
|
||||
|
||||
| 能力 | 位置 | 状态 |
|
||||
|------|------|------|
|
||||
| **useAIStream Hook** | `shared/components/Chat/hooks/useAIStream.ts` | ✅ 已实现 |
|
||||
| **StreamingService** | `backend/src/common/streaming/` | ✅ 已实现 |
|
||||
| **traceSteps 状态管理** | `ssaStore.ts` 第 32-35 行 | ✅ 已实现 |
|
||||
|
||||
```typescript
|
||||
// 现有 ssaStore.ts
|
||||
setTraceSteps: (steps: TraceStep[]) => void;
|
||||
updateTraceStep: (index: number, step: Partial<TraceStep>) => void;
|
||||
```
|
||||
|
||||
**SSA 执行日志的技术路径**:
|
||||
|
||||
| 方案 | 说明 | MVP 采用 |
|
||||
|------|------|---------|
|
||||
| **方案 A** | 对话流内嵌执行状态(复用 useAIStream) | ✅ 推荐 |
|
||||
| **方案 B** | 独立 SSE 端点 | Phase 2 |
|
||||
| **方案 C** | 同步返回 + 伪进度 | 备选 |
|
||||
|
||||
**推荐方案 A 示例**:
|
||||
|
||||
```
|
||||
// AI 对话流中输出执行状态
|
||||
data: {"choices":[{"delta":{"content":"🔍 正在检验正态性..."}}]}
|
||||
data: {"choices":[{"delta":{"content":"✅ Shapiro-Wilk p=0.32,符合正态分布"}}]}
|
||||
data: {"choices":[{"delta":{"content":"📊 正在执行独立样本 T 检验..."}}]}
|
||||
data: {"choices":[{"delta":{"content":"<Artifact type='result' id='xxx' />"}}]}
|
||||
```
|
||||
|
||||
**无需额外开发**:直接复用 `AIStreamChat` 组件,后端调整输出格式即可。
|
||||
|
||||
**计划更新**:在技术方案章节补充"执行日志数据流"说明。
|
||||
|
||||
---
|
||||
|
||||
## 盲区五:前端数据 Schema 解析前置
|
||||
|
||||
### 审查意见
|
||||
> 建议前端用 papaparse 提取表头,拼接到 Prompt 发送给 Planner。
|
||||
|
||||
### 回应:❌ 不采纳,违背隐私架构
|
||||
|
||||
**SSA 隐私保护架构**(核心设计原则):
|
||||
|
||||
```
|
||||
真实数据绝不传给 LLM,只传脱敏后的 Schema
|
||||
```
|
||||
|
||||
**正确的数据流**:
|
||||
|
||||
```
|
||||
┌─────────┐ 完整文件 ┌─────────┐ 脱敏 Schema ┌─────────┐
|
||||
│ 前端 │ ───────────────→ │ 后端 │ ───────────────→ │ LLM │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
↑
|
||||
DataParserService
|
||||
├── 隐藏稀有值 (<5)
|
||||
├── 模糊 Min/Max (N<10)
|
||||
└── 脱敏处理
|
||||
```
|
||||
|
||||
**如果前端解析 Schema 会发生什么**:
|
||||
|
||||
| 风险 | 说明 |
|
||||
|------|------|
|
||||
| 绕过隐私保护 | 原始 Schema 可能包含敏感分类值 |
|
||||
| 脱敏逻辑分散 | 后端已有 `DataParserService`,前端重复实现 |
|
||||
| 架构不一致 | 违背"后端统一控制"原则 |
|
||||
|
||||
**现有后端实现**:
|
||||
|
||||
```typescript
|
||||
// backend/src/modules/ssa/services/DataParserService.ts
|
||||
// 已实现:
|
||||
// - 表头解析
|
||||
// - 稀有值隐藏(<5)
|
||||
// - Min/Max 模糊(N<10)
|
||||
// - 脱敏 Schema 返回
|
||||
```
|
||||
|
||||
**结论**:保持现有设计,不在前端解析 Schema。
|
||||
|
||||
---
|
||||
|
||||
## 📝 计划更新汇总
|
||||
|
||||
| 盲区 | 处理方式 | 计划更新内容 |
|
||||
|------|---------|-------------|
|
||||
| **盲区一** | ✅ 采纳 | Day 2 新增"流式标记解析器"任务 |
|
||||
| **盲区二** | ✅ 部分采纳 | Store 设计章节补充 `hydrateFromHistory` |
|
||||
| **盲区三** | ⏸️ 延后 | 风险章节注明 Phase 2 处理 |
|
||||
| **盲区四** | 📝 澄清 | 技术方案章节补充执行日志数据流说明 |
|
||||
| **盲区五** | ❌ 不采纳 | 无需修改,保持后端 Schema 解析 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 最终结论
|
||||
|
||||
1. **计划总体方向正确**,审查团队的 A- 评分符合实际
|
||||
2. **盲区一、二、三** 的建议有价值,已纳入计划或延后处理
|
||||
3. **盲区四** 审查者对现有 SSE 能力不了解,已澄清技术路径
|
||||
4. **盲区五** 建议违背隐私架构设计,不予采纳
|
||||
|
||||
**准予启动开发!** 🚀
|
||||
|
||||
---
|
||||
|
||||
**回应人:** SSA 开发团队
|
||||
**日期:** 2026-02-20
|
||||
@@ -0,0 +1,239 @@
|
||||
# SSA-Pro V11 UI Development Summary
|
||||
|
||||
> **Date**: 2026-02-20
|
||||
> **Developer**: AI Assistant
|
||||
> **Status**: ✅ All tests passed
|
||||
|
||||
---
|
||||
|
||||
## 1. Development Overview
|
||||
|
||||
Today completed the SSA-Pro V11 UI frontend development and frontend-backend integration testing. Major achievements include:
|
||||
|
||||
- ✅ V11 UI pixel-perfect implementation (Gemini-style design)
|
||||
- ✅ Multi-task support (multiple analyses in single session)
|
||||
- ✅ Single-page scrolling workspace layout
|
||||
- ✅ Input box overlay bug fix (scroll spacer solution)
|
||||
- ✅ Word document export functionality
|
||||
- ✅ Full end-to-end integration testing passed
|
||||
|
||||
---
|
||||
|
||||
## 2. Completed Features
|
||||
|
||||
### 2.1 V11 UI Implementation
|
||||
|
||||
| Component | Description | Status |
|
||||
|-----------|-------------|--------|
|
||||
| `SSAWorkspace.tsx` | Main workspace container | ✅ |
|
||||
| `SSASidebar.tsx` | Left collapsible sidebar | ✅ |
|
||||
| `SSAChatPane.tsx` | Central chat area | ✅ |
|
||||
| `SSAWorkspacePane.tsx` | Right dynamic workspace | ✅ |
|
||||
| `SSACodeModal.tsx` | R code viewer modal | ✅ |
|
||||
| `SSAToast.tsx` | Global toast notifications | ✅ |
|
||||
| `TypeWriter.tsx` | Typewriter effect for AI responses | ✅ |
|
||||
|
||||
### 2.2 Multi-Task Support
|
||||
|
||||
- **Store Extension**: Added `analysisHistory: AnalysisRecord[]` and `currentRecordId`
|
||||
- **Record Management**: `addAnalysisRecord`, `updateAnalysisRecord`, `selectAnalysisRecord`
|
||||
- **Card Linking**: Chat cards carry `recordId` for task switching
|
||||
- **State Sync**: Phase state correctly syncs when switching records
|
||||
|
||||
### 2.3 Single-Page Scrolling Layout
|
||||
|
||||
- Replaced tab-based switching with single-page scrolling
|
||||
- Step progress bar: `Analysis Plan → Executing → Results`
|
||||
- Section dividers between SAP, execution log, and results
|
||||
- Auto-scroll to relevant section during execution
|
||||
|
||||
### 2.4 Input Box Overlay Fix
|
||||
|
||||
**Root Cause**: Flexbox layout ignores child element's `padding-bottom` for scroll calculation
|
||||
|
||||
**Solution**: Physical scroll spacer element
|
||||
|
||||
```tsx
|
||||
<div ref={chatEndRef} className="scroll-spacer" />
|
||||
```
|
||||
|
||||
```css
|
||||
.scroll-spacer {
|
||||
height: 220px;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 Word Document Export
|
||||
|
||||
- Implemented using `docx` library
|
||||
- Report structure: Data description → Method → Prerequisites → Descriptive stats → Results → Chart → Conclusion
|
||||
- Dynamic filename: `统计分析报告_{dataFileName}_{datetime}.docx`
|
||||
- Embedded chart images
|
||||
|
||||
---
|
||||
|
||||
## 3. Backend Changes
|
||||
|
||||
### 3.1 Plan Message Persistence
|
||||
|
||||
Added plan message saving in `/plan` route:
|
||||
|
||||
```typescript
|
||||
await prisma.ssaMessage.create({
|
||||
data: {
|
||||
sessionId: id,
|
||||
role: 'assistant',
|
||||
contentType: 'plan',
|
||||
content: { ...mockPlan, query }
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 3.2 Dynamic Filename in R Code
|
||||
|
||||
- `RClientService.ts`: Extract original filename from session
|
||||
- `t_test_ind.R`: Use `input$original_filename` in generated code
|
||||
|
||||
### 3.3 Data Format Transformation
|
||||
|
||||
- Backend converts snake_case to camelCase for frontend
|
||||
- Plots array transformation (raw base64 → structured objects)
|
||||
|
||||
---
|
||||
|
||||
## 4. Bug Fixes
|
||||
|
||||
| Issue | Root Cause | Solution |
|
||||
|-------|------------|----------|
|
||||
| Input box overlay | Flexbox padding-bottom ignored | Scroll spacer element |
|
||||
| Multi-task state confusion | Phase state not reset on switch | useEffect on currentRecordId |
|
||||
| Variable names hardcoded | Not reading from plan parameters | Dynamic extraction with fallback |
|
||||
| P-value not displaying | snake_case/camelCase mismatch | Backend transformation |
|
||||
| Image loading failure | Raw base64 string format | Frontend/backend transformation |
|
||||
| R code wrong filename | Hardcoded "your_data.csv" | Pass original_filename to R service |
|
||||
|
||||
---
|
||||
|
||||
## 5. File Changes Summary
|
||||
|
||||
### New Files (16)
|
||||
|
||||
```
|
||||
frontend-v2/src/modules/ssa/
|
||||
├── SSAWorkspace.tsx
|
||||
├── components/
|
||||
│ ├── SSAChatPane.tsx
|
||||
│ ├── SSASidebar.tsx
|
||||
│ ├── SSAWorkspacePane.tsx
|
||||
│ ├── SSACodeModal.tsx
|
||||
│ ├── SSAToast.tsx
|
||||
│ └── TypeWriter.tsx
|
||||
├── hooks/
|
||||
│ └── useArtifactParser.ts
|
||||
└── styles/
|
||||
└── ssa-workspace.css
|
||||
|
||||
docs/03-业务模块/SSA-智能统计分析/
|
||||
├── 03-UI设计/
|
||||
│ ├── V11.html
|
||||
│ ├── 产品原型图V8双屏版.html
|
||||
│ └── SSA-Pro UX方案对比与技术选型报告.md
|
||||
├── 04-开发计划/
|
||||
│ └── 05-前端UI改进开发计划-V8双屏版.md
|
||||
└── 06-开发记录/
|
||||
├── SSA-Pro 前端 UI 改进计划审查报告.md
|
||||
├── SSA-Pro 前端UI改进计划-审查回应.md
|
||||
└── UI遮挡Bug终极修复指南.md
|
||||
```
|
||||
|
||||
### Modified Files (10)
|
||||
|
||||
```
|
||||
backend/src/modules/ssa/
|
||||
├── executor/RClientService.ts # Add original_filename
|
||||
└── routes/analysis.routes.ts # Plan persistence + data transformation
|
||||
|
||||
frontend-v2/src/modules/ssa/
|
||||
├── index.tsx # Switch to SSAWorkspace
|
||||
├── stores/ssaStore.ts # Multi-task support
|
||||
├── hooks/useAnalysis.ts # Word export + record management
|
||||
├── types/index.ts # Add recordId, snake_case fields
|
||||
└── components/index.ts # Export updates
|
||||
|
||||
r-statistics-service/tools/
|
||||
└── t_test_ind.R # Dynamic filename
|
||||
```
|
||||
|
||||
### Deleted Files (7)
|
||||
|
||||
Old V8/V9 components removed:
|
||||
- APATable.tsx, ExecutionProgress.tsx, ExecutionTrace.tsx
|
||||
- ModeSwitch.tsx, PlanCard.tsx, ResultCard.tsx
|
||||
- SAPDownloadButton.tsx, SAPPreview.tsx
|
||||
|
||||
---
|
||||
|
||||
## 6. Testing Results
|
||||
|
||||
### Integration Test Checklist
|
||||
|
||||
- [x] Upload CSV file (cqol-demo - 有缺失.csv)
|
||||
- [x] Generate analysis plan via natural language query
|
||||
- [x] View SAP with correct variable mapping
|
||||
- [x] Execute analysis and view real-time logs
|
||||
- [x] View results with P-value and chart
|
||||
- [x] Export Word report with all sections
|
||||
- [x] View and download R code with correct filename
|
||||
- [x] Switch between multiple analysis tasks
|
||||
- [x] Input box not blocking chat content
|
||||
- [x] Full-screen mode working correctly
|
||||
|
||||
### Browser Compatibility
|
||||
|
||||
- [x] Chrome (Incognito mode)
|
||||
- [x] Chrome (Normal mode)
|
||||
|
||||
---
|
||||
|
||||
## 7. Next Steps (Recommendations)
|
||||
|
||||
1. **Additional Statistical Methods**: Extend beyond T-test to ANOVA, Chi-square, etc.
|
||||
2. **SSE Streaming**: Real-time execution log streaming
|
||||
3. **History Persistence**: Save analysis history to database
|
||||
4. **Error Boundary**: Better error handling and recovery
|
||||
5. **Accessibility**: Keyboard navigation improvements
|
||||
|
||||
---
|
||||
|
||||
## 8. Technical Notes
|
||||
|
||||
### Pointer Events Solution
|
||||
|
||||
```css
|
||||
.chat-input-wrapper {
|
||||
pointer-events: none; /* Gradient overlay passes through */
|
||||
}
|
||||
.chat-input-container {
|
||||
pointer-events: auto; /* Container restores click */
|
||||
}
|
||||
.input-box {
|
||||
pointer-events: auto; /* Input box clickable */
|
||||
}
|
||||
```
|
||||
|
||||
### Scroll Spacer Solution
|
||||
|
||||
Physical DOM element replaces CSS padding-bottom which is ignored in Flexbox:
|
||||
|
||||
```tsx
|
||||
<div ref={chatEndRef} className="scroll-spacer" />
|
||||
```
|
||||
|
||||
This ensures bottom content is always scrollable above the floating input box.
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: v1.0
|
||||
**Last Updated**: 2026-02-20
|
||||
88
docs/03-业务模块/SSA-智能统计分析/06-开发记录/UI遮挡Bug终极修复指南.md
Normal file
88
docs/03-业务模块/SSA-智能统计分析/06-开发记录/UI遮挡Bug终极修复指南.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# **SSA-Pro UI 遮挡 Bug 终极修复方案**
|
||||
|
||||
**问题现象:** 悬浮输入框物理遮挡了聊天区最底部的按钮,导致无法点击。
|
||||
|
||||
**根本原因:** Flexbox 布局下子元素的 padding-bottom 滚动塌陷。
|
||||
|
||||
**解决方案:** 引入物理占位垫片 (Scroll Spacer)。
|
||||
|
||||
### **到底哪里出了问题?(真凶浮出水面)**
|
||||
|
||||
在您的 `ssa-workspace.css` 中,定义了:
|
||||
|
||||
CSS
|
||||
|
||||
.chat-messages-wrapper {
|
||||
|
||||
padding-bottom: 160px; /\* 您期望用它把滚动条撑高 \*/
|
||||
|
||||
}
|
||||
|
||||
**问题就在这里!** 当父容器(`.chat-container`)是 `display: flex; flex-direction: column;` 时,**现代浏览器会忽略子元素底部的 `margin-bottom` 和 `padding-bottom` 来计算滚动区域**。
|
||||
|
||||
这就导致了:
|
||||
|
||||
1. 滚动条以为到底了,其实并没有加上那 160px 的安全高度。
|
||||
2. 导致“确认执行”按钮在滚动到最底部时,**物理位置刚好停在了白色实体的输入框(`.input-container-inner`)的后面**!
|
||||
3. 虽然外层渐变是透明且穿透的,但内部白色输入框有 `pointer-events: auto`,它实实在在地像一堵墙一样盖在了按钮上面,所以您怎么都点不到!
|
||||
|
||||
请前端开发人员执行以下两步修改:
|
||||
|
||||
## **第一步:修改 React 组件 (SSAChatPane.tsx)**
|
||||
|
||||
找到您代码底部的 messagesEndRef 自动滚动锚点。我们给这个锚点加上一个特定的 className,让它充当“物理垫片”。
|
||||
|
||||
**修改前:**
|
||||
|
||||
{/\* 自动滚动锚点 \*/}
|
||||
\<div ref={messagesEndRef} /\>
|
||||
\</div\>
|
||||
\</div\>
|
||||
|
||||
{/\* 底部输入框... \*/}
|
||||
|
||||
**修改后(直接替换为下面这行):**
|
||||
|
||||
{/\* 自动滚动锚点与占位垫片 \- 解决底部输入框遮挡的终极方案 \*/}
|
||||
\<div ref={messagesEndRef} className="scroll-spacer" /\>
|
||||
\</div\>
|
||||
\</div\>
|
||||
|
||||
## **第二步:修改样式表 (ssa-workspace.css)**
|
||||
|
||||
清理掉之前失效的 padding 尝试,并为刚才的垫片添加高度样式。
|
||||
|
||||
**修改前:**
|
||||
|
||||
.chat-messages-wrapper {
|
||||
width: 100%;
|
||||
max-width: 768px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
min-height: 100%;
|
||||
padding-bottom: 160px; /\* \<--- 这个失效了,必须删掉 \*/
|
||||
}
|
||||
|
||||
**修改后(请仔细核对替换):**
|
||||
|
||||
.chat-messages-wrapper {
|
||||
width: 100%;
|
||||
max-width: 768px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
min-height: 100%;
|
||||
padding-bottom: 0px; /\* 删掉原来的 160px \*/
|
||||
}
|
||||
|
||||
/\* 🆕 新增:强制撑开底部空间的物理垫片 \*/
|
||||
.scroll-spacer {
|
||||
height: 220px; /\* 这个高度必须大于您悬浮输入框的最高高度 \*/
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
## **原理验证**
|
||||
|
||||
修改完成后,当大模型输出“确认执行”卡片并自动滚动到底部时,.scroll-spacer 会在按钮下方强制撑开 220px 的空白区域。这样,**按钮就会稳稳地停留在悬浮输入框的上方**,再也不会被盖住了。
|
||||
Reference in New Issue
Block a user