Appearance
模块:Node System
唯一职责:实现 Vue Flow 自定义节点的渲染、编辑和 AI 生成交互。
边界
属于本模块:
- 通用节点框架(NodeShell):节点容器、标题栏、连接点、阴影状态
- 紧凑模式 / 编辑模式切换、悬浮面板、右键上下文菜单
- 状态机 composable(useNodeShellState):selected/hovered/connecting → 各区域可见性
- TextNode:TipTap 富文本编辑(Markdown 存储 + 渲染)+ AI 文本生成
- ImageNode:图片上传/预览 + AI 图片生成(支持参考图)
- VideoNode:视频播放 + AI 视频生成
- Toolbar(各节点的内容操作工具,通过 NodeShell toolbar slot 悬浮显示)
- NodeActionBar + Prompt Input(UDropdownMenu 模型选择、credits、
#paramsslot 透传、生成按钮,通过 NodeToolbar 悬浮显示。按钮样式共享NODE_TOOLBAR_BTNclass fromconstants/nodeUi.ts) - UpstreamMediaPreview(上游参考图/视频缩略图预览,NodeShell 自动渲染)
- 节点状态管理(idle → generating → done/error)
不属于本模块:
- 节点在画布上的位置管理(由 Vue Flow + flowStore 负责)
- 节点间的连线和数据流(由 Canvas Editor + State Management 负责)
- HTTP 请求发送(由 AI Service 模块负责)
- 全局连线状态(isConnecting)的 provide(由 useCanvasEditor 负责)
对外接口
节点通过 Vue Flow 的 node-types 注册(text / image / video),不对外暴露组件 API。IS_CONNECTING_KEY 由 useCanvasEditor provide,节点内部 inject 使用。
消费的接口
| 依赖模块 | 调用的方法/属性 | 用途 |
|---|---|---|
| flowStore | updateNodeData(id, data) | 更新节点数据(乐观 + WS 同步) |
| flowStore | removeNode(id) | 删除节点(通过右键菜单) |
| useGenerate | generate(nodeId, params) | 触发 AI 生成(HTTP POST + WS Push 接收结果) |
| useNodeModels | models | 获取当前节点类型可用模型列表 |
| modelsStore | getAiModels(modality) | 获取模型列表 |
| modelsStore | getDefaultModelKey(modality) | 获取默认模型 |
| useModelAbilities | aspectRatioOptions, resolutionOptions, durationOptions, resolveValues, getConstrainedOptions | 获取模型动态能力选项、rules 约束修正 |
| useCanvasEditor | provide(IS_CONNECTING_KEY, ref) | 注入全局连线状态 |
状态机
节点生成状态(NodeStatus)
NodeShell 显示模式
不变量
- 每个节点组件接收 Vue Flow 的
NodeProps,从data读取/写入状态 - 未选中时紧凑模式:仅标题 + 内容预览,内容区有透明蒙版阻止交互
- 选中时编辑模式:上方悬浮 Toolbar(通过
v-if+ NodeToolbar:is-visible="true"),下方悬浮 Prompt + ActionBar - Handle 在 hover / selected / connecting 三种情况显示,其余隐藏(opacity + pointer-events)
- 右键上下文菜单通过 UContextMenu 包裹节点卡片实现
generating状态下生成按钮被禁用,内容区叠加半透明遮罩 + loading 指示器阻止交互,浮动工具栏隐藏- 生成失败不丢失用户已输入的 prompt 和现有内容
- 图片上传限制:类型 JPG/PNG/WebP、大小 ≤ 30MB
- 视频上传限制:类型 MP4/WebM、大小 ≤ 300MB
- ImageNode/VideoNode 的生成参数(宽高比、分辨率、时长等)由模型 abilities 动态驱动,不硬编码
- 模型切换或初次加载时自动修正参数到合法值(
watch+{ immediate: true }) - 生成时校验参考图片数量不超过模型
reference_image限制 - 下游节点(有 incoming edge 的 Image/Video 节点)不渲染上传区域和工具栏上传按钮,只能通过 AI 生成获得内容
错误场景
| 场景 | 模块行为 | 调用方职责 |
|---|---|---|
| 图片上传文件类型不合法(非 JPG/PNG/WebP) | 拒绝文件,显示 toast 错误提示 | 无需处理(节点内部兜底) |
| 图片上传文件超过 30MB / 视频超过 300MB | 拒绝文件,显示 toast 错误提示 | 无需处理 |
| 下游节点尝试上传 | 上传区域和工具栏上传按钮不渲染,无法触发上传 | 无需处理 |
| AI 生成请求网络超时或后端返回错误 | 节点状态变为 error,保留用户已输入的 prompt 和现有内容 | 无需处理 |
| AI 生成请求被 abort(组件卸载或用户取消) | 请求取消,节点状态恢复为之前状态 | 无需处理 |
| Object URL 泄漏(组件卸载时未释放) | 组件 onUnmounted 中释放所有 Object URL 和 abort 进行中的请求 | 无需处理 |
| 模型列表未加载完成时用户点击生成 | 使用当前已选模型(或默认模型),不阻塞生成 | 无需处理 |
组件结构
实现位置
| 角色 | 文件路径 |
|---|---|
| 通用框架 | app/components/nodes/NodeShell.vue |
| 状态机 composable | app/composables/useNodeShellState.ts |
| 连接点 | app/components/nodes/NodeHandle.vue |
| 标题栏 | app/components/nodes/NodeHeader.vue |
| 生成控制栏 | app/components/nodes/NodeActionBar.vue |
| Prompt 输入 | app/components/nodes/NodePromptInput.vue |
| 上游媒体预览 | app/components/nodes/UpstreamMediaPreview.vue |
| 上游数据 composable | app/composables/useUpstreamData.ts |
| 模型能力 composable | app/composables/useModelAbilities.ts |
| 文本节点 | app/components/nodes/TextNode.vue |
| 图片节点 | app/components/nodes/ImageNode.vue |
| 视频节点 | app/components/nodes/VideoNode.vue |
| 编辑器 composable | app/composables/useTextEditor.ts |
| 测试 | test/nuxt/nodeShell.test.ts, useNodeShellState.test.ts, textNode.test.ts, videoNode.test.ts |