Skip to content

模块:Node System

唯一职责:实现 Vue Flow 自定义节点的渲染、编辑和 AI 生成交互。

边界

属于本模块:

  • 通用节点框架(NodeShell):节点容器、标题栏、连接点、阴影状态
  • 紧凑模式 / 编辑模式切换、悬浮面板、右键上下文菜单
  • 状态机 composable(useNodeShellState):selected/hovered/connecting → 各区域可见性
  • TextNode:TipTap 富文本编辑 + AI 文本生成
  • ImageNode:图片上传/预览 + AI 图片生成(支持参考图)
  • VideoNode:视频播放 + AI 视频生成
  • Toolbar(各节点的内容操作工具,通过 NodeShell toolbar slot 悬浮显示)
  • NodeActionBar + Prompt Input(模型选择、变体、生成按钮,通过 NodeToolbar 悬浮显示)
  • 节点状态管理(idle → generating → done/error)

不属于本模块:

  • 节点在画布上的位置管理(由 Vue Flow + flowStore 负责)
  • 节点间的连线和数据流(由 Canvas Editor + State Management 负责)
  • HTTP 请求发送(由 AI Service 模块负责)
  • 全局连线状态(isConnecting)的 provide(由 useCanvasEditor 负责)

对外接口

Vue Flow 节点注册

typescript
// 画布页面通过 Vue Flow 的 node-types slot 注册
// types: 'text' | 'image' | 'video'
// 每种类型对应一个组件:TextNode.vue / ImageNode.vue / VideoNode.vue

useNodeShellState

typescript
// composables/useNodeShellState.ts
export const IS_CONNECTING_KEY: InjectionKey<Ref<boolean>>

export function useNodeShellState(input: {
  selected: Ref<boolean>
  isHovered: Ref<boolean>
  isConnecting: Ref<boolean>
}): {
  mode: ComputedRef<'compact' | 'edit'>
  showHandles: ComputedRef<boolean>
  showFloatingToolbar: ComputedRef<boolean>
  showFloatingBottom: ComputedRef<boolean>
}

消费的接口

依赖模块调用的方法/属性用途
flowStoreupdateNodeData(id, data)更新节点数据
flowStoreremoveNode(id)删除节点(通过右键菜单)
AiServicegenerateText(params)AI 文本生成
AiServicegenerateImage(params)AI 图片生成
AiServicegenerateVideo(params)AI 视频生成
modelsStoregetAiModels(modality)获取模型列表
modelsStoregetDefaultModelKey(modality)获取默认模型
useCanvasEditorprovide(IS_CONNECTING_KEY, ref)注入全局连线状态

状态机

节点生成状态(NodeStatus

idle ──[点击生成]-→ generating ──[成功]-→ done
                        └──[失败]-→ error
done ──[修改 prompt/模型]-→ idle
error ──[修改重试]-→ idle

NodeShell 显示模式

default (compact) ──[click]-→ selected (edit)
default ──[hover]-→ hovered ──[leave]-→ default
default ──[connectStart]-→ connect-ready ──[connectEnd]-→ default
selected ──[clickAway/Esc]-→ default

详见 NodeShell spec 状态机

不变量

  1. 每个节点组件接收 Vue Flow 的 NodeProps,从 data 读取/写入状态
  2. 未选中时紧凑模式:仅标题 + 内容预览,内容区有透明蒙版阻止交互
  3. 选中时编辑模式:上方悬浮 Toolbar(通过 NodeToolbar),下方悬浮 Prompt + ActionBar
  4. Handle 在 hover / selected / connecting 三种情况显示,其余隐藏(opacity + pointer-events)
  5. 右键上下文菜单通过 UContextMenu 包裹节点卡片实现
  6. generating 状态下生成按钮被禁用
  7. 生成失败不丢失用户已输入的 prompt 和现有内容
  8. 图片上传限制:类型 JPG/PNG/WebP、大小 ≤ 10MB
  9. 视频上传限制:类型 MP4/WebM、大小 ≤ 50MB

错误场景

场景模块行为调用方职责
图片上传文件类型不合法(非 JPG/PNG/WebP)拒绝文件,显示 toast 错误提示无需处理(节点内部兜底)
图片上传文件超过 10MB / 视频超过 50MB拒绝文件,显示 toast 错误提示无需处理
AI 生成请求网络超时或后端返回错误节点状态变为 error,保留用户已输入的 prompt 和现有内容无需处理
AI 生成请求被 abort(组件卸载或用户取消)请求取消,节点状态恢复为之前状态无需处理
Object URL 泄漏(组件卸载时未释放)组件 onUnmounted 中释放所有 Object URL 和 abort 进行中的请求无需处理
模型列表未加载完成时用户点击生成使用当前已选模型(或默认模型),不阻塞生成无需处理

组件结构

UContextMenu(右键菜单包裹)
└── NodeShell(通用容器 · sf-glass 卡片)
    ├── NodeHandle (target) — visible=showHandles
    ├── NodeToolbar (position=Top) — isVisible=showTopToolbar
    │   └── [slot:toolbar](TextEditorToolbar / 图片工具按钮 / 上传按钮)
    ├── NodeHeader(图标 + 标题,选中时可编辑)
    ├── [slot:default — 内容区](始终显示)
    │   ├── TextNode → TextEditorContent
    │   ├── ImageNode → 图片上传/预览 + ImageResultGrid
    │   └── VideoNode → 视频播放器
    │   └── [蒙版 v-if="!selected"](紧凑模式阻止交互)
    ├── NodeToolbar (position=Bottom) — isVisible=showFloatingBottom
    │   ├── [slot:prompt] → NodePromptInput
    │   └── NodeActionBar(模型选择 + 变体 + 生成按钮)
    └── NodeHandle (source) — visible=showHandles

实现位置

角色文件路径
通用框架apps/web/app/components/nodes/NodeShell.vue
状态机 composableapps/web/app/composables/useNodeShellState.ts
连接点apps/web/app/components/nodes/NodeHandle.vue
标题栏apps/web/app/components/nodes/NodeHeader.vue
生成控制栏apps/web/app/components/nodes/NodeActionBar.vue
Prompt 输入apps/web/app/components/nodes/NodePromptInput.vue
文本节点apps/web/app/components/nodes/TextNode.vue
图片节点apps/web/app/components/nodes/ImageNode.vue
视频节点apps/web/app/components/nodes/VideoNode.vue
编辑器 composableapps/web/app/composables/useTextEditor.ts
测试apps/web/test/nuxt/nodeShell.test.ts, useNodeShellState.test.ts, textNode.test.ts, videoNode.test.ts