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

8.6 KiB
Raw Blame History

这份详细的数据样本非常关键!它揭示了几个核心特征:

  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
节点结构
{
    "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:
{
  "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:
{
  "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 圈转动。
  1. NODE_PROGRESS (进度):
  • Payload: {"node_id": "n_xxx", "progress": 45, "message": "Parsing events..."}
  • UI 表现: 节点上方出现微型进度条。
  1. DIMENSION_EMIT (维度展开 - 关键!):
  • DirectoryScanner 扫出 100 个文件,后续节点会运行 100 次。
  • Payload: {"node_id": "n_trace_loader", "batch_index": 5, "total_batches": 100}
  • UI 表现: 连接线 (Edge) 上显示“粒子流动”动画,数字 5/100 跳动。
  1. PREVIEW_READY (预览就绪):
  • Payload: {"node_id": "n_xxx", "preview_type": "table_summary", "rows": 5000}
  • UI 表现: 节点变成“可点击”状态,显示“ 5k rows”。

4. 前端架构设计 (React + TS + Zustand)

为了支撑这套协议,前端必须采用 Registry (注册表) 模式。

4.1 目录结构重构

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

// 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 变化,实现动画。

// 伪代码思路
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 数据,定义 NodeSchemaParamSchemaExecutionGraph 的 TypeScript 接口。注意 param_schema 中的 optionswidget 字段。
  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 版本最核心的机制:后端定义数据 -> 前端注册表解析 -> 自动生成界面