Skip to content

Feature: 通用节点框架(NodeShell)

一句话目标

为三种节点类型提供统一的容器,通过选中/未选中两种模式切换紧凑展示与完整编辑体验。

行为约束

约束 1:未选中态 — 紧凑展示

前置条件: 节点未被选中且非 hover 状态 行为: 节点只渲染 NodeHeader(标题 + 类型图标)和内容区(default slot)。Toolbar slot、Prompt slot、NodeActionBar 均不渲染。Handle 隐藏。 后置条件: 节点呈紧凑卡片形态

约束 2:选中态 — 完整编辑

前置条件: 节点被单选(仅一个节点选中)且未在拖拽中 行为: 上方悬浮渲染 toolbar slot(内容操作工具),下方依次渲染 prompt slot 和 NodeActionBar。Handle 显示。内容区可交互(编辑文本、查看图片等)。多选、拖拽中、或框选进行中时,浮动面板(toolbar/prompt/ActionBar)不渲染,仅 Handle 显示。 后置条件: 节点进入编辑模式,用户可输入 prompt 并触发生成

约束 3:连接点显示

前置条件: 节点在画布中 行为: Handle 在以下情况显示:节点被选中、鼠标 hover 节点、画布中正在进行连线拖拽。其余时候隐藏。 后置条件: 左侧 target handle、右侧 source handle 可见

约束 4:点击与拖拽分离

前置条件: 节点未选中 行为: 单击 → 选中节点;按住拖拽 → 移动节点,不改变选中状态 后置条件: 两种交互互不干扰

约束 5:标题编辑

前置条件: 节点被选中 行为: 标题区可编辑,变更通过事件回写 后置条件: 标题持久化到节点数据

约束 6:悬浮面板定位

前置条件: 节点被选中 行为: Toolbar 悬浮于节点卡片上方,Prompt + ActionBar 悬浮于节点卡片下方,定位相对于节点边界 后置条件: 悬浮面板不遮挡节点内容,跟随节点移动/缩放

状态机

状态定义

状态HandleToolbarPrompt + ActionBar内容区
default隐藏隐藏隐藏只读展示
hovered显示隐藏隐藏只读展示
selected(单选)显示悬浮显示悬浮显示可交互
selected(多选)显示隐藏隐藏只读展示
selected + dragging显示隐藏隐藏只读展示
connect-ready显示隐藏隐藏只读展示

转换规则

事件备注
defaultmouseEnterhovered
defaultclickselected
defaultconnectStart (全局)connect-ready画布开始连线拖拽
hoveredmouseLeavedefault
hoveredclickselected
hoveredconnectStart (全局)connect-ready
selectedclickAway / Escapedefault点击画布空白或按 Esc
selectedconnectStart (全局)connect-ready连线期间临时退出编辑态
connect-readyconnectEnd (全局)default恢复到连线前的状态

约束

  • selectedhovered 不叠加 — 选中态已包含 hovered 的所有可见元素
  • connect-ready 是全局事件驱动,与节点自身交互无关
  • 多选(≥2 个节点选中)时,所有节点的浮动面板隐藏,仅 Handle 显示
  • 框选进行中(选框拖拽期间),浮动面板隐藏
  • 拖拽中浮动面板隐藏,拖拽结束后恢复

Acceptance Criteria

紧凑模式(default 状态)

  • [x] AC-01:未选中且非 hover 时,仅显示 NodeHeader 和内容区(default slot)
  • [x] AC-02:未选中时 toolbar slot 不显示(通过 v-if 条件渲染控制,NodeToolbar 使用 :is-visible="true" 延迟挂载以避免 viewport 订阅)
  • [x] AC-03:未选中时 prompt slot 和 NodeActionBar 不显示
  • [x] AC-04:未选中且非 hover 时 Handle 不可见不可交互(CSS opacity:0 + pointer-events:none

编辑模式(selected 状态)

  • [x] AC-05:单选时 toolbar slot 悬浮渲染于节点上方
  • [x] AC-06:单选时 prompt slot 和 NodeActionBar 悬浮渲染于节点下方
  • [x] AC-07:选中时 Handle 显示
  • [x] AC-08:选中时标题可编辑,变更回写到节点数据
  • [x] AC-08a:多选时(≥2 节点选中)浮动面板(toolbar/prompt/ActionBar)不渲染
  • [x] AC-08b:拖拽中浮动面板不渲染,拖拽结束后恢复

Handle 显示逻辑

  • [x] AC-09:hover 节点时 Handle 显示,mouseLeave 后隐藏
  • [x] AC-10:画布连线拖拽期间(connect-ready),所有节点 Handle 显示
  • [x] AC-11:连线结束后 Handle 恢复到之前的显示状态

交互

  • [x] AC-12:单击未选中节点 → 进入 selected 状态
  • [x] AC-13:按住拖拽 → 移动节点,不触发选中
  • [x] AC-14a:点击画布空白区 → 退出 selected,回到 default(Vue Flow 原生行为)
  • [ ] AC-14b:按 Escape → 退出 selected(未显式实现,依赖 Vue Flow 引擎行为,未验证)

视觉

  • [x] AC-15:悬浮面板跟随节点位置,不遮挡内容区

通用

  • [x] AC-16:TextNode、ImageNode、VideoNode 三种节点均通过 NodeShell 实现双模式

BDD Scenarios

gherkin
Feature: NodeShell 紧凑/编辑双模式

  Background:
    Given 画布中有一个文本节点 "T1"
    And "T1" 处于未选中状态

  # --- 紧凑模式 ---

  Scenario: 未选中节点展示紧凑模式
    Then "T1" 显示标题和内容区
    And "T1" 不显示 Toolbar
    And "T1" 不显示 Prompt 输入区和 ActionBar
    And "T1" 不显示 Handle

  # --- 选中进入编辑模式 ---

  Scenario: 单击节点进入编辑模式
    When 用户单击 "T1"
    Then "T1" 变为选中状态
    And "T1" 上方悬浮显示 Toolbar
    And "T1" 下方悬浮显示 Prompt 输入区和 ActionBar
    And "T1" 左右 Handle 显示
    And "T1" 标题可编辑

  Scenario: 选中态编辑标题并回写
    Given "T1" 处于选中状态
    When 用户将标题修改为 "新标题"
    Then 节点数据中 title 字段更新为 "新标题"

  # --- 退出编辑模式 ---

  Scenario: 点击空白区退出编辑模式
    Given "T1" 处于选中状态
    When 用户点击画布空白区域
    Then "T1" 回到未选中状态
    And Toolbar、Prompt、ActionBar 消失
    And Handle 隐藏

  Scenario: 按 Escape 退出编辑模式
    Given "T1" 处于选中状态
    When 用户按下 Escape 键
    Then "T1" 回到未选中状态

  # --- Handle 显示逻辑 ---

  Scenario: hover 显示 Handle
    When 用户鼠标悬停在 "T1"
    Then "T1" 左右 Handle 显示
    When 用户鼠标移出 "T1"
    Then "T1" Handle 隐藏

  Scenario: 连线拖拽期间所有节点显示 Handle
    Given 画布中还有一个图片节点 "I1" 且未选中
    When 用户从 "T1" 的 source handle 开始拖拽连线
    Then "T1""I1" 的 Handle 均显示
    When 用户释放连线(无论是否连接成功)
    Then Handle 恢复到拖拽前状态

  # --- 拖拽不触发选中 ---

  Scenario: 按住拖拽移动节点不触发选中
    When 用户按住 "T1" 并拖拽移动
    Then "T1" 跟随鼠标移动
    And "T1" 不进入选中状态
    And 不显示 Toolbar、Prompt、ActionBar

  # --- 多选隐藏浮动面板 ---

  Scenario: 多选时不显示浮动面板
    Given 画布中还有一个图片节点 "I1"
    When 用户框选 "T1""I1"
    Then "T1""I1" 均为选中状态
    And 两个节点均不显示 Toolbar、Prompt、ActionBar
    And 两个节点的 Handle 均显示

  Scenario: 拖拽选中节点时浮动面板隐藏
    Given "T1" 处于选中状态且显示浮动面板
    When 用户按住 "T1" 开始拖拽
    Then Toolbar、Prompt、ActionBar 隐藏
    When 用户释放鼠标
    Then Toolbar、Prompt、ActionBar 恢复显示

  # --- 跨节点类型 ---

  Scenario: 三种节点类型均支持双模式
    Given 画布中有文本节点、图片节点、视频节点各一个
    When 依次单击每个节点
    Then 每个节点均进入编辑模式,显示各自的 Toolbar 和 Prompt
    When 依次点击空白区
    Then 每个节点均回到紧凑模式

TDD 单元测试要点

NodeShell 组件测试(Vitest + Vue Test Utils)

#测试要点AC状态
T-01selected=false 时 toolbar slot 不渲染到 DOMAC-02
T-02selected=false 时 prompt slot 和 ActionBar 不渲染到 DOMAC-03
T-03selected=falsehovered=false 时 Handle 不渲染AC-04
T-04selected=true 时 toolbar slot 渲染AC-05
T-05selected=true 时 prompt slot 和 ActionBar 渲染AC-06
T-06selected=true 时 Handle 渲染AC-07
T-07hover 状态切换 Handle 显示/隐藏AC-09
T-08多选时浮动面板不渲染AC-08a
T-09拖拽时浮动面板不渲染AC-08b
T-10选中时 border 为 brand-500 色视觉
T-11ahover 时 border 为 50% 品牌色视觉
T-12a标题编辑触发 update:title 事件AC-08未覆盖(NodeHeader 被 mock)

状态计算逻辑测试(纯函数 / composable)

#测试要点AC状态
T-13connectStarted=true 时所有节点返回 handle 可见AC-10
T-14aconnectEnded 后 handle 可见性恢复到之前状态AC-11
T-15a给定 { selected, hovered, connectStarted, dragging, isMultiSelected } 输入,正确计算各区域可见性状态机
T-16a多选时 mode 为 compact,浮动面板隐藏AC-08a
T-17拖拽时浮动面板隐藏但 handle 保持显示AC-08b

composable useNodeShellState(已实现):

  • 输入:{ selected, isHovered, isConnecting, dragging, isMultiSelected }
  • 输出:{ mode, showHandles, showFloatingPanels }

集成测试(节点类型验证)

#测试要点AC状态
T-18TextNode 使用 NodeShell,selected 切换时 toolbar/prompt 显隐正确AC-16未覆盖
T-19ImageNode 使用 NodeShell,selected 切换时 toolbar/prompt 显隐正确AC-16未覆盖
T-20VideoNode 使用 NodeShell,selected 切换时 toolbar/prompt 显隐正确AC-16未覆盖

已知缺口

功能

  • Escape 退出编辑:AC-14b 依赖画布引擎原生行为,未验证 Escape 是否自动 deselect

测试

  • T-12a:标题编辑 update:title 事件未在 nodeShell.test.ts 中覆盖(NodeHeader 被 mock)
  • T-18~20:三种节点类型的集成测试未编写
  • useGenerate:无独立测试文件

AI 生成状态机

用户在任意节点点击生成后,系统调用 AI API 完成生成,结果自动填充节点,全程有明确的状态反馈。

状态转换规则

行为约束

约束 G1:状态转换 — 仅允许上图中的转换路径,其他转换为非法。

约束 G2:generating 期间重复提交 — 取消当前请求,立即发起新请求;旧请求回调被忽略。

约束 G3:generating 期间 loading — 内容区叠加半透明遮罩 + ChatShimmer 微光文字动画("AI 生成中..."),旧内容可见但不可交互。

约束 G4:生成结果填充 — API 成功返回后,结果写入节点数据(TextNode → content,ImageNode → imageUrl/results,VideoNode → videoUrl),状态变为 done

约束 G5:错误不丢失用户输入 — API 失败后状态变为 error,显示 toast 错误信息;prompt、现有内容、已上传文件均保持不变。

约束 G6:不兼容上游输入 — 全量透传给后端处理,前端不做兼容性过滤。详见 data-flow 约束 6。

约束 G7:组件卸载清理onUnmounted 中 abort 当前请求,防止悬挂回调。

Acceptance Criteria

  • [x] AC-G01:节点初始状态为 idle
  • [x] AC-G02:点击生成后立即进入 generating,按钮禁用
  • [x] AC-G03agenerating 期间,内容区显示 ChatShimmer 微光文字动画
  • [x] AC-G03bgenerating 期间,有已有内容时叠加遮罩层,旧内容可见但不可交互
  • [x] AC-G03cgenerating 期间,TextNode 编辑器不可交互(覆盖层阻断鼠标事件)
  • [x] AC-G03d:生成完成(done/error)后,loading 和遮罩立即消失,恢复交互
  • [x] AC-G03e:三种节点类型均支持上述 loading 行为
  • [x] AC-G04:API 成功后进入 done,结果写入节点,loading 消失
  • [x] AC-G05:API 失败后进入 error,不丢失用户输入,loading 消失
  • [x] AC-G06:连续调用生成时,abort 旧请求,发起新请求
  • [x] AC-G07:组件卸载时 abort 进行中的请求
  • [x] AC-G08:生成时注入上游节点数据到请求参数 → 详见 data-flow

BDD 场景

gherkin
Feature: AI 生成状态机

  Scenario: 正常生成文本
    Given 一个文本节点处于 idle 状态,已输入 prompt
    When 用户点击生成按钮
    Then 节点状态变为 generating,按钮禁用
    When AI 生成成功返回
    Then 节点状态变为 done,内容区显示生成结果

  Scenario: 生成失败保持用户输入
    Given 一个文本节点处于 idle 状态,已输入 prompt "测试失败"
    When 用户点击生成按钮,API 请求失败
    Then 节点状态变为 error
    And prompt 输入保持 "测试失败" 不变,显示错误 toast

  Scenario: 连续点击 abort 旧请求
    Given 一个文本节点处于 generating 状态(第一次请求)
    When 用户再次点击生成
    Then 第一次请求被 abort,发起新的生成请求

  Scenario: 组件卸载时 abort
    Given 一个文本节点处于 generating 状态
    When 用户导航离开画布页面
    Then 进行中的请求被 abort

  Scenario: 生成期间 loading 遮罩
    Given 一个图片节点已有图片,处于 idle 状态
    When 用户点击生成按钮
    Then 内容区叠加遮罩层,旧内容可见但不可交互
    When 生成完成
    Then 遮罩消失,内容区恢复交互

TDD 测试要点

生成流程逻辑(useGenerate composable):

  • [~] idle → generating 状态转换正确
  • [~] 成功后通过 WS Push 更新节点状态和数据
  • [~] 失败后 status=error 且 prompt 保持不变
  • [~] HTTP 请求失败时回滚节点状态

注:生成流程已改为 HTTP POST 触发 + WS Push 接收结果。当前 useGenerate composable 尚未有独立测试文件。

Out of Scope(生成)

  • 生成结果的版本历史
  • 多变体并行生成的进度展示
  • 流式生成(streaming)

Out of Scope

  • Toolbar 内部内容 — 各节点类型的 Toolbar 具体按钮和逻辑(文本格式化、图片工具条等),由各节点 spec 定义
  • 节点特有的内容区逻辑 — 富文本编辑、图片上传/预览、视频播放等,由各节点 spec 定义
  • 节点拖拽/位置管理 — 由画布引擎处理
  • 悬浮面板的具体像素尺寸和颜色 — 由 design-system 定义
  • 多选框选交互 — 框选本身由画布引擎处理,NodeShell 通过 IS_MULTI_SELECT_KEY 注入感知多选状态