TraceStudio-dev/docs/web1.0/FIX_CRITICAL_ISSUES.md
2026-01-07 19:34:45 +08:00

525 lines
16 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.

# 🔧 关键问题修复报告
## 🐛 问题诊断
### 问题 1Inspector 修改参数无效 ❌
**症状**
- 在 Inspector 面板修改节点参数
- 节点本体的参数预览不更新
- 切换节点后参数丢失
**根本原因**
```typescript
// ❌ 旧代码:仅单向同步 Workspace → Store
React.useEffect(() => {
setStoredNodes(nodes) // Workspace 变化 → Store
}, [nodes, setStoredNodes])
// ❌ 缺少反向同步 Store → Workspace
// Inspector 调用 updateNodeData 更新 Store 后
// Workspace 的 nodes 没有更新!
```
**数据流断裂**
```
Inspector 修改参数
updateNodeData() 更新 Store.nodes
storedNodes 变化
❌ Workspace 的 nodes 没有更新(缺少监听)
React Flow 不重新渲染
节点不刷新
```
---
### 问题 2聚合节点多输入未实现 ❌
**症状**
- 虽然 `main.py` 添加了 `node_logic: "aggregate"``allow_multiple_inputs: true`
- 但所有节点仍然执行单输入顶替逻辑
- Aggregator 节点无法连接多个数据源
**根本原因**
```typescript
// ❌ 旧代码:所有节点都执行单输入顶替
setEdges((eds) => {
const filtered = eds.filter((e) => {
// 无条件删除目标 Handle 的旧连线
if (e.target === connection.target &&
e.targetHandle === connection.targetHandle) {
return false
}
return true
})
return addEdge(connection, filtered)
})
// ❌ 没有检查 meta.allow_multiple_inputs
```
**预期行为**
- 普通节点CSVLoader, FilterRows单输入顶替
- 聚合节点Aggregator允许多条连线
---
## ✅ 解决方案
### 修复 1双向状态同步
**修改文件**`web/src/components/Workspace.tsx`
```typescript
// ✅ 新代码:双向同步 Store ↔ Workspace
// Workspace → Store拖拽、删除等操作
React.useEffect(() => {
setStoredNodes(nodes)
}, [nodes, setStoredNodes])
React.useEffect(() => {
setStoredEdges(edges)
}, [edges, setStoredEdges])
// Store → WorkspaceInspector 修改参数)
React.useEffect(() => {
// 使用回调形式更新,避免无限循环
setNodes((currentNodes) => {
// 如果 storedNodes 与当前 nodes 相同,不更新
if (JSON.stringify(currentNodes) === JSON.stringify(storedNodes)) {
return currentNodes
}
return storedNodes
})
}, [storedNodes, setNodes])
React.useEffect(() => {
setEdges((currentEdges) => {
if (JSON.stringify(currentEdges) === JSON.stringify(storedEdges)) {
return currentEdges
}
return storedEdges
})
}, [storedEdges, setEdges])
```
**修复后的数据流**
```
Inspector 修改参数
updateNodeData() 更新 Store.nodes创建新引用
storedNodes 变化
✅ useEffect 监听到 storedNodes 变化
setNodes(storedNodes) 更新 Workspace.nodes
React Flow 检测到 nodes 引用变化
UniversalNode 重新渲染useEffect([data]) 触发)
✅ 节点参数预览实时更新
```
**优化细节**
- 使用 `JSON.stringify` 比较避免不必要的更新
- 使用回调形式 `setNodes((current) => ...)` 避免无限循环
- React Flow 的 `useNodesState` 会正确处理引用更新
---
### 修复 2聚合节点多输入逻辑
**修改文件**
1. `server/main.py` - 添加缺失的 `allow_multiple_inputs` 字段
2. `web/src/components/Workspace.tsx` - 实现条件分支逻辑
#### main.py 修复
```python
"Aggregator": {
"display_name": "数据聚合",
"category": "Transform",
"node_logic": "aggregate", # ✅ 聚合节点标识
"allow_multiple_inputs": True, # ✅ 允许多输入
"supports_preview": True,
"inputs": [{"name": "input", "type": "DataTable"}],
"outputs": [{"name": "result", "type": "DataTable"}],
# ...
}
```
#### Workspace.tsx 修复
```typescript
// ✅ 新代码:根据节点类型决定连线策略
const onConnect = useCallback((connection: Connection) => {
if (!connection.source || !connection.target) return
const sourceType = getNodeOutputType(nodes, connection.source, connection.sourceHandle)
const edgeColor = TYPE_COLORS[sourceType] || TYPE_COLORS.Any
// 检查目标节点是否允许多输入
const targetNode = nodes.find(n => n.id === connection.target)
const allowMultipleInputs = targetNode?.data?.meta?.allow_multiple_inputs === true
setEdges((eds) => {
let finalEdges = eds
// 普通节点:执行单输入顶替逻辑
if (!allowMultipleInputs) {
finalEdges = eds.filter((e) => {
// 删除目标 Handle 的旧连线(单输入约束)
if (e.target === connection.target &&
e.targetHandle === connection.targetHandle) {
return false
}
return true
})
}
// 聚合节点:允许多条连线到同一输入
return addEdge({
...connection,
type: 'default',
animated: false,
style: {
stroke: edgeColor,
strokeWidth: 3,
opacity: 0.8,
},
data: {
sourceType,
color: edgeColor,
}
}, finalEdges)
})
}, [nodes, setEdges])
```
**逻辑流程图**
```
用户拖拽连线 → onConnect 触发
获取目标节点 targetNode
检查 targetNode.data.meta.allow_multiple_inputs
├─ false普通节点
│ ↓
│ 过滤掉旧连线(单输入顶替)
│ ↓
│ 添加新连线
└─ true聚合节点
保留所有旧连线
添加新连线(允许多输入)
```
---
## 🧪 测试验证
### 测试 1Inspector 参数修改(修复问题 1
**操作步骤**
```
1. 从 NodePalette 拖拽 "CSV 数据加载器" 到画布
┌─ CSV 数据加载器 ──────┐
│ file_path: │
│ delimiter: , │
└───────────────────────┘
2. 点击节点,打开 Inspector 面板
3. 修改 file_path 为 "data.csv"
✅ 预期结果:节点本体立即更新
┌─ CSV 数据加载器 [待更新] ─┐
│ file_path: data.csv │ ← ✅ 实时显示
│ delimiter: , │
└───────────────────────────┘
4. 点击其他节点,再点回 CSV Loader
✅ 预期结果:参数值保留为 "data.csv"
5. 刷新浏览器F5
✅ 预期结果:所有参数从 localStorage 恢复
```
**验证点**
- [x] 修改参数后节点立即刷新
- [x] 参数预览实时更新file_path: data.csv
- [x] 切换节点不丢失数据
- [x] 刷新页面数据保留
- [x] 显示橙色"待更新"标签
- [x] 预览成功后标签消失
---
### 测试 2聚合节点多输入修复问题 2
**操作步骤**
```
场景 A普通节点单输入约束
1. 拖拽 CSV Loader A
2. 拖拽 CSV Loader B
3. 拖拽 FilterRows
4. 连接 A.output → FilterRows.input
✅ 预期:显示一条连线
5. 连接 B.output → FilterRows.input
✅ 预期A 的连线被删除,只保留 B 的连线
CSV A ─╳──→ FilterRows ← 旧连线删除
CSV B ────→ FilterRows ← 新连线保留
场景 B聚合节点多输入支持
1. 拖拽 CSV Loader A
2. 拖拽 CSV Loader B
3. 拖拽 CSV Loader C
4. 拖拽 Aggregator数据聚合
5. 连接 A.output → Aggregator.input
✅ 预期:显示一条连线
6. 连接 B.output → Aggregator.input
✅ 预期A 和 B 的连线都保留
CSV A ────→ ╲
CSV B ────→ ─→ Aggregator ← 两条连线共存
7. 连接 C.output → Aggregator.input
✅ 预期A、B、C 的连线都保留
CSV A ────→ ╲
CSV B ────→ ─→ Aggregator ← 三条连线共存
CSV C ────→
```
**验证点**
- [x] FilterRows普通节点只保留最新连线
- [x] Aggregator聚合节点保留所有连线
- [x] 旧连线正确删除(普通节点)
- [x] 多条连线正确显示(聚合节点)
- [x] 连线颜色继承源节点类型
---
## 📊 技术对比
| 功能 | 修复前 | 修复后 |
|------|--------|--------|
| **状态同步** | 单向Workspace → Store | 双向Workspace ↔ Store |
| **Inspector 修改** | ❌ 不生效 | ✅ 实时刷新 |
| **参数保留** | ❌ 切换丢失 | ✅ 自动保留 |
| **脏数据标记** | ❌ 不显示 | ✅ 橙色标签 |
| **聚合节点** | ❌ 单输入 | ✅ 多输入 |
| **普通节点** | ✅ 单输入 | ✅ 单输入 |
| **连线策略** | 无条件顶替 | 条件分支 |
---
## 🔍 核心机制解析
### 1. 双向绑定的数据流
```
┌─────────────────────────────────────────────────────┐
│ Zustand Store │
│ nodes: NodeSchema[] │
│ updateNodeData(id, data) { │
│ const nodes = get().nodes.map(n => { │
│ if (n.id === id) { │
│ return { ...n, data: {...n.data, ...data} } │ ← 新引用
│ } │
│ return n │
│ }) │
│ set({ nodes }) │
│ localStorage.setItem(...) │
│ } │
└─────────────┬────────────────────┬──────────────────┘
│ │
│ storedNodes │ setStoredNodes(nodes)
↓ ↑
┌─────────────────────────────────────────────────────┐
│ Workspace.tsx │
│ const [nodes, setNodes] = useNodesState() │
│ │
│ // Store → Workspace │
│ useEffect(() => { │
│ setNodes((current) => { │
│ if (JSON.stringify(current) === JSON... │
│ return current // 避免无限循环 │
│ return storedNodes // 应用 Store 变化 │
│ }) │
│ }, [storedNodes]) │
│ │
│ // Workspace → Store │
│ useEffect(() => { │
│ setStoredNodes(nodes) │
│ }, [nodes]) │
└─────────────┬───────────────────────────────────────┘
│ nodes prop
┌─────────────────────────────────────────────────────┐
│ React Flow │
│ <ReactFlow nodes={nodes} /> │
│ // 检测 nodes 引用变化 → diff 算法 → 更新 DOM │
└─────────────┬───────────────────────────────────────┘
│ node data prop
┌─────────────────────────────────────────────────────┐
│ UniversalNode.tsx │
│ const UniversalNode = ({ data, dirty }) => { │
│ useEffect(() => { │
│ // data 变化时重新渲染 │
│ }, [data]) │
│ │
│ const value = data[key] // 读取最新参数值 │
│ return <div>{value}</div> // 显示参数预览 │
│ } │
└─────────────────────────────────────────────────────┘
```
### 2. 聚合节点判定逻辑
```typescript
// 元数据定义Python
{
"Aggregator": {
"node_logic": "aggregate",
"allow_multiple_inputs": true, // 关键字段
// ...
}
}
// 前端连线判定TypeScript
const targetNode = nodes.find(n => n.id === connection.target)
const allowMultipleInputs = targetNode?.data?.meta?.allow_multiple_inputs === true
if (!allowMultipleInputs) {
// 普通节点:删除旧连线
finalEdges = eds.filter(e =>
!(e.target === connection.target && e.targetHandle === connection.targetHandle)
)
} else {
// 聚合节点:保留所有连线
finalEdges = eds
}
```
### 3. 无限循环预防机制
**问题**:双向同步可能导致无限循环
```
Store 更新 → storedNodes 变化 → Workspace 更新 nodes
→ nodes 变化 → 触发 Store 更新 → storedNodes 变化 → ...
```
**解决**JSON 深度比较
```typescript
setNodes((currentNodes) => {
// 如果内容相同,返回当前引用(不触发更新)
if (JSON.stringify(currentNodes) === JSON.stringify(storedNodes)) {
return currentNodes // 阻止循环
}
return storedNodes // 应用新数据
})
```
**优化空间**
- 使用 `lodash.isEqual` 替代 `JSON.stringify`(性能更好)
- 添加 `useRef` 记录上次更新来源(避免重复同步)
---
## 🎯 修复效果总结
### 问题 1Inspector 参数修改 ✅
- **根本原因**:缺少 Store → Workspace 反向同步
- **修复方案**:添加 `useEffect` 监听 `storedNodes` 变化
- **验证结果**:✅ 修改参数后节点立即刷新,数据不丢失
### 问题 2聚合节点多输入 ✅
- **根本原因**:未检查 `allow_multiple_inputs` 字段
- **修复方案**:条件分支逻辑 + 后端元数据补全
- **验证结果**:✅ Aggregator 支持多条连线FilterRows 保持单输入
### 额外优化
- ✅ 脏数据标记(橙色"待更新"标签)
- ✅ 自动持久化到 localStorage
- ✅ 预览成功后清除脏标记
- ✅ 无限循环预防机制
---
## 📁 修改文件清单
1. **web/src/components/Workspace.tsx**
- ✅ 添加 Store → Workspace 双向同步
- ✅ 实现聚合节点多输入逻辑
- ✅ 添加 JSON 深度比较防止循环
2. **server/main.py**
- ✅ Aggregator 添加 `allow_multiple_inputs: True`
3. **web/src/stores/useStore.ts** (已在前次完成)
- ✅ updateNodeData 返回新引用
- ✅ 自动标记 dirty
- ✅ 自动持久化 localStorage
4. **web/src/components/nodes/UniversalNode.tsx** (已在前次完成)
- ✅ useEffect 监听 data 变化
- ✅ 显示脏数据标签
---
## 🚀 启动测试
```bash
# 终端 1启动后端
cd server
python main.py
# 终端 2启动前端
cd web
npm run dev
```
**测试清单**
1. 打开 http://localhost:5173
2. 拖拽 CSV Loader 到画布
3. 在 Inspector 修改 file_path
4. 验证节点实时刷新 ✅
5. 切换到其他节点再切回来
6. 验证参数保留 ✅
7. 拖拽两个 CSV Loader + 一个 Aggregator
8. 连接两个 Loader 到 Aggregator
9. 验证两条连线共存 ✅
---
**修复完成时间**2026-01-07
**状态**:✅ 两个核心问题已完全解决