目标(已确认)
「全部场次 + 点场次」与「选集 + 点同一场次」读写同一
storyboard_graph_state键:(project_id, episode_id=剧集节点 id, chapter_id=场次节点 id)。不需要兜底、不考虑历史兼容:可删除/收紧依赖
__all__/STORYBOARD_SCOPE_ALL的故事板前端路径;遗留库中仅__all__键下的 graph 行可视为可丢弃(不迁移)。以项目最优解落地:单一解析函数 + 全链路同一
episodeId,避免双轨。
核心结论
场次节点已有
chapter_id(剧集 id):list_nodes_by_typescene 分支LEFT JOIN edges ... contains→source_node_id as chapter_id。当前缺口:
StoryboardTab中sceneGraphEpisodeId在未选集时落到STORYBOARD_SCOPE_ALL,与「选集」下的真实episode_id分裂。
推荐实现(最优、改动面收敛)
1. 单一「故事板剧集作用域」解析(前端)
新增小工具(例如 ui/src/domain/storyboardEpisodeScope.ts)或内联 useMemo:
// 语义:显式选集 > App 传入的 chapter > 当前场次所属剧集
resolvedStoryboardEpisodeId =
(activeChapterId?.trim() ||
chapter?.id?.trim() ||
activeScene?.chapter_id?.trim() ||
"");StoryboardTab:sceneGraphEpisodeId改为上述resolvedStoryboardEpisodeId(不再|| STORYBOARD_SCOPE_ALL)。无场次:不加载 graph(现有逻辑已处理)。
有场次但
resolved为空:视为数据损坏/孤儿场次 — 不兜底:toast 固定文案(如「场次未关联剧集,无法加载故事板画布」),loadStoryboardGraph/save不调用;与「不要兼容」一致。
2. 与 graph 键一致的所有调用点(前端)
以下必须使用同一 resolvedStoryboardEpisodeId(从 StoryboardTab 下传或从 App 用相同公式计算,避免分叉):
位置 | 调整 |
|---|---|
|
|
|
|
|
|
| |
与垫图/场次上下文一致:传 | |
|
3. [sceneStoryboardChildNodes.ts](d:/00Dev/bgxiong-ai-story/ui/src/domain/sceneStoryboardChildNodes.ts)
删除
episodeId默认值STORYBOARD_SCOPE_ALL;改为调用方必传 已解析 的剧集 id。若
episodeId为空:直接返回[]或throw(二选一;推荐返回[]+ 上层 toast,避免未处理异常)。
4. 常量与类型清理
[storyboardGraph.ts](d:/00Dev/bgxiong-ai-story/ui/src/domain/storyboardGraph.ts):STORYBOARD_SCOPE_ALL若仅故事板 graph 使用,可删除并全量替换;若 片段画布等仍用__all__(见[types.ts](d:/00Dev/bgxiong-ai-story/ui/src/components/segment-canvas/types.ts)、AppClips),则保留常量但 故事板模块禁止再引用。[useStoryboardSceneGraph](d:/00Dev/bgxiong-ai-story/ui/src/components/storyboard/hooks/useStoryboardSceneGraph.ts):移除仅用于__all__回退的 import/拼接。
5. 后端(可选收紧,与「无兜底」对齐)
[storyboard_graph::normalize_scope_id](d:/00Dev/bgxiong-ai-story/src-tauri/src/storyboard_graph.rs):对load_storyboard_graph_state/upsert_storyboard_graph_state的入口,若trim为空则Err而非写入/读取__all__(需同步改[commands/storyboard.rs](d:/00Dev/bgxiong-ai-story/src-tauri/src/commands/storyboard.rs)三处normalize_scope_id)。风险:仅当保证前端永远传非空剧集 id 时安全;否则 Tauri 命令会报错(符合「严格」)。
文档注释:
chapter_scene_board_pipeline.rs1388 行附近 更新为「episode_scope_id必须为剧集节点 id,与前端一致」。
6. 数据(不兼容策略)
不编写迁移合并
__all__→ 真实剧集:旧行自然废弃。可选(本地开发):手动删
storyboard_graph_state中episode_id='__all__'的行;不作为代码交付必选项。
验证
选集 A → 选场次 S → 改画布 → 切「全部场次」→ 再点 S:布局与节点与选集时一致。
从未选集路径进入:仅点 S(
activeScene.chapter_id有值):同上。清除场次或孤儿场次:
resolved为空时行为符合预期(不静默读__all__)。
实施任务清单
引入
resolvedStoryboardEpisodeId解析并接入StoryboardTab+ 所有子 hook / API 参数。更新
App.refreshStoryboardShots与sceneStoryboardChildNodes(去默认__all__)。删除故事板路径上对
STORYBOARD_SCOPE_ALL的依赖;保留或收缩常量定义。(可选)后端
normalize_scope_id对空串改为Err+cargo check。手动回归两条路径 +
npm run build/ 相关ReadLints。
