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
|