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

18 KiB
Raw Blame History

🔄 节点功能与目录分离重构

📋 需求分析

原有问题

category 字段承担双重职责:

  1. 表示节点功能Loader/Transform/Visualizer→ 用于节点颜色、图标、Handle 显示
  2. 表示目录分类 → 用于 NodePalette 树状展示

冲突:无法实现 "Loader/CSV"、"Transform/Filter" 这样的二级目录分类

解决方案

category 和节点功能分离:

  • function: 节点功能Loader/Transform/Visualizer
  • category: 树状目录路径Loader/CSV, Transform/Filter

同时弃用前端的 node.type 属性,统一使用 function 字段。


后端修改main.py

所有节点添加 function 字段

# 修改前
"CSVLoader": {
    "display_name": "CSV 数据加载器",
    "category": "Loader",  # 既表示功能又表示目录
    "node_logic": "standard",
    # ...
}

# 修改后
"CSVLoader": {
    "display_name": "CSV 数据加载器",
    "function": "Loader",       # ✅ 节点功能:加载器
    "category": "Loader/CSV",   # ✅ 树状目录:加载器/CSV
    "node_logic": "standard",
    # ...
}

元数据结构完整示例

{
    "CSVLoader": {
        "display_name": "CSV 数据加载器",
        "function": "Loader",              # 节点功能
        "category": "Loader/CSV",          # 树状目录
        "node_logic": "standard",          # 节点逻辑
        "supports_preview": True,
        "inputs": [],
        "outputs": [{"name": "table", "type": "DataTable"}],
        "param_schema": {...}
    },
    
    "UTraceLoader": {
        "display_name": "UTrace 文件加载器",
        "function": "Loader",
        "category": "Loader/Trace",        # 二级目录Trace
        # ...
    },
    
    "FilterRows": {
        "display_name": "行过滤器",
        "function": "Transform",
        "category": "Transform/Filter",    # 二级目录Filter
        # ...
    },
    
    "SelectColumns": {
        "display_name": "列选择器",
        "function": "Transform",
        "category": "Transform/Select",    # 二级目录Select
        # ...
    },
    
    "Aggregator": {
        "display_name": "数据聚合",
        "function": "Transform",
        "category": "Transform/Aggregate",
        "node_logic": "aggregate",
        "allow_multiple_inputs": True,     # ✅ 多输入支持
        # ...
    },
    
    "TimeRangeFilter": {
        "display_name": "时间范围过滤",
        "function": "Transform",
        "category": "Transform/Filter",    # 与 FilterRows 共享目录
        # ...
    },
    
    "ChartVisualizer": {
        "display_name": "图表可视化",
        "function": "Visualizer",
        "category": "Visualizer/Chart",
        # ...
    },
    
    "TableOutput": {
        "display_name": "表格输出",
        "function": "Visualizer",
        "category": "Visualizer/Table",
        # ...
    }
}

完整节点清单

节点类型 function category node_logic allow_multiple_inputs
CSVLoader Loader Loader/CSV standard false
UTraceLoader Loader Loader/Trace standard false
FilterRows Transform Transform/Filter standard false
SelectColumns Transform Transform/Select standard false
Aggregator Transform Transform/Aggregate aggregate true
TimeRangeFilter Transform Transform/Filter standard false
ChartVisualizer Visualizer Visualizer/Chart standard false
TableOutput Visualizer Visualizer/Table standard false

前端修改

1. UniversalNode.tsx

接口重构

// 修改前
interface UniversalNodeProps {
  data: {
    meta?: {
      category?: 'Loader' | 'Transform' | 'Visualizer'  // ❌ 混用
    }
  }
}

// 修改后
interface UniversalNodeProps {
  data: {
    meta?: {
      function?: 'Loader' | 'Transform' | 'Visualizer'  // ✅ 节点功能
      category?: string  // ✅ 树状目录路径(如 "Loader/CSV"
    }
  }
}

常量重命名

// 修改前
const CATEGORY_COLORS: Record<string, string> = {
  Loader: '#3b82f6',
  Transform: '#8b5cf6',
  Visualizer: '#22c55e',
}

// 修改后
const FUNCTION_COLORS: Record<string, string> = {
  Loader: '#3b82f6',
  Transform: '#8b5cf6',
  Visualizer: '#22c55e',
}

组件实现

// 修改前
const category = data.meta?.category || 'Transform'
const headerColor = CATEGORY_COLORS[category] || '#6b7280'
const showInput = category === 'Transform' || category === 'Visualizer'
const showOutput = category === 'Loader' || category === 'Transform'

// 修改后
const nodeFunction = data.meta?.function || 'Transform'
const headerColor = FUNCTION_COLORS[nodeFunction] || '#6b7280'
const showInput = nodeFunction === 'Transform' || nodeFunction === 'Visualizer'
const showOutput = nodeFunction === 'Loader' || nodeFunction === 'Transform'

Header 显示

// 修改前
<div>{categoryIcon[category] || '🔹'}</div>
<div>{category}</div>

// 修改后
<div>{functionIcon[nodeFunction] || '🔹'}</div>
<div>{nodeFunction}</div>  {/* 显示 Loader/Transform/Visualizer */}

2. Inspector.tsx

图标映射

// 修改前
<span>
  {node.type === 'csv' ? '📂' : 
   node.type === 'transform' ? '⚙️' : 
   node.type === 'output' ? '📊' : '📦'}
</span>

// 修改后
<span>
  {node.data?.meta?.function === 'Loader' ? '📥' : 
   node.data?.meta?.function === 'Transform' ? '⚙️' : 
   node.data?.meta?.function === 'Visualizer' ? '📊' : '📦'}
</span>

标题显示

// 修改前
<h3>{node.type.toUpperCase()}</h3>  {/* CSV, TRANSFORM, OUTPUT */}

// 修改后
<h3>{node.data?.meta?.display_name || 'Node'}</h3>  {/* CSV 数据加载器 */}

系统属性

// 修改前
<div>
  <label>节点类型</label>
  <input value={node.type} disabled />  {/* csv, transform, output */}
</div>

// 修改后
<div>
  <label>节点功能</label>
  <input value={node.data?.meta?.function || 'Unknown'} disabled />  {/* Loader */}
</div>

<div>
  <label>目录分类</label>
  <input value={node.data?.meta?.category} disabled />  {/* Loader/CSV */}
</div>

3. Workspace.tsx

MiniMap 颜色映射

// 修改前
<MiniMap 
  nodeColor={(node) => {
    const category = node.data?.meta?.category
    if (category === 'Loader') return '#3b82f6'
    if (category === 'Transform') return '#8b5cf6'
    if (category === 'Visualizer') return '#22c55e'
    return '#64748b'
  }}
/>

// 修改后
<MiniMap 
  nodeColor={(node) => {
    const nodeFunction = node.data?.meta?.function
    if (nodeFunction === 'Loader') return '#3b82f6'
    if (nodeFunction === 'Transform') return '#8b5cf6'
    if (nodeFunction === 'Visualizer') return '#22c55e'
    return '#64748b'
  }}
/>

4. NodePalette.tsx

接口更新

// 修改前
interface PluginMeta {
  display_name?: string
  category?: string  // 混用
}

// 修改后
interface PluginMeta {
  display_name?: string
  function?: string   // 节点功能
  category?: string   // 树状目录
}

二级目录解析

// 修改前
const groups: Record<string, { key: string; items: Array<...> }> = {}
for (const [k, v] of Object.entries(plugins)) {
  const cat = (v.category || 'Other').split('/')[0]
  if (!groups[cat]) groups[cat] = { key: cat, items: [] }
  groups[cat].items.push({ type: k, meta: v })
}

// 修改后
const groups: Record<string, { 
  key: string; 
  items: Array<{ type: string; meta: PluginMeta; subCategory?: string }> 
}> = {}

for (const [k, v] of Object.entries(plugins)) {
  const categoryPath = v.category || 'Other'
  const [mainCat, subCat] = categoryPath.split('/')
  
  if (!groups[mainCat]) {
    groups[mainCat] = { key: mainCat, items: [] }
  }
  
  groups[mainCat].items.push({ 
    type: k, 
    meta: v,
    subCategory: subCat  // 保存二级分类
  })
}

算子卡片显示

// 修改前
<div>
  <span>
    {group.key === 'Loaders' ? '📂' : 
     group.key === 'Transforms' ? '⚙️' : '📦'}
  </span>
  <span>{item.meta.display_name}</span>
</div>

// 修改后
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
  {/* 主行:图标 + 名称 */}
  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
    <span>
      {item.meta.function === 'Loader' ? '📥' : 
       item.meta.function === 'Transform' ? '⚙️' : 
       item.meta.function === 'Visualizer' ? '📊' : '📦'}
    </span>
    <span>{item.meta.display_name}</span>
  </div>
  
  {/* 二级分类标签 */}
  {item.subCategory && (
    <div style={{ fontSize: 10, color: 'rgba(255,255,255,0.4)' }}>
      {item.subCategory}  {/* CSV, Trace, Filter, Select */}
    </div>
  )}
</div>

📊 视觉效果对比

NodePalette 显示

修改前

▸ Loaders (2)
  📂 CSV 数据加载器
  📂 UTrace 文件加载器

▸ Transforms (4)
  ⚙️ 行过滤器
  ⚙️ 列选择器
  ⚙️ 数据聚合
  ⚙️ 时间范围过滤

修改后

▸ Loader (2)
  📥 CSV 数据加载器
     CSV                    ← 二级分类标签
  📥 UTrace 文件加载器
     Trace                  ← 二级分类标签

▸ Transform (4)
  ⚙️ 行过滤器
     Filter                 ← 二级分类标签
  ⚙️ 列选择器
     Select
  ⚙️ 数据聚合
     Aggregate
  ⚙️ 时间范围过滤
     Filter                 ← 相同二级目录

Inspector 面板

修改前

┌─ 系统信息 ────────┐
│ 节点 ID: n_xxx    │
│ 节点类型: csv     │  ← 前端类型(混乱)
│ 分类: Loader      │  ← 混用
└──────────────────┘

修改后

┌─ 系统信息 ────────────┐
│ 节点 ID: n_xxx        │
│ 节点功能: Loader      │  ← 清晰的功能标识
│ 目录分类: Loader/CSV  │  ← 完整的目录路径
└──────────────────────┘

节点本体显示

修改前

┌─ CSV 数据加载器 ──┐
│ Loader            │  ← 显示 category
│ file_path: ...    │
└──────────────────┘

修改后

┌─ CSV 数据加载器 ──┐
│ LOADER            │  ← 显示 function功能
│ file_path: ...    │
└──────────────────┘

🎯 数据流追踪

从后端到前端的完整流程

1⃣ 后端定义main.py
   ↓
GET /plugins
   ↓
{
  "CSVLoader": {
    "function": "Loader",      ← 节点功能
    "category": "Loader/CSV"   ← 树状目录
  }
}
   ↓
2⃣ NodePalette 接收
   ↓
解析 category: "Loader/CSV"
   ├─ mainCat: "Loader"       ← 一级目录
   └─ subCat: "CSV"           ← 二级目录
   ↓
显示:
   Loader (2)
     📥 CSV 数据加载器
        CSV                   ← subCategory 标签
   ↓
3⃣ 拖拽到画布
   ↓
创建节点:
{
  id: "n_xxx",
  type: "universal",           ← 所有节点统一类型
  data: {
    meta: {
      function: "Loader",      ← 用于功能判断
      category: "Loader/CSV"   ← 用于显示目录
    }
  }
}
   ↓
4⃣ UniversalNode 渲染
   ↓
读取 function: "Loader"
   ├─ 决定颜色:#3b82f6蓝色
   ├─ 决定图标:📥
   ├─ 决定 Handle只显示 Output
   └─ 显示文字LOADER
   ↓
5⃣ Inspector 显示
   ↓
系统信息:
   - 节点功能: Loader          ← function
   - 目录分类: Loader/CSV      ← category

🔍 关键概念对比

function vs category

字段 用途 值域 示例 前端使用位置
function 节点功能 Loader, Transform, Visualizer "Loader" UniversalNode 颜色、图标、Handle 逻辑
category 树状目录 任意路径(支持 / 分隔) "Loader/CSV" NodePalette 分组、Inspector 显示

节点类型演变

阶段 前端类型 后端类型 功能标识 目录标识
Phase 1 node.type = 'csv' N/A 混乱
Phase 2 node.type = 'universal' meta.category ⚠️ category 混用 ⚠️ category 混用
Phase 3 (现在) node.type = 'universal' meta.function function category

🧪 测试验证

测试 1后端 API 验证

# 访问后端
curl http://127.0.0.1:8000/plugins

# 验证响应包含
{
  "plugins": {
    "CSVLoader": {
      "function": "Loader",       ✅
      "category": "Loader/CSV",   ✅
      "allow_multiple_inputs": false
    },
    "Aggregator": {
      "function": "Transform",
      "category": "Transform/Aggregate",
      "allow_multiple_inputs": true}
  }
}

测试 2NodePalette 二级目录

操作:打开应用
预期:左侧面板显示

✅ Loader (2)
     📥 CSV 数据加载器
        CSV                  ← 二级分类标签
     📥 UTrace 文件加载器
        Trace

✅ Transform (4)
     ⚙️ 行过滤器
        Filter
     ⚙️ 列选择器
        Select
     ⚙️ 数据聚合
        Aggregate
     ⚙️ 时间范围过滤
        Filter               ← 相同目录共享

测试 3节点功能识别

操作:拖拽 CSV Loader 到画布
预期:
  1. 节点颜色:蓝色(#3b82f6         ✅
  2. 节点图标:📥                      ✅
  3. Handle 显示:只有 Output          ✅
  4. 节点 Header显示 "LOADER"        ✅

操作:拖拽 FilterRows 到画布
预期:
  1. 节点颜色:紫色(#8b5cf6         ✅
  2. 节点图标:⚙️                      ✅
  3. Handle 显示Input + Output       ✅
  4. 节点 Header显示 "TRANSFORM"     ✅

测试 4Inspector 显示

操作:选中 CSV Loader 节点
预期 Inspector 显示:

┌─ 系统信息 ─────────────┐
│ 节点 ID: n_xxx         │  ✅
│ 节点功能: Loader       │  ✅ (原来是 node.type)
│ 目录分类: Loader/CSV   │  ✅ (新增)
└───────────────────────┘

┌─ 参数配置 ─────────────┐
│ file_path: [输入框]    │
│ delimiter: [输入框]    │
└───────────────────────┘

测试 5MiniMap 颜色

操作:拖拽多种节点到画布
预期 MiniMap 显示:
  - Loader 节点:蓝色       ✅
  - Transform 节点:紫色    ✅
  - Visualizer 节点:绿色   ✅

📁 修改文件清单

后端

  1. server/main.py
    • 所有节点添加 "function" 字段
    • 修改 "category" 为树状路径(支持 / 分隔)
    • Aggregator 添加 "allow_multiple_inputs": True

前端

  1. web/src/components/nodes/UniversalNode.tsx

    • CATEGORY_COLORSFUNCTION_COLORS
    • meta.categorymeta.function
    • categoryIconfunctionIcon
    • Header 显示 nodeFunction 代替 category
  2. web/src/components/Inspector.tsx

    • 图标映射:node.typenode.data.meta.function
    • 标题显示:node.type.toUpperCase()node.data.meta.display_name
    • 系统属性:删除 node.type,添加 functioncategory 显示
  3. web/src/components/Workspace.tsx

    • MiniMap 颜色:meta.categorymeta.function
  4. web/src/components/NodePalette.tsx

    • 接口添加 function 字段
    • 二级目录解析:category.split('/')[mainCat, subCat]
    • 算子卡片:显示 function 图标 + subCategory 标签

🎨 设计亮点

1. 职责分离

  • function: 节点功能逻辑颜色、图标、Handle
  • category: UI 组织结构(树状目录、分组)

2. 可扩展性

# 轻松添加三级目录
"category": "Transform/Filter/Advanced"

# 前端自动支持
categoryPath.split('/')  ['Transform', 'Filter', 'Advanced']

3. 语义化

// 修改前(混乱)
if (node.type === 'csv') { ... }          // 前端类型
if (meta.category === 'Loader') { ... }  // 既是功能又是目录

// 修改后(清晰)
if (meta.function === 'Loader') { ... }   // 节点功能
if (meta.category.startsWith('Loader/')) { ... }  // 目录位置

4. 向后兼容

  • 前端 node.type 统一为 'universal',不再依赖具体类型
  • 所有节点使用相同的 UniversalNode 组件
  • 元数据驱动,易于扩展

🚀 后续增强建议

1. 动态树状目录

// 支持折叠二级目录
Loader (2)
   CSV (1)
    - CSV 数据加载器
   Trace (1)
    - UTrace 文件加载器

Transform (4)
   Filter (2)
    - 行过滤器
    - 时间范围过滤
   Select (1)
   Aggregate (1)

2. 功能徽章

// 节点卡片显示功能标签
┌─ CSV 数据加载器 ────────┐
 [Loader] [CSV] [Preview] 
 file_path: ...           
└─────────────────────────┘

3. 搜索增强

// 支持按 function 和 category 过滤
搜索:Loader       显示所有 Loader 功能节点
搜索:Filter       显示所有 Filter 目录节点
搜索:Loader/CSV   显示特定路径节点

完成时间2026-01-07
版本Function & Category Separation v1.0
状态 重构完成,功能与目录完全分离