# TraceStudio 前端 v0.3.0 实现总结 **发布日期**: 2024-01 **版本**: v0.3.0 **关键特性**: Param 绑定模式、EdgeType 标记、暴露端口 --- ## 📋 概述 本版本完成了四大属性(InputSpec/OutputSpec/ParamSpec/ContextSpec)在前端的完整实现,包括: 1. **Param 绑定模式系统** - 三种参数值来源:静态值、Context 引用、暴露为端口 2. **连线 EdgeType 标记** - 数据维度的视觉编码:数组粗线(4px)、标量细线(3px) 3. **暴露端口显示** - Param 升级为输入端口、Context 暴露为输出端口 --- ## 🔧 核心改动 ### 1. 数据结构扩展 (`web/src/stores/useStore.ts`) #### 新增类型定义 ```typescript // Param 绑定模式 export type ParamBindingMode = 'static' | 'context' | 'exposed' export interface ParamBinding { mode: ParamBindingMode value?: any // 静态值(mode='static') contextRef?: string // 上下文引用(mode='context'),格式: $Global.VarName 或 $NodeID.VarName } export interface NodeSchema { // ... 现有字段 ... bindings?: Record // 参数绑定信息 exposedPorts?: { params: string[] // 暴露为输入端口的参数列表 contexts: string[] // 暴露为输出端口的 Context 变量列表 } } ``` **用途**: 支持参数值动态来源和端口暴露的数据模型 --- ### 2. API 类型规范化 (`web/src/utils/api.ts`) #### 扩展 getPlugins 返回类型 ```typescript // 从后端 /api/plugins 返回 { plugins: { [nodeType: string]: { display_name: string // 新增字段 inputs?: Array<{ name: string; type: string; description?: string }> outputs?: Array<{ name: string; type: string; description?: string }> param_schema?: Array<{ name: string; type: string; default?: any }> context_vars?: Record } } } ``` **用途**: 前端能正确解析并显示四大属性 --- ### 3. Inspector 参数绑定 UI (`web/src/components/Inspector.tsx`) #### 新增功能 ```tsx // 1. availableContextVars 计算(useMemo) const availableContextVars = useMemo(() => { // 收集 $Global.* 变量 // 递归查找上游节点的 context_vars // 返回可选变量列表 }, [node, nodes, edges]) // 2. 三种绑定模式按钮(inline) // 3. 模式对应的输入控件 if (binding.mode === 'static') { // input[number] 或 input[text] } if (binding.mode === 'context') { // select 下拉列表 } if (binding.mode === 'exposed') { // 提示文本 } ``` **用户交互流**: 1. 用户在 Inspector 中选择参数 2. 点击三个绑定模式按钮之一 3. 根据模式显示对应的输入控件 4. 修改时同时更新 `node.data[key]` 和 `node.bindings[key]` **数据流**: ``` 用户操作 → handleModeChange() → updateNodeData() → node.bindings[paramName] = { mode, value/contextRef } → node.exposedPorts.params.push(paramName) [仅 mode='exposed'] ``` --- ### 4. 连线 EdgeType 标记 (`web/src/components/Workspace.tsx`) #### 新增 Helper 函数 ```typescript function isArrayType(type?: string): boolean { return type?.includes('Array') || type?.includes('List') || type?.includes('Table') } ``` #### 修改 onConnect 处理器 ```typescript const onConnect = useCallback((connection: Connection) => { const sourceType = getNodeOutputType(nodes, connection.source, connection.sourceHandle) // 计算 edgeType const edgeType = isArrayType(sourceType) ? 'array' : 'scalar' const strokeWidth = edgeType === 'array' ? 4 : 3 // 添加到 edge.data 用于持久化 return addEdge({ ...connection, style: { strokeWidth }, data: { sourceType, edgeType, color: edgeColor } }, finalEdges) }, [nodes, setEdges]) ``` **视觉效果**: - 数组边:粗线(strokeWidth 4px)、亮蓝色 - 标量边:细线(strokeWidth 3px)、橙色/紫色 **执行动画保留**: ```typescript useEffect(() => { // 执行时更新边的动画状态 const updatedEdges = edges.map((edge) => { const baseStrokeWidth = edge.data?.edgeType === 'array' ? 4 : 3 return { ...edge, animated: executionStatus.running, style: { ...edge.style, strokeWidth: executionStatus.running ? baseStrokeWidth * 1.5 : baseStrokeWidth } } }) }, [executionStatus]) ``` --- ### 5. UniversalNode 暴露端口显示 (`web/src/components/nodes/UniversalNode.tsx`) #### 端口集合构建 ```typescript // 合并原始和暴露的端口 const allInputs = [ ...inputs, ...(exposedPorts.params || []).map(paramName => ({ name: paramName, type: 'Exposed', isExposed: true })) ] const allOutputs = [ ...outputs, ...(exposedPorts.contexts || []).map(contextName => ({ name: contextName, type: 'Context', isExposed: true })) ] ``` #### Handle 渲染(样式区分) ```tsx {allInputs.map((input, idx) => { const color = input.isExposed ? '#ec4899' : getTypeColor(input.type) const size = input.isExposed ? 14 : 12 return ( ) })} ``` #### 暴露端口指示区(新增) ```tsx {(exposedPorts.params?.length > 0 || exposedPorts.contexts?.length > 0) && (
{exposedPorts.params?.length > 0 &&
⚡ Params: {exposedPorts.params.join(', ')}
} {exposedPorts.contexts?.length > 0 &&
📋 Contexts: {exposedPorts.contexts.join(', ')}
}
)} ``` **视觉标记**: - 暴露 Param 端口:粉红色(#ec4899)、14x14、⚡ 标签 - 暴露 Context 端口:绿色(#22c55e)、14x14、📋 标签 - 节点内指示区:紫红色背景,列出所有暴露的端口名称 --- ### 6. 参数规范化 (`web/src/components/NodePalette.tsx`) #### normalizeParamSchema 函数 ```typescript function normalizeParamSchema(raw: any): Array { if (!raw) return [] if (Array.isArray(raw)) return raw // 对象转数组:Object → Array<{ name, ...spec }> return Object.entries(raw).map(([name, spec]) => ({ name, ...(spec as any) })) } ``` #### 插件加载时应用规范化 ```typescript const normalized = Object.fromEntries( Object.entries(response.data.plugins || {}).map(([type, meta]) => [ type, { ...meta, param_schema: normalizeParamSchema((meta as any).param_schema), inputs: (meta as any).inputs || [], outputs: (meta as any).outputs || [] } ]) ) ``` **用途**: 确保参数结构统一,前端代码不需分别处理对象和数组格式 --- ## 📊 功能对应表 | 需求 | 实现位置 | 关键代码 | 状态 | |------|----------|---------|------| | Param 三种绑定模式 | Inspector.tsx | ParamBindingMode type + 模式按钮 | ✅ 完成 | | Context 变量聚合 | Inspector.tsx | availableContextVars useMemo | ✅ 完成 | | 端口暴露动作 | Inspector.tsx | handleModeChange + exposedPorts | ✅ 完成 | | EdgeType 推断 | Workspace.tsx | isArrayType + onConnect | ✅ 完成 | | 连线粗细差异 | Workspace.tsx | strokeWidth 4/3 映射 | ✅ 完成 | | 暴露 Param 端口 | UniversalNode.tsx | allInputs 合并 + isExposed 标记 | ✅ 完成 | | 暴露 Context 端口 | UniversalNode.tsx | allOutputs 合并 + 绿色标记 | ✅ 完成 | | 端口指示区显示 | UniversalNode.tsx | exposedPorts 检查 + 紫红色区域 | ✅ 完成 | | 参数规范化 | NodePalette.tsx | normalizeParamSchema 函数 | ✅ 完成 | --- ## 🧪 测试场景 ### 场景 1:参数静态值 ``` 1. 创建 Transform 节点 2. Inspector 中参数选择 📝 静态值 3. 输入值(数字/文本) ✓ 验证:node.bindings[key] = { mode: 'static', value: ... } ``` ### 场景 2:Context 绑定 ``` 1. Loader → Transform(数据连线) 2. Transform 参数选择 🔗 Context 3. 下拉列表选择 $LoaderNode.Variable ✓ 验证:node.bindings[key] = { mode: 'context', contextRef: '$LoaderNode.Variable' } ``` ### 场景 3:端口暴露 ``` 1. Transform 参数点击 ⚡ 暴露端口 2. 节点左侧出现粉红色新端口 ✓ 验证:node.exposedPorts.params 包含该参数名 ``` ### 场景 4:EdgeType 视觉 ``` 1. Loader (DataTable 输出) → Transform 2. 连线显示为粗线(4px) ✓ 验证:edge.data.edgeType = 'array' ``` --- ## 🔌 集成注意事项 ### 前端与后端通讯 - **现状**: bindings 和 exposedPorts 存储在前端,未同步到后端 - **TODO**: 修改 `/api/graph/execute` 端点接收并处理这些元数据 ### 参数值替换流程(待实现) ``` 1. 用户执行工作流 2. 前端收集所有 bindings 3. 发送到后端 /api/graph/execute { nodes, edges, bindings, exposedPorts } 4. 后端解析 bindings: - mode='static' → 使用 value - mode='context' → 从全局/上游节点查询 contextRef - mode='exposed' → 从上游连线获取值 5. 执行节点时使用最终参数值 ``` ### 暴露端口连接处理(已支持) ``` 1. UniversalNode 显示暴露的端口(粉红/绿色) 2. 用户可拖拽从其他节点连接到暴露端口 3. Workspace.onConnect 正常处理,edge.target 指向暴露的 Handle ID 4. 执行时后端需识别 targetHandle 是否为暴露端口 ``` --- ## 📝 文件变更清单 | 文件 | 变更类型 | 关键改动 | |------|----------|---------| | `web/src/stores/useStore.ts` | 修改 | 新增 ParamBindingMode、ParamBinding、bindings、exposedPorts | | `web/src/utils/api.ts` | 修改 | 扩展 getPlugins 返回类型,包含四大属性 | | `web/src/components/Inspector.tsx` | 修改 | 新增 Param 绑定 UI、availableContextVars、三种模式输入 | | `web/src/components/Workspace.tsx` | 修改 | 新增 isArrayType、onConnect 中 edgeType 计算 | | `web/src/components/nodes/UniversalNode.tsx` | 修改 | 新增 allInputs/allOutputs、暴露端口指示区、端口样式差异 | | `web/src/components/NodePalette.tsx` | 修改 | 新增 normalizeParamSchema、应用规范化 | | `web/FEATURES_TEST.md` | 新建 | 功能验证清单和测试场景 | | `web/TEST_INTEGRATION.js` | 新建 | 浏览器控制台测试脚本 | | `web/IMPLEMENTATION_SUMMARY.md` | 新建 | 本文档 | --- ## 🎯 后续工作优先级 ### 🔴 高优先级 - [ ] 后端 `/api/graph/execute` 解析 bindings 并替换参数值 - [ ] 工作流保存/加载时持久化 bindings 和 exposedPorts - [ ] 删除上游节点时自动清空相关 contextRef ### 🟡 中优先级 - [ ] 连线右键菜单:修改 dimensionMode(EXPAND/COLLAPSE/BROADCAST) - [ ] 参数绑定撤销/重做支持 - [ ] Context 变量搜索框(列表过长时) ### 🟢 低优先级 - [ ] 批量修改参数绑定模式 - [ ] 暴露端口权限控制 - [ ] 性能优化:大规模图的渲染 --- ## 🚀 快速验证 ### 浏览器控制台测试 ```javascript // 1. 加载测试脚本 const script = document.createElement('script') script.src = '/TEST_INTEGRATION.js' document.head.appendChild(script) // 2. 运行完整测试 await testCompleteWorkflow() // 3. 或逐个测试 await testParamBinding() await testEdgeType() await testExposedPorts() ``` --- ## 📚 相关文档 - [FEATURES_TEST.md](./FEATURES_TEST.md) - 详细测试场景 - [TEST_INTEGRATION.js](./TEST_INTEGRATION.js) - 控制台测试脚本 - [../docs/web1.0/](../docs/web1.0/) - 设计文档存档 --- **版本信息**: v0.3.0 | 2024-01 | 前端完整开发