结果分析标签页工作区 设计文档
日期: 2026-03-13 状态: 待实现
背景
当前"结果管理"页面采用三层面包屑导航(列表 → 实验详情 → 任务分析),每次只能查看一个任务的分析结果,切换任务时替换当前视图。
本设计将"结果分析"拆分为独立页面,采用 IDE 式标签页工作区,支持同时打开多个任务的分析视图并自由切换。
需求
| 需求 | 说明 |
|---|---|
| 独立入口 | 侧边栏新增"结果分析"菜单项 |
| 多标签 | 同时打开最多 10 个任务分析标签 |
| 去重 | 同一任务再次打开时切换到已有标签 |
| LRU 淘汰 | 超出 10 个时自动关闭最早打开的标签 |
| 持久化 | 标签元数据存 localStorage,刷新后恢复 |
| 打开入口 | 仅从"结果管理"TaskDetailPanel 触发 |
架构概览
侧边栏 [结果分析] → viewMode = 'analysis'
↓
ResultAnalysisPage
┌─────────────────────────────────────┐
│ [实验A·#abc1 ×] [实验B·#xyz2 ×] [+?]│ ← 标签栏
├─────────────────────────────────────┤
│ │
│ TaskAnalysisView (复用现有组件) │
│ KPI / Gantt / Roofline / 内存 / │
│ 通信流量 / ... │
│ │
└─────────────────────────────────────┘
无标签时显示空状态:
"暂无任务分析,请从结果管理中打开"
依赖方向:ResultAnalysisPage → TaskAnalysisView(复用)← Results 页面(仍继续使用)
数据模型
AnalysisTab(存 localStorage)
interface AnalysisTab {
taskId: string // 用于去重,等于 EvaluationTask.task_id
experimentId: number
experimentName: string
taskLabel: string // 显示名,如 "实验A · prefill"
openedAt: number // Unix 时间戳,用于 LRU 淘汰
}
localStorage key: tier6_analysis_tabs
格式: AnalysisTab[](仅元数据,无大 JSON)
运行时缓存(内存,不持久化)
// 按需 fetch,缓存在 Map
tabResultsCache: Map<taskId, TaskResultsResponse>
// 对应的 analysisResults(由 useAnalysisResults 派生)
tabAnalysisCache: Map<taskId, PlanAnalysisResult[]>
页面刷新后,从 localStorage 恢复标签列表,激活某标签时按需重新 fetch 对应的 taskResults。
组件结构
新建文件
frontend/src/
├── contexts/
│ └── AnalysisTabsContext.tsx # 标签状态管理
└── pages/
└── ResultAnalysis/
└── index.tsx # ResultAnalysisPage 主页面
修改文件
| 文件 | 改动 |
|---|---|
contexts/UIStateContext.tsx | ViewMode 加 'analysis' |
layouts/MainLayout/index.tsx | 挂载 ResultAnalysisPage |
layouts/MainLayout/Sidebar.tsx | 加"结果分析"菜单项 |
contexts/WorkbenchContext.tsx | 加 AnalysisTabsProvider 包裹层 |
pages/Results/components/TaskDetailPanel.tsx | 加"在结果分析中打开"按钮 |
pages/Results/components/TaskAnalysisView.tsx | 加 showBreadcrumb?: boolean prop(tabbed 模式隐藏面包屑) |
AnalysisTabsContext API
interface AnalysisTabsContextType {
// 标签列表(最多 10 个)
tabs: AnalysisTab[]
// 当前激活的 taskId
activeTabId: string | null
// 运行时结果缓存
tabResultsCache: Map<string, TaskResultsResponse>
// 是否正在加载某标签的结果
loadingTabId: string | null
// 打开/切换标签(去重 + LRU 淘汰)
openTab: (task: EvaluationTask, experiment: Experiment) => void
// 关闭标签
closeTab: (taskId: string) => void
// 切换激活标签
setActiveTab: (taskId: string) => void
}
openTab 逻辑
openTab(task, experiment):
1. 已存在同 taskId 的标签 → setActiveTab(taskId),返回
2. tabs.length >= 10 → 删除 openedAt 最小的那个(LRU)
3. 构造新 AnalysisTab,追加到 tabs
4. setActiveTab(新 taskId)
5. 触发 fetchResults(taskId)(如缓存中不存在)
6. 同步写 localStorage
7. 切换 viewMode 到 'analysis'
ResultAnalysisPage 布局
<div className="h-full flex flex-col">
{/* 标签栏 */}
<div className="flex items-center border-b overflow-x-auto flex-shrink-0">
{tabs.map(tab => (
<TabItem
key={tab.taskId}
active={tab.taskId === activeTabId}
label={tab.taskLabel}
loading={loadingTabId === tab.taskId}
onClick={() => setActiveTab(tab.taskId)}
onClose={() => closeTab(tab.taskId)}
/>
))}
</div>
{/* 内容区 */}
{activeTab ? (
<TaskAnalysisView
showBreadcrumb={false} // 新增 prop,隐藏面包屑
selectedExperiment={...}
selectedTask={...}
taskResults={tabResultsCache.get(activeTabId)}
taskResultsLoading={loadingTabId === activeTabId}
analysisResults={...}
onBackToList={() => {}} // 无操作(标签页模式不需要)
onBackToTasks={() => {}}
/>
) : (
<EmptyState message="暂无任务分析,请从结果管理中打开" />
)}
</div>
TaskDetailPanel 触发入口
在 TaskDetailPanel 底部(现有"性能分析"按钮附近)新增:
<Button
variant="outline"
size="sm"
onClick={() => {
analysisTabsContext.openTab(task, experiment)
// openTab 内部会切换 viewMode,不需要额外操作
}}
>
<BarChart2 className="mr-1 h-3 w-3" />
在结果分析中打开
</Button>
原有"性能分析"按钮保留(仍为结果管理内的面包屑跳转),两个按钮并存,用户可自由选择。
标签名生成规则
taskLabel = `${experiment.name} · ${task.config_snapshot?.eval_mode ?? 'task'}`
// 例:实验A · prefill
// 例:P1-R1-B4-C32 · decode
若 experimentName 过长,标签显示时 CSS 截断(max-width: 180px, text-overflow: ellipsis)。
localStorage 持久化策略
- 写入时机:每次 openTab / closeTab / setActiveTab 后立即写
- 读取时机:
AnalysisTabsContextProvider 初始化时一次性读取 - 恢复后行为:只恢复 tabs 元数据 + activeTabId,tabResultsCache 为空
- 激活标签时:检测缓存为空则自动 fetch taskResults
- 错误处理:fetch 失败时标签仍显示,内容区显示错误提示
实现步骤
AnalysisTabsContext.tsx— 实现 Context,含 localStorage 读写UIStateContext.tsx— 加'analysis'ViewModeWorkbenchContext.tsx— 加 Provider 包裹MainLayout— 路由 + 侧边栏ResultAnalysis/index.tsx— 主页面TaskAnalysisView.tsx— 加showBreadcrumbpropTaskDetailPanel.tsx— 加打开按钮
不在本次实现范围内
- 多标签对比视图(两个分析并排)
- 从侧边栏直接拖放排序标签
- 标签内容刷新按钮