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

397 lines
12 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.

# 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<string, ParamBinding> // 参数绑定信息
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<string, any>
}
}
}
```
**用途**: 前端能正确解析并显示四大属性
---
### 3. Inspector 参数绑定 UI (`web/src/components/Inspector.tsx`)
#### 新增功能
```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 函数
```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 (
<Handle
style={{
background: color,
width: size, height: size,
boxShadow: `0 0 8px ${color}80`
}}
title={input.isExposed ? `⚡ Exposed Param: ${input.name}` : input.name}
/>
)
})}
```
#### 暴露端口指示区(新增)
```tsx
{(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 函数
```typescript
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) }))
}
```
#### 插件加载时应用规范化
```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: ... }
```
### 场景 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 变量搜索框(列表过长时)
### 🟢 低优先级
- [ ] 批量修改参数绑定模式
- [ ] 暴露端口权限控制
- [ ] 性能优化:大规模图的渲染
---
## 🚀 快速验证
### 浏览器控制台测试
```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 | 前端完整开发