498 lines
12 KiB
Markdown
498 lines
12 KiB
Markdown
|
|
# TraceStudio 功能更新文档
|
|||
|
|
|
|||
|
|
## 版本记录
|
|||
|
|
- **v0.2.0** - 2026-01-07
|
|||
|
|
- ✨ 多层级树状分类系统
|
|||
|
|
- 🖱️ 增强的右键菜单系统
|
|||
|
|
- 👁️ 节点预览数据持久化
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 更新内容
|
|||
|
|
|
|||
|
|
### 1. ✨ 多层级树状分类系统
|
|||
|
|
|
|||
|
|
#### 功能描述
|
|||
|
|
节点面板支持类似文件树的多层级分类结构,方便组织大量算子。
|
|||
|
|
|
|||
|
|
**核心特性**:
|
|||
|
|
- 📁 支持最多 **3 级树状目录**
|
|||
|
|
- 🏷️ 超出 3 级的分类作为 `subCategory` 标签显示
|
|||
|
|
- 🔄 递归展开/折叠,自动统计子节点数量
|
|||
|
|
- 🔍 搜索时自动展开匹配节点
|
|||
|
|
|
|||
|
|
**示例结构**:
|
|||
|
|
```
|
|||
|
|
📥 Loader (2)
|
|||
|
|
▸ CSV (1)
|
|||
|
|
▸ Local (1)
|
|||
|
|
📥 CSV 数据加载器
|
|||
|
|
📁 File (超出3级)
|
|||
|
|
▸ Trace (1)
|
|||
|
|
📥 UTrace 文件加载器
|
|||
|
|
|
|||
|
|
⚙️ Transform (4)
|
|||
|
|
▸ Filter (2)
|
|||
|
|
⚙️ 行过滤器
|
|||
|
|
⚙️ 时间范围过滤
|
|||
|
|
▸ Select (1)
|
|||
|
|
⚙️ 列选择器
|
|||
|
|
▸ Aggregate (1)
|
|||
|
|
⚙️ 数据聚合
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**后端配置示例** (`server/main.py`):
|
|||
|
|
```python
|
|||
|
|
"CSVLoader": {
|
|||
|
|
"function": "Loader",
|
|||
|
|
"category": "Loader/CSV/Local/File", # 4级分类
|
|||
|
|
# 显示效果:
|
|||
|
|
# - Loader > CSV > Local 作为树结构
|
|||
|
|
# - "File" 作为 subCategory 标签
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
"FilterRows": {
|
|||
|
|
"function": "Transform",
|
|||
|
|
"category": "Transform/Filter", # 2级分类,正常显示
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**技术实现**:
|
|||
|
|
- 文件:[web/src/components/NodePalette.tsx](../web/src/components/NodePalette.tsx)
|
|||
|
|
- 使用 `Map<string, TreeNode>` 构建高效树索引
|
|||
|
|
- 递归渲染函数 `renderTreeNode()` 处理嵌套结构
|
|||
|
|
- 自动过滤空分类节点
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. 🖱️ 增强的右键菜单系统
|
|||
|
|
|
|||
|
|
#### 功能描述
|
|||
|
|
全面的右键菜单支持,涵盖画布、节点、连线的所有常用操作。
|
|||
|
|
|
|||
|
|
**菜单类型**:
|
|||
|
|
|
|||
|
|
| 右键对象 | 菜单项 | 功能描述 | 快捷键 |
|
|||
|
|
|---------|-------|---------|--------|
|
|||
|
|
| **画布** | 💾 保存工作流 | 保存到 localStorage | - |
|
|||
|
|
| **画布** | 📥 导入工作流 | 从 JSON 文件导入 | - |
|
|||
|
|
| **画布** | 📤 导出工作流 | 导出为 JSON 文件 | - |
|
|||
|
|
| **画布** | 🗑️ 清空画布 | 清空所有节点和连线 | - |
|
|||
|
|
| **节点** | 📋 复制节点 | 复制节点到偏移位置 | - |
|
|||
|
|
| **节点** | ✂️ 断开所有连线 | 删除该节点的所有连线 | - |
|
|||
|
|
| **节点** | 🗑️ 删除节点 | 删除节点及其连线 | Delete |
|
|||
|
|
| **连线** | ✂️ 删除此连线 | 删除当前选中的连线 | - |
|
|||
|
|
| **连线** | 🗑️ 删除此接口所有连线 | 删除同一 target handle 的所有连线 | - |
|
|||
|
|
|
|||
|
|
**交互优化**:
|
|||
|
|
- ✅ 鼠标左键拖拽时自动关闭右键菜单
|
|||
|
|
- ✅ 点击画布自动关闭菜单
|
|||
|
|
- ✅ 危险操作(清空画布)有确认提示
|
|||
|
|
|
|||
|
|
**技术实现**:
|
|||
|
|
- 文件:[web/src/components/Workspace.tsx](../web/src/components/Workspace.tsx)
|
|||
|
|
- React Flow 事件:`onEdgeContextMenu`, `onNodeContextMenu`, `onPaneContextMenu`
|
|||
|
|
- 监听拖拽开始和鼠标按下事件自动关闭菜单
|
|||
|
|
|
|||
|
|
**使用示例**:
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 删除同一接口的所有连线(用于清理多输入节点)
|
|||
|
|
case 'deleteAll': {
|
|||
|
|
const edge = edges.find(e => e.id === contextMenu.edgeId)
|
|||
|
|
if (edge) {
|
|||
|
|
setEdges((eds) => eds.filter((e) =>
|
|||
|
|
!(e.target === edge.target && e.targetHandle === edge.targetHandle)
|
|||
|
|
))
|
|||
|
|
}
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. 👁️ 节点预览数据持久化
|
|||
|
|
|
|||
|
|
#### 功能描述
|
|||
|
|
节点执行或预览后,预览数据会持久化显示在节点下方,无需重复加载。
|
|||
|
|
|
|||
|
|
**支持的预览类型**:
|
|||
|
|
|
|||
|
|
1. **📊 表格预览**(CSV 数据)
|
|||
|
|
- 显示前 5 行数据
|
|||
|
|
- 显示前 4 列(超出部分显示省略)
|
|||
|
|
- 自动统计总行数
|
|||
|
|
- 表头加粗显示
|
|||
|
|
|
|||
|
|
2. **🖼️ 图表预览**(图像)
|
|||
|
|
- 显示图表图像
|
|||
|
|
- 自适应缩放(最大高度 200px)
|
|||
|
|
- 支持常见图片格式
|
|||
|
|
|
|||
|
|
**数据结构**:
|
|||
|
|
```typescript
|
|||
|
|
interface NodeData {
|
|||
|
|
preview?: {
|
|||
|
|
type: 'table' | 'image'
|
|||
|
|
data: any // 表格数组或图片URL
|
|||
|
|
columns?: string[] // 表格列名
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**表格预览示例**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"preview": {
|
|||
|
|
"type": "table",
|
|||
|
|
"columns": ["ID", "Name", "Value", "Timestamp"],
|
|||
|
|
"data": [
|
|||
|
|
{ "ID": 1, "Name": "Item A", "Value": 100, "Timestamp": "2026-01-07" },
|
|||
|
|
{ "ID": 2, "Name": "Item B", "Value": 200, "Timestamp": "2026-01-07" }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**图表预览示例**:
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"preview": {
|
|||
|
|
"type": "image",
|
|||
|
|
"data": "data:image/png;base64,iVBORw0KG..."
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**技术实现**:
|
|||
|
|
- 文件:[web/src/components/nodes/UniversalNode.tsx](../web/src/components/nodes/UniversalNode.tsx)
|
|||
|
|
- 预览区域使用滚动容器(最大高度 300px)
|
|||
|
|
- 表格使用优化的 HTML table 结构
|
|||
|
|
- 图片使用 `object-fit: contain` 自适应显示
|
|||
|
|
|
|||
|
|
**样式特点**:
|
|||
|
|
- 🎨 深色主题适配
|
|||
|
|
- 📏 固定宽度防止节点过宽
|
|||
|
|
- 🔤 文本超出自动省略
|
|||
|
|
- 📊 数据行数统计标签
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🛠️ 技术细节
|
|||
|
|
|
|||
|
|
### 树状分类算法
|
|||
|
|
|
|||
|
|
**核心函数**:
|
|||
|
|
```typescript
|
|||
|
|
const MAX_TREE_DEPTH = 3 // 最大树层级
|
|||
|
|
|
|||
|
|
function buildTree(): TreeNode {
|
|||
|
|
const root: TreeNode = {
|
|||
|
|
name: 'root',
|
|||
|
|
items: [],
|
|||
|
|
children: new Map(),
|
|||
|
|
expanded: true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (const [nodeType, meta] of Object.entries(plugins)) {
|
|||
|
|
const categoryPath = meta.category || 'Other'
|
|||
|
|
const parts = categoryPath.split('/')
|
|||
|
|
|
|||
|
|
// 限制树深度
|
|||
|
|
const treeParts = parts.slice(0, MAX_TREE_DEPTH)
|
|||
|
|
const subCategory = parts.length > MAX_TREE_DEPTH
|
|||
|
|
? parts.slice(MAX_TREE_DEPTH).join('/')
|
|||
|
|
: undefined
|
|||
|
|
|
|||
|
|
// 逐级构建树
|
|||
|
|
let currentNode = root
|
|||
|
|
for (let i = 0; i < treeParts.length; i++) {
|
|||
|
|
const part = treeParts[i]
|
|||
|
|
|
|||
|
|
if (i === treeParts.length - 1) {
|
|||
|
|
// 叶子节点
|
|||
|
|
currentNode.items.push({ type: nodeType, meta, subCategory })
|
|||
|
|
} else {
|
|||
|
|
// 中间节点
|
|||
|
|
if (!currentNode.children.has(part)) {
|
|||
|
|
currentNode.children.set(part, {
|
|||
|
|
name: part,
|
|||
|
|
items: [],
|
|||
|
|
children: new Map(),
|
|||
|
|
expanded: expandedPaths.has(buildPath(treeParts.slice(0, i + 1)))
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
currentNode = currentNode.children.get(part)!
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return root
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**递归渲染**:
|
|||
|
|
```typescript
|
|||
|
|
function renderTreeNode(node: TreeNode, depth: number, path: string[]): React.ReactNode {
|
|||
|
|
const currentPath = buildPath(path)
|
|||
|
|
const isExpanded = expandedPaths.has(currentPath) || search !== ''
|
|||
|
|
|
|||
|
|
// 过滤 + 统计
|
|||
|
|
const filteredItems = node.items.filter(/* ... */)
|
|||
|
|
const totalCount = countNodes(node)
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div style={{ marginLeft: depth > 0 ? 12 : 0 }}>
|
|||
|
|
{/* 目录标题 */}
|
|||
|
|
{/* 叶子节点 */}
|
|||
|
|
{/* 递归渲染子目录 */}
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 右键菜单状态管理
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const [contextMenu, setContextMenu] = useState<{
|
|||
|
|
x: number
|
|||
|
|
y: number
|
|||
|
|
type: 'pane' | 'node' | 'edge'
|
|||
|
|
nodeId?: string
|
|||
|
|
edgeId?: string
|
|||
|
|
} | null>(null)
|
|||
|
|
|
|||
|
|
// 自动关闭菜单的场景
|
|||
|
|
const onPaneMouseDown = useCallback(() => {
|
|||
|
|
if (contextMenu) setContextMenu(null)
|
|||
|
|
}, [contextMenu])
|
|||
|
|
|
|||
|
|
const onNodeDragStart = useCallback(() => {
|
|||
|
|
if (contextMenu) setContextMenu(null)
|
|||
|
|
}, [contextMenu])
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 预览数据渲染
|
|||
|
|
|
|||
|
|
**表格优化**:
|
|||
|
|
- 只显示前 5 行(避免节点过长)
|
|||
|
|
- 只显示前 4 列(避免节点过宽)
|
|||
|
|
- 单元格内容超出自动省略
|
|||
|
|
|
|||
|
|
**响应式设计**:
|
|||
|
|
```typescript
|
|||
|
|
const hasPreview = data.meta?.supports_preview && data.preview
|
|||
|
|
|
|||
|
|
{hasPreview && (
|
|||
|
|
<div style={{
|
|||
|
|
borderTop: '1px solid rgba(148,163,184,0.15)',
|
|||
|
|
background: 'rgba(0,0,0,0.2)',
|
|||
|
|
maxHeight: 300,
|
|||
|
|
overflow: 'auto'
|
|||
|
|
}}>
|
|||
|
|
{/* 预览内容 */}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 使用指南
|
|||
|
|
|
|||
|
|
### 配置多层级分类
|
|||
|
|
|
|||
|
|
**步骤 1:修改后端配置** (`server/main.py`)
|
|||
|
|
```python
|
|||
|
|
"MyNode": {
|
|||
|
|
"function": "Transform", # 节点功能
|
|||
|
|
"category": "Transform/Filter/Advanced/Custom", # 4级分类
|
|||
|
|
# 显示效果:
|
|||
|
|
# Transform > Filter > Advanced (树结构)
|
|||
|
|
# + "Custom" 标签 (subCategory)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**步骤 2:启动服务**
|
|||
|
|
```bash
|
|||
|
|
# 后端
|
|||
|
|
conda activate tracestudio
|
|||
|
|
python -m uvicorn server.main:app --reload
|
|||
|
|
|
|||
|
|
# 前端
|
|||
|
|
cd web
|
|||
|
|
npm run dev
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**步骤 3:查看效果**
|
|||
|
|
- 节点面板会自动构建树状结构
|
|||
|
|
- 点击目录展开/折叠
|
|||
|
|
- 使用搜索框快速定位
|
|||
|
|
|
|||
|
|
### 使用右键菜单
|
|||
|
|
|
|||
|
|
**场景 1:批量删除连线**
|
|||
|
|
1. 创建多个节点连接到同一个 Aggregator
|
|||
|
|
2. 右键点击任意一条连线
|
|||
|
|
3. 选择 "🗑️ 删除此接口所有连线"
|
|||
|
|
4. 所有连接到该接口的连线都被删除
|
|||
|
|
|
|||
|
|
**场景 2:复制节点**
|
|||
|
|
1. 右键点击节点
|
|||
|
|
2. 选择 "📋 复制节点"
|
|||
|
|
3. 新节点会出现在偏移位置(+50px)
|
|||
|
|
|
|||
|
|
**场景 3:导出工作流**
|
|||
|
|
1. 右键点击画布空白处
|
|||
|
|
2. 选择 "📤 导出工作流"
|
|||
|
|
3. 保存为 JSON 文件
|
|||
|
|
|
|||
|
|
### 添加预览数据
|
|||
|
|
|
|||
|
|
**方法 1:执行节点后自动保存**
|
|||
|
|
```typescript
|
|||
|
|
// 执行完成后更新节点数据
|
|||
|
|
updateNodeData(nodeId, {
|
|||
|
|
preview: {
|
|||
|
|
type: 'table',
|
|||
|
|
columns: ['ID', 'Name', 'Value'],
|
|||
|
|
data: [
|
|||
|
|
{ ID: 1, Name: 'Item A', Value: 100 },
|
|||
|
|
{ ID: 2, Name: 'Item B', Value: 200 }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**方法 2:预览按钮触发**
|
|||
|
|
```typescript
|
|||
|
|
// Inspector 中添加预览按钮
|
|||
|
|
<button onClick={async () => {
|
|||
|
|
const result = await fetchPreview(nodeId)
|
|||
|
|
updateNodeData(nodeId, {
|
|||
|
|
preview: {
|
|||
|
|
type: 'table',
|
|||
|
|
columns: result.columns,
|
|||
|
|
data: result.data.slice(0, 100) // 限制数据量
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}}>
|
|||
|
|
👁️ 预览数据
|
|||
|
|
</button>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 测试建议
|
|||
|
|
|
|||
|
|
### 测试 1:多层级分类
|
|||
|
|
```python
|
|||
|
|
# server/main.py
|
|||
|
|
"TestNode": {
|
|||
|
|
"category": "A/B/C/D/E/F" # 6级分类
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
预期效果:显示 `A > B > C > TestNode + 标签 "📁 D/E/F"`
|
|||
|
|
|
|||
|
|
### 测试 2:右键菜单自动关闭
|
|||
|
|
1. 右键打开菜单
|
|||
|
|
2. 按住鼠标左键拖拽节点
|
|||
|
|
3. 菜单应该自动关闭
|
|||
|
|
|
|||
|
|
### 测试 3:预览数据持久化
|
|||
|
|
1. 给节点添加预览数据
|
|||
|
|
2. 拖动节点到其他位置
|
|||
|
|
3. 预览应该一直显示
|
|||
|
|
4. 刷新页面后预览应该恢复(如果已持久化到 localStorage)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔧 配置选项
|
|||
|
|
|
|||
|
|
### 树层级限制
|
|||
|
|
```typescript
|
|||
|
|
// web/src/components/NodePalette.tsx
|
|||
|
|
const MAX_TREE_DEPTH = 3 // 修改此值调整最大层级
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 预览数据限制
|
|||
|
|
```typescript
|
|||
|
|
// web/src/components/nodes/UniversalNode.tsx
|
|||
|
|
maxHeight: 300, // 预览区域最大高度
|
|||
|
|
data.slice(0, 5) // 显示前5行
|
|||
|
|
columns.slice(0, 4) // 显示前4列
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 右键菜单样式
|
|||
|
|
```typescript
|
|||
|
|
// web/src/components/Workspace.tsx
|
|||
|
|
background: 'rgba(10,22,40,0.95)',
|
|||
|
|
backdropFilter: 'blur(12px)',
|
|||
|
|
border: '1px solid rgba(59,130,246,0.2)',
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🐛 已知问题
|
|||
|
|
|
|||
|
|
1. **预览数据过大**
|
|||
|
|
- 问题:大数据集可能导致节点过大
|
|||
|
|
- 解决方案:限制预览行数(当前5行)
|
|||
|
|
|
|||
|
|
2. **树结构过深**
|
|||
|
|
- 问题:超过10级的分类可能显示混乱
|
|||
|
|
- 解决方案:设置合理的 MAX_TREE_DEPTH(推荐3-4级)
|
|||
|
|
|
|||
|
|
3. **右键菜单位置**
|
|||
|
|
- 问题:靠近边缘时菜单可能超出屏幕
|
|||
|
|
- 待优化:自动调整菜单位置
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 性能优化
|
|||
|
|
|
|||
|
|
1. **树结构构建**
|
|||
|
|
- 使用 `Map` 数据结构,查找复杂度 O(1)
|
|||
|
|
- 递归深度限制防止栈溢出
|
|||
|
|
|
|||
|
|
2. **预览数据渲染**
|
|||
|
|
- 虚拟滚动(未来优化)
|
|||
|
|
- 懒加载大图片
|
|||
|
|
|
|||
|
|
3. **菜单交互**
|
|||
|
|
- 使用 `useCallback` 防止不必要的重渲染
|
|||
|
|
- 事件委托优化性能
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📚 相关文件
|
|||
|
|
|
|||
|
|
| 文件 | 描述 | 主要改动 |
|
|||
|
|
|------|------|---------|
|
|||
|
|
| `web/src/components/NodePalette.tsx` | 节点面板 | ✅ 树状分类 |
|
|||
|
|
| `web/src/components/Workspace.tsx` | 画布组件 | ✅ 右键菜单 + 自动关闭 |
|
|||
|
|
| `web/src/components/nodes/UniversalNode.tsx` | 通用节点 | ✅ 预览数据显示 |
|
|||
|
|
| `server/main.py` | 后端API | ⚠️ 需配置 category |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚀 未来计划
|
|||
|
|
|
|||
|
|
- [ ] 拖拽排序节点面板分类
|
|||
|
|
- [ ] 预览数据支持更多格式(JSON、Markdown)
|
|||
|
|
- [ ] 右键菜单支持自定义操作
|
|||
|
|
- [ ] 预览数据的缓存管理
|
|||
|
|
- [ ] 节点分组(Group)功能
|
|||
|
|
- [ ] 快捷键支持(Ctrl+C/V 复制粘贴)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📞 反馈与支持
|
|||
|
|
|
|||
|
|
如有问题或建议,请通过以下方式反馈:
|
|||
|
|
- 提交 Issue
|
|||
|
|
- Pull Request
|
|||
|
|
- 联系开发团队
|
|||
|
|
|
|||
|
|
**最后更新**:2026-01-07
|
|||
|
|
**版本**:v0.2.0
|