TraceStudio-dev/docs/web/IMPLEMENTATION_SUMMARY.md
2026-01-09 21:37:02 +08:00

12 KiB
Raw Blame History

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)

新增类型定义

// 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<string, ParamBinding>  // 参数绑定信息
  exposedPorts?: {
    params: string[]  // 暴露为输入端口的参数列表
    contexts: string[]  // 暴露为输出端口的 Context 变量列表
  }
}

用途: 支持参数值动态来源和端口暴露的数据模型


2. API 类型规范化 (web/src/utils/api.ts)

扩展 getPlugins 返回类型

// 从后端 /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<string, any>
    }
  }
}

用途: 前端能正确解析并显示四大属性


3. Inspector 参数绑定 UI (web/src/components/Inspector.tsx)

新增功能

// 1. availableContextVars 计算useMemo
const availableContextVars = useMemo(() => {
  // 收集 $Global.* 变量
  // 递归查找上游节点的 context_vars
  // 返回可选变量列表
}, [node, nodes, edges])

// 2. 三种绑定模式按钮inline
<button onClick={() => handleModeChange('static')}>📝 静态值</button>
<button onClick={() => handleModeChange('context')}>🔗 Context</button>
<button onClick={() => handleModeChange('exposed')}> 暴露端口</button>

// 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 函数

function isArrayType(type?: string): boolean {
  return type?.includes('Array') || type?.includes('List') || type?.includes('Table')
}

修改 onConnect 处理器

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、橙色/紫色

执行动画保留:

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)

端口集合构建

// 合并原始和暴露的端口
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 渲染(样式区分)

{allInputs.map((input, idx) => {
  const color = input.isExposed ? '#ec4899' : getTypeColor(input.type)
  const size = input.isExposed ? 14 : 12
  
  return (
    <Handle
      style={{
        background: color,
        width: size, height: size,
        boxShadow: `0 0 8px ${color}80`
      }}
      title={input.isExposed ? `⚡ Exposed Param: ${input.name}` : input.name}
    />
  )
})}

暴露端口指示区(新增)

{(exposedPorts.params?.length > 0 || exposedPorts.contexts?.length > 0) && (
  <div style={{ background: 'rgba(236,72,153,0.08)', borderBottom: '1px solid rgba(236,72,153,0.2)' }}>
    {exposedPorts.params?.length > 0 && <div> Params: {exposedPorts.params.join(', ')}</div>}
    {exposedPorts.contexts?.length > 0 && <div>📋 Contexts: {exposedPorts.contexts.join(', ')}</div>}
  </div>
)}

视觉标记:

  • 暴露 Param 端口:粉红色(#ec4899、14x14、 标签
  • 暴露 Context 端口:绿色(#22c55e、14x14、📋 标签
  • 节点内指示区:紫红色背景,列出所有暴露的端口名称

6. 参数规范化 (web/src/components/NodePalette.tsx)

normalizeParamSchema 函数

function normalizeParamSchema(raw: any): Array<any> {
  if (!raw) return []
  if (Array.isArray(raw)) return raw
  // 对象转数组Object<name, spec> → Array<{ name, ...spec }>
  return Object.entries(raw).map(([name, spec]) => ({ name, ...(spec as any) }))
}

插件加载时应用规范化

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: ... }

场景 2Context 绑定

1. Loader → Transform数据连线
2. Transform 参数选择 🔗 Context
3. 下拉列表选择 $LoaderNode.Variable
✓ 验证node.bindings[key] = { mode: 'context', contextRef: '$LoaderNode.Variable' }

场景 3端口暴露

1. Transform 参数点击 ⚡ 暴露端口
2. 节点左侧出现粉红色新端口
✓ 验证node.exposedPorts.params 包含该参数名

场景 4EdgeType 视觉

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

🟡 中优先级

  • 连线右键菜单:修改 dimensionModeEXPAND/COLLAPSE/BROADCAST
  • 参数绑定撤销/重做支持
  • Context 变量搜索框(列表过长时)

🟢 低优先级

  • 批量修改参数绑定模式
  • 暴露端口权限控制
  • 性能优化:大规模图的渲染

🚀 快速验证

浏览器控制台测试

// 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()

📚 相关文档


版本信息: v0.3.0 | 2024-01 | 前端完整开发