TraceStudio-dev/docs/web1.1/2.md
Boshuang Zhao 5790ec164f add web v2
2026-01-10 19:08:49 +08:00

210 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

这份详细的数据样本非常关键!它揭示了几个核心特征:
1. **自动化的 Map/Reduce 逻辑**:注意看 `edges` 中的 `dimension_mode: "down"`。这说明你的系统支持“隐式循环” —— `DirectoryScanner` 产出一个数组,而 `TraceLoader` 接收单个文件,后端会自动将数组拆解并发执行。
2. **复杂参数配置**`ColumnOpsNode` 的参数是一个 JSON 字符串这意味着前端需要特殊的编辑器Monaco Editor而不仅仅是简单的文本框。
3. **强类型约束**Schema 中明确定义了 `DataTable`, `Array<String>`, `Number` 等类型。
基于你提供的这些真实数据,我为你制定了 **TraceStudio V2 API 协议与前端架构白皮书**。这是给 Copilot 执行的终极蓝图。
---
# 📜 TraceStudio V2 架构与协议白皮书
> **设计目标**:构建一个服务端驱动 (Server-Driven)、支持自动批处理 (Auto-Batching)、具备流式反馈 (Streaming) 的高性能数据分析平台。
## 1. 核心概念定义
在开始 API 设计前,必须统一前后端的领域术语:
* **Manifest (图谱)**: 后端返回的 `plugins` JSON定义了所有能力的边界。
* **Blueprint (蓝图)**: 前端发送给后端的 `nodes` + `edges` JSON描述计算逻辑。
* **Job (作业)**: 蓝图的一次实例化运行。
* **Artifact (产物)**: 节点运行产生的结果预览数据、CSV文件、图表
* **Dimension Expansion (维度展开)**: 当 `dimension_mode: "down"` 时,后端自动将 List 拆解为 Item 进行并发处理的过程。
---
## 2. API 协议规范 (The Protocol)
### 2.1 静态契约:能力发现 (Schema Discovery)
前端启动时调用,建立组件注册表。
* **Endpoint**: `GET /api/schema/manifest`
```json
节点结构
{
"nodes":{"DirectoryScanner":{"display_name":"目录扫描器","category":"IO/Scanner","description":"扫描目录并输出文件路径列表","icon":"📁","node_type":"input","class_name":"DirectoryScanner","node_logic":"standard","supports_preview":true,"inputs":[],"outputs":[{"name":"count","type":"Number","description":"文件数量"},{"name":"files","type":"Array<String>","description":"文件路径列表(数组)"}],"param_schema":[{"name":"max_files","type":"Number","default":0,"description":"最大文件数0=无限制)","min":0,"step":1},{"name":"reverse_sort","type":"Boolean","default":false,"description":"反向排序"},{"name":"sort_by","type":"String","default":"name","description":"排序方式","options":["name","size","modified","created","none"]},{"name":"recursive","type":"Boolean","default":false,"description":"是否递归扫描子目录"},{"name":"pattern","type":"String","default":"*.utrace","description":"文件匹配模式(支持 glob","required":true},{"name":"directory","type":"String","default":"","description":"要扫描的目录(相对于用户目录)","required":true}],"context_vars":{},"cache_policy":"none"}
}
```
### 2.2 动态契约:作业提交 (Job Submission)
前端不等待结果,只获取“工单号”。
* **Endpoint**: `POST /api/execution/queue`
* **Payload**: 直接使用你提供的 `nodes`, `edges`, `global_context` 结构。
* **Response**:
```json
{
"job_id": "job_20260110_xf82",
"status": "queued",
"queue_position": 1
}
```
### 2.3 交互契约:节点预览 (Lazy Interaction)
当用户点击某个已经跑完的节点时,前端才去拉取大数据。
* **Endpoint**: `GET /api/execution/{job_id}/node/{node_id}/preview`
* **Query**: `?limit=100&offset=0`
* **Response**:
```json
{
"type": "DataTable",
"schema": {"Duration": "Float64", "Name": "String"},
"data": [{"Duration": 33.5, "Name": "MainThread"}, ...] // 前100行
}
```
---
## 3. WebSocket 流式协议 (The Streaming Experience)
这是实现“节点流动、即时更新”的关键。后端在执行过程中通过 WS 推送 Event。
**连接**: `WS /ws/events?client_id={uuid}`
#### 事件类型定义:
1. **`JOB_STARTED`**: 整个图开始运行。
2. **`NODE_STATUS` (状态流转)**:
* Payload: `{"node_id": "n_xxx", "status": "running" | "completed" | "error"}`
* *UI 表现*: 节点边框变色Loading 圈转动。
3. **`NODE_PROGRESS` (进度)**:
* Payload: `{"node_id": "n_xxx", "progress": 45, "message": "Parsing events..."}`
* *UI 表现*: 节点上方出现微型进度条。
4. **`DIMENSION_EMIT` (维度展开 - 关键!)**:
*`DirectoryScanner` 扫出 100 个文件,后续节点会运行 100 次。
* Payload: `{"node_id": "n_trace_loader", "batch_index": 5, "total_batches": 100}`
* *UI 表现*: 连接线 (Edge) 上显示“粒子流动”动画,数字 `5/100` 跳动。
5. **`PREVIEW_READY` (预览就绪)**:
* Payload: `{"node_id": "n_xxx", "preview_type": "table_summary", "rows": 5000}`
* *UI 表现*: 节点变成“可点击”状态,显示“✅ 5k rows”。
---
## 4. 前端架构设计 (React + TS + Zustand)
为了支撑这套协议,前端必须采用 **Registry (注册表)** 模式。
### 4.1 目录结构重构
```text
src/core/
├── schema/ # 后端定义的静态类型
│ ├── NodeDefinition.ts # 对应 Manifest 中的节点
│ └── WidgetDefinition.ts # 控件类型定义
├── registry/ # 核心JSON -> React 组件的映射
│ ├── WidgetRegistry.tsx # { "Boolean": <Switch>, "JsonEditor": <Monaco> }
│ └── TypeRegistry.ts # { "DataTable": "#blue", "String": "#yellow" }
└── store/
└── executionStore.ts # 管理 WebSocket 消息和节点状态
```
### 4.2 核心模块WidgetRegistry (控件工厂)
处理你那个复杂的 `param_schema`
```tsx
// src/core/registry/WidgetRegistry.tsx
import { Switch } from '@/components/ui/switch';
import { JsonEditor } from '@/components/widgets/JsonEditor';
import { FilePicker } from '@/components/widgets/FilePicker';
const widgetMap = {
// 基础类型
'Boolean': (props) => <Switch checked={props.value} onCheckedChange={props.onChange} />,
'String': (props) => <Input value={props.value} onChange={props.onChange} />,
'Number': (props) => <Slider value={props.value} onChange={props.onChange} />,
// 高级控件 (通过 param_schema.widget 指定)
'JsonEditor': (props) => <JsonEditor code={props.value} language="json" />,
'FilePicker': (props) => <FilePicker path={props.value} pattern={props.schema.pattern} />,
'Select': (props) => <Select options={props.schema.options} ... />
};
export const renderWidget = (paramSchema, value, onChange) => {
// 1. 优先使用 schema 中显式定义的 widget (如 "widget": "file")
let WidgetComp = widgetMap[paramSchema.widget];
// 2. 其次根据 type 推断 (如 "type": "Boolean")
if (!WidgetComp) WidgetComp = widgetMap[paramSchema.type];
// 3. 兜底
if (!WidgetComp) WidgetComp = widgetMap['String'];
return <WidgetComp value={value} onChange={onChange} schema={paramSchema} />;
}
```
### 4.3 核心模块Execution Visualizer (流式可视化)
`GraphCanvas` 中监听 Store 变化,实现动画。
```tsx
// 伪代码思路
const EdgeRenderer = ({ edge }) => {
const executionState = useExecutionStore(s => s.nodeStatus[edge.target]);
// 如果是 down 模式且正在运行,显示粒子动画
const isFlowing = executionState?.status === 'running';
const isBatching = edge.dimension_mode === 'down';
return (
<svg>
<path className={isFlowing ? 'animate-flow-dash' : ''} ... />
{isBatching && <text>自动批处理中...</text>}
</svg>
)
}
```
---
## 5. 给 Copilot 的执行指令 (Prompt)
你可以直接复制以下内容发给 Copilot 开始下一步:
> **指令:初始化 V2 核心注册表与类型定义**
> 这里的背景:我们要重构 TraceStudio 的前端核心,采用服务端驱动 UI 的模式。
> 请在 `web/src/core` 下创建以下基础架构:
> 1. **`types.ts`**: 根据我提供的 `plugins` JSON 数据,定义 `NodeSchema`、`ParamSchema` 和 `ExecutionGraph` 的 TypeScript 接口。注意 `param_schema` 中的 `options` 和 `widget` 字段。
> 2. **`registry/WidgetRegistry.tsx`**: 创建一个注册表对象将后端类型String, Number, Boolean和特殊 WidgetJsonEditor, FilePicker映射到 React 组件(先用 Placeholder 组件代替)。实现一个 `renderWidget(schema, value, onChange)` 函数。
> 3. **`components/Inspector.tsx`**: 重写属性面板。它应该接收一个 `NodeModel`,遍历其 `param_schema`,并调用 `renderWidget` 来生成表单。
>
>
> 请确保代码严格遵循 TypeScript 类型安全,不要使用 `any`。
这样,你就确立了 V2 版本最核心的机制:**后端定义数据 -> 前端注册表解析 -> 自动生成界面**。