12 KiB
12 KiB
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):
"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
- 使用
Map<string, TreeNode>构建高效树索引 - 递归渲染函数
renderTreeNode()处理嵌套结构 - 自动过滤空分类节点
2. 🖱️ 增强的右键菜单系统
功能描述
全面的右键菜单支持,涵盖画布、节点、连线的所有常用操作。
菜单类型:
| 右键对象 | 菜单项 | 功能描述 | 快捷键 |
|---|---|---|---|
| 画布 | 💾 保存工作流 | 保存到 localStorage | - |
| 画布 | 📥 导入工作流 | 从 JSON 文件导入 | - |
| 画布 | 📤 导出工作流 | 导出为 JSON 文件 | - |
| 画布 | 🗑️ 清空画布 | 清空所有节点和连线 | - |
| 节点 | 📋 复制节点 | 复制节点到偏移位置 | - |
| 节点 | ✂️ 断开所有连线 | 删除该节点的所有连线 | - |
| 节点 | 🗑️ 删除节点 | 删除节点及其连线 | Delete |
| 连线 | ✂️ 删除此连线 | 删除当前选中的连线 | - |
| 连线 | 🗑️ 删除此接口所有连线 | 删除同一 target handle 的所有连线 | - |
交互优化:
- ✅ 鼠标左键拖拽时自动关闭右键菜单
- ✅ 点击画布自动关闭菜单
- ✅ 危险操作(清空画布)有确认提示
技术实现:
- 文件:web/src/components/Workspace.tsx
- React Flow 事件:
onEdgeContextMenu,onNodeContextMenu,onPaneContextMenu - 监听拖拽开始和鼠标按下事件自动关闭菜单
使用示例:
// 删除同一接口的所有连线(用于清理多输入节点)
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. 👁️ 节点预览数据持久化
功能描述
节点执行或预览后,预览数据会持久化显示在节点下方,无需重复加载。
支持的预览类型:
-
📊 表格预览(CSV 数据)
- 显示前 5 行数据
- 显示前 4 列(超出部分显示省略)
- 自动统计总行数
- 表头加粗显示
-
🖼️ 图表预览(图像)
- 显示图表图像
- 自适应缩放(最大高度 200px)
- 支持常见图片格式
数据结构:
interface NodeData {
preview?: {
type: 'table' | 'image'
data: any // 表格数组或图片URL
columns?: string[] // 表格列名
}
}
表格预览示例:
{
"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" }
]
}
}
图表预览示例:
{
"preview": {
"type": "image",
"data": "data:image/png;base64,iVBORw0KG..."
}
}
技术实现:
- 文件:web/src/components/nodes/UniversalNode.tsx
- 预览区域使用滚动容器(最大高度 300px)
- 表格使用优化的 HTML table 结构
- 图片使用
object-fit: contain自适应显示
样式特点:
- 🎨 深色主题适配
- 📏 固定宽度防止节点过宽
- 🔤 文本超出自动省略
- 📊 数据行数统计标签
🛠️ 技术细节
树状分类算法
核心函数:
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
}
递归渲染:
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>
)
}
右键菜单状态管理
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 列(避免节点过宽)
- 单元格内容超出自动省略
响应式设计:
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)
"MyNode": {
"function": "Transform", # 节点功能
"category": "Transform/Filter/Advanced/Custom", # 4级分类
# 显示效果:
# Transform > Filter > Advanced (树结构)
# + "Custom" 标签 (subCategory)
}
步骤 2:启动服务
# 后端
conda activate tracestudio
python -m uvicorn server.main:app --reload
# 前端
cd web
npm run dev
步骤 3:查看效果
- 节点面板会自动构建树状结构
- 点击目录展开/折叠
- 使用搜索框快速定位
使用右键菜单
场景 1:批量删除连线
- 创建多个节点连接到同一个 Aggregator
- 右键点击任意一条连线
- 选择 "🗑️ 删除此接口所有连线"
- 所有连接到该接口的连线都被删除
场景 2:复制节点
- 右键点击节点
- 选择 "📋 复制节点"
- 新节点会出现在偏移位置(+50px)
场景 3:导出工作流
- 右键点击画布空白处
- 选择 "📤 导出工作流"
- 保存为 JSON 文件
添加预览数据
方法 1:执行节点后自动保存
// 执行完成后更新节点数据
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:预览按钮触发
// 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:多层级分类
# server/main.py
"TestNode": {
"category": "A/B/C/D/E/F" # 6级分类
}
预期效果:显示 A > B > C > TestNode + 标签 "📁 D/E/F"
测试 2:右键菜单自动关闭
- 右键打开菜单
- 按住鼠标左键拖拽节点
- 菜单应该自动关闭
测试 3:预览数据持久化
- 给节点添加预览数据
- 拖动节点到其他位置
- 预览应该一直显示
- 刷新页面后预览应该恢复(如果已持久化到 localStorage)
🔧 配置选项
树层级限制
// web/src/components/NodePalette.tsx
const MAX_TREE_DEPTH = 3 // 修改此值调整最大层级
预览数据限制
// web/src/components/nodes/UniversalNode.tsx
maxHeight: 300, // 预览区域最大高度
data.slice(0, 5) // 显示前5行
columns.slice(0, 4) // 显示前4列
右键菜单样式
// web/src/components/Workspace.tsx
background: 'rgba(10,22,40,0.95)',
backdropFilter: 'blur(12px)',
border: '1px solid rgba(59,130,246,0.2)',
🐛 已知问题
-
预览数据过大
- 问题:大数据集可能导致节点过大
- 解决方案:限制预览行数(当前5行)
-
树结构过深
- 问题:超过10级的分类可能显示混乱
- 解决方案:设置合理的 MAX_TREE_DEPTH(推荐3-4级)
-
右键菜单位置
- 问题:靠近边缘时菜单可能超出屏幕
- 待优化:自动调整菜单位置
📊 性能优化
-
树结构构建
- 使用
Map数据结构,查找复杂度 O(1) - 递归深度限制防止栈溢出
- 使用
-
预览数据渲染
- 虚拟滚动(未来优化)
- 懒加载大图片
-
菜单交互
- 使用
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