* feat(editor): 编辑器实施 M0-M1 - 剪辑入口占位与插件 SDK v2 垂直切片 - detail.tsx 新增剪辑成片视图(editor tab,常驻),概览页插入剪辑步骤卡 - plugin-types.ts 新增 v2 类型(EditorSlotKind/EditorPluginPermission/PluginManifestV2),修复权限数组交叉归约 bug - editor-slot-registry.ts 插槽注册表(优先级排序、幂等注册、订阅机制) - plugin-registry.ts 注册器 v1/v2 分支校验,v1 路径零改动 - editor-shell.tsx 垂直切片插件:timeline-panel 插槽真实渲染,验证插件链路全通 - 测试:插槽注册表 5 用例 + 命令测试骨架,v1 插件回归 21 例零回归 * feat(editor): 命令协议黄金文件 - editor-commands 纯函数 reducer + 8 个黄金 op * feat(editor): 快照撤销历史 - editor-history 有界 200 层 undo/redo * feat(editor): 命令状态机 store - dispatch/手势预览/1.5s 防抖保存/串行保存链 * fix(editor): createEditorStore 返回 zustand hook 类型修复 typecheck * feat(editor): 手势 echo 最小件 - 时间线面板拖拽移动/裁剪 + store 接线与本地持久化 * docs(editor): M2 完成状态记录 * feat(editor): M3.1 时间线面板选中联动 - store 选中状态 + 片段高亮/点击取消 * feat(editor): M3.2 预览监视器 - preview-renderer 插槽 + 真实媒体帧/播放头/时间码 * feat(editor): M3.3 片段检查器 - inspector 插槽 + 属性编辑入队可撤销 * feat(editor): M3.4 asset-ingest 素材库插件(addClip 命令入轨) * feat(editor): M3.4 补充 - host 上下文(项目信息+资产)、editor.tsx 接线、asset-ingest 纯函数、死代码清理 * feat(editor): M3.5 subtitle-tools - SRT import/export + rebuild from node snapshot * feat(editor): M3.6 transcription - deterministic mock ASR to subtitle track * feat(editor): M3.7 export - render plan + ffmpeg.wasm fallback export * feat(editor): M3.8 - render all 8 editor slots in three-pane layout + ai-assistant placeholder * docs(editor): M3 完成状态 - 8 插件 + 全插槽接线,冒烟待确认 * feat(editor): 编辑器媒体与时间线打磨 - 资产来源分组筛选、时间线全宽布局、拖拽末片段修复、OSS 降级本地存储、Concat 风格控制条与检查器 * fix(plugins): 合并上游后修复插件分类筛选对 v2 清单的类型兼容 * feat(editor): 时间线工具态与轨道操作打磨 - 工具按钮选中仅图标变色、监视器 Concat 式控制条、removeTrack 黄金命令与轨道行头移除按钮、maskClosable 迁移 * fix(api): 资源上传超 MaxBytesReader 上限时报中文大小限制而非英文裸 400 * fix(editor): 监视器控制条 - 修复播放按钮 JSX 模板闭合花括号缺失导致的编译错误 * feat(api): 本地媒体导入分片化对齐 Concat - >50MB 视频不再失败 - 分片会话三接口(POST开始/PUT单片/complete合并)、前端超50MB自动切8MB分片并失败整传重试、默认配额放宽(日200MB→2GB、总2GB→20GB)、UploadResourceFile 豁免单文件限 * feat(editor): 轨道行头新增可见性/静音开关按钮 - 复用现有图标资源,按轨道类型显隐;命令层新增 setTrackFlag 黄金命令(flag 白名单 fail-closed)并补单测 * docs(progress): 记录轨道可见/静音开关改动与复测项 * docs(editor): M4 转写与渲染任务设计 - 复用 Task 表与状态机、独立创建 API、worker 领取类型过滤、whisper HTTP provider(OpenAI 兼容 /v1/audio) * fix(api): OSS 上传失败自动降级本地存储时 body 已被 Transport 关闭 - newOSSRequest 用 NopCloser 包装请求体,服务端提前返回(签名 403)时不再关闭调用方持有的文件,降级分支 Seek 重读生效;补回归测试 * docs(progress): 记录媒体导入根治(分片+OSS 降级修复)复测项 * feat(api): 视频播放副本转码 H.265→H.264 + variant=playback 服务与启动回填 - 上传视频后异步探测 codec,H.265/AV1 走 moov 快速探测并转码 H.264 播放副本;GET /api/user-data/...?variant=playback 就绪时服务副本(独立 ETag :pb),未就绪回退原件并撤销后缀防 304 黑屏;storeResource 触发、启动幂等回填存量;前端播放失败自动轮询副本就绪后刷新 * fix(editor): 轨道至少保留一条 - 不可移除轨道点击 X 时居中浮现原因提示(2.2s 自动消失, aria-live);修正可见性 toggle 传参 (visible===false 语义) ;轨道行内移除按钮改为常驻(不可移除时点击给提示) * feat(timeline): M4.1 字幕转写后台任务 - whisper.cpp 本地执行 - 新增 timeline_transcription 任务类型,worker 按类型分叉独立执行器(不走计费与模型渠道);输入 resourceId+language,读取用户资源后 ffmpeg 预处理 16k 单声道 wav,POST 本地 whisper.cpp /inference,结果写回 segments+srt+language;未配置 CANVAS_WHISPER_BASE_URL 等失败均落明确终态;feature 门控;进度分阶段;含解析/SRT/快速失败单元测试 * feat(media): 素材卡片时长与媒体面板打磨 - 导入媒体解析时长(前端 media-metadata + 后端 project_asset durationMs 透出)随卡片展示;媒体面板/监视器/时间线相关样式细节收尾 * docs(progress): 阶段 C 监视器播放复检清单 - H.265 播放副本/真实播放驱动/时长链路待浏览器确认 * feat(timeline): M4.1 转写任务创建 API - POST /api/timeline/transcriptions - service CreateTimelineTranscriptionTask:IsDraining 守护 + feature 门控 + 资源归属与可转写类型校验 + 排队计 active 限额(含任务级/项目级存储估算);local/whisper.cpp 标记,任务不经过模型路由与计费;createTaskWithinStorageQuota 复用与 ErrActiveTaskLimit 用户可读映射;测试覆盖排队成功(owner/type/provider 断言)、跨用户资源拒绝、非音视频拒绝 * feat(timeline): M4.3 字幕转写前端接线 - 真实任务替换 mock - 新增 services/api/timeline-tasks.ts(POST /timeline/transcriptions,复用 apiClient+request);EditorTranscription 改为 创建任务→waitForGenerationTask 轮询(25min)→结果 segments 映射 SrtEntry[]→rebuildSubtitleClips 写入字幕轨道;实时展示 stage/progress,失败/空识别/无字幕轨道分别提示;素材选择支持音频与视频(取 storageKey 中的 resource id 提交) * fix(media): 播放副本状态收敛与轮询终态 - 无ffmpeg/远端存储/编码不可处理落 none 避免重复探测;前端 none 停轮询回原生播放并加 processing 上限 * feat(timeline): M4.2 时间线渲染任务 - POST /api/timeline/renders 创建(buildRenderPlan 校验含可渲染媒体、配额入队、local/ffmpeg、60min 超时);worker 顶部按类型分叉 processTimelineRender;ffmpeg 合成产物写资源存储并经 ResultJSON 记 resourceId;render 计划/结果测试 PASS * fix(resource): 转码原子抢占与 OSS 请求体加固 - ClaimPlaybackTranscode 空/none→processing 单实例抢占避免重复转码,runPlaybackTranscode defer recover 落 failed;If-Range 与带 :pb 后缀的 serveETag 比较;无请求体的 OSS 请求不再包 NopCloser(nil)(防 Go 1.26 body 探测 nil 解引用) * style(editor): 修正字幕回写面板缩进 * docs(timeline): M4 设计文档按实现回写 - 单一 worker 池 + processClaimedTask 顶部按类型分叉(决策 3/4),端点统一为复数 /api/timeline/transcriptions 与 /api/timeline/renders(注册于 RegisterTaskRoutes),ResultJSON/feature gate/ffmpeg env 同步;Runbook M4 表格与状态行更新;auth.go API 前缀白名单补 timeline * fix(service): 修复上游 v1.2.5 公告配图删除保护自锁与测试夹具缺口 上游 #385 资源引用走查器对公告配图/草稿做了硬性引用检查,导致三个问题: - UpdateAnnouncement 在更新公告前对旧配图走查,必然命中当前公告自身引用而自锁, 公告替换配图功能实际不可用;DiscardAnnouncementImage 同理被草稿自身引用阻塞。 修复:ensureResourceHasNoBusinessReferences 支持忽略指定引用(当前公告/当前草稿), 其余引用仍按原语义拦截。 - admin DeleteAdminResources 将公告草稿引用视为阻塞,但删除事务会级联清理草稿与 资源绑定,service 层永不进入事务,回滚审计注入测试无法到达预期路径。修复:service 预检时过滤公告草稿引用,与 repository 级联语义对齐。 - project_settings_test.go 夹具未建 ResourceReferenceSnapshot 走查的 Task/Session/ Message/TaskLog/Result/Announcement/AnnouncementImageDraft 表,报 no such table。 修复:夹具 AutoMigrate 补齐。 后端全量 go test ./... 通过。 * feat(editor): 成片导出接入后端渲染任务(M4.2 前端接线)- timeline-tasks 增 createTimelineRenderTask;editor-export 主路径改 POST /timeline/renders 轮询取 resourceId 内嵌预览/下载,ffmpeg.wasm 降级保留 * feat(editor): M5 插件权限执行(fail-closed)- plugin-permission-check 新建(8 插槽最小权限表/判定/断言);SlotStack 渲染前过滤缺权限插槽改诊断条;editor-shell manifest 补 timeline.command/export.run;测试 10 用例含 editor-shell 真实回归 * feat(editor): M6.1 AI 命令 schema + M6.2 时间线摘要 - ai-command-schema(parse/dry-run 校验/12-op 提示词契约/版本化+golden);timeline-summary(确定性中文摘要/折叠上限);各 20/10 用例全绿 * docs(progress): M6 质检清单 - M6.1 schema/M6.2 摘要标记完成(含测试计数),M6.3 待模型源确认;清理 M5.1 重复条目 * feat(editor): AI 命令 schema 支持只读问答 - commands 空数组合法化(无法执行/纯提问时 reasoning 直接作答),新增 AI_EDITING_MAX_COMMANDS=8 上限防失控编辑,提示词同步 * feat(editor): AI 助手重构为右上悬浮对话面板(M6.3)+ 素材入库原子化收尾 - editor-ai-assistant.tsx: 占位面板替换为 OpenAI Canvas 风格右上悬浮对话面板; 面板内文本模型选择器(effective.textModels,选择即持久化并覆写请求模型), 消息流 user/assistant/error/preview 四类气泡 + 思考中占位,空态引导示例 chips 一键发送,底部输入条;顶部工具栏常驻 Sparkles 入口(aria-pressed 切换)。 修复上一版重复/截断函数声明(appendToEntry 双定义、settleAnswer 三声明等)。 宿主链路:buildAiEditingSystemPrompt(summarizeTimeline(project)) 喂上下文 → parseAiCommandPlan → validateAiCommandBatch dry-run(任一非法整批拒绝)→ ≤3 条 dispatch 直执行 / >3 条预览确认卡片;宿主拒绝时 failTurn 回填 FAILED_MARK 可重试。 - editor-asset-ingest/editor-preview-monitor/detail/editor.tsx: 配套接线与打磨 (编辑器顶栏 AI 入口、素材导入面板、预览监视、项目页顶栏整理)。 - backend: LinkProjectAsset 事务内原子创建资产 + 项目链接(OnConflict 幂等), 资产首次入库不再预落库,失败整体回滚,消除"有资产无链接"孤儿记录。 - docs: pending-test.mdx / runbook M6.3 状态更新(tsc + 测试 + 构建通过, 浏览器冒烟待真实文本渠道)。
159 lines
19 KiB
Markdown
159 lines
19 KiB
Markdown
# 编辑器实施 Runbook(分步执行计划)
|
||
|
||
> 配套文档:[编辑器预设插件化实施规格](../plans/editor-preset-plugin-implementation.md)(目标架构与接口)与 [ADR-0001 ~ 0007](../adr/)(决策)。本文件只解决一个问题:**按什么顺序、以什么粒度动手,让每一步都可验证、可回退、看得见进度**。
|
||
>
|
||
> 状态:2026-09-01 制定。M0–M3 已完成并提交;M4.1(转写后端:本地 whisper.cpp HTTP,
|
||
> `CANVAS_WHISPER_BASE_URL`)e9d1b8ad/bf26ebb1、M4.2(渲染后端:`POST /api/timeline/renders`)
|
||
> 3f16d13b、M4.3(转写前端回写,真实任务替换 mock)fd5e8139 均已提交。
|
||
|
||
## 0. 执行原则(为什么这样拆)
|
||
|
||
一次性大爆炸实施的三个失败模式:**看不见进度**(写一堆类型层后毫无可见产物)、**无法定位回归**(一个 diff 里混了协议+UI+后端)、**无法回退**(改动面太大只能整体回滚)。本计划按三条铁律对抗:
|
||
|
||
1. **原子步**:每一步是一个可独立验证的变更,只动一个目标(类型 / 纯函数 / 单个插件 / 单个接线点),不混多个目标。
|
||
2. **验证门**:每步结束必须过最轻的验证门(typecheck / 专项测试 / 构建,按步标注),通过才能进下一步。验证命令以当前 `package.json` 为准(§4 速查)。
|
||
3. **垂直切片优先**:先让「最小可演示」跑通(M0 空壳 + M1.4 插槽链路 + M2 命令),让每一步都有浏览器可见或测试可断言的产物,再横向铺开功能面。
|
||
|
||
配套纪律:每步一个 commit(`feat(editor)` / `test(editor)` 等,见 AGENTS.md);测试文件按项目实际约定放 `web/test/`(不是 `src/__tests__`,实施规格 §6 的路径为示意,落位时以本文件为准);不覆盖、不回滚非本次变更。
|
||
|
||
## 1. 里程碑总览
|
||
|
||
| 里程碑 | 核心产出 | 演示 / 验收标准 | 依赖 |
|
||
|---|---|---|---|
|
||
| **M0 准备与基线** | 编辑器路由占位壳 + 概览流程入口 + 测试骨架 | 浏览器访问 `/projects/:id/editor` 看到占位页;概览页有「剪辑成片」步骤卡;`bun test test/editor-commands.test.ts` 可跑 | 无 |
|
||
| **M1 SDK v2** | v2 类型 + editor-slot-registry + 注册器兼容 + 插槽链路垂直切片 | 一个预设插件注册的 timeline-panel 插槽在占位页**真实渲染**出空面板(链路全通) | M0 |
|
||
| **M2 命令状态机** | editor-commands / editor-history / editor-store + 黄金文件测试 | 命令序列 → 状态快照黄金文件比对通过;撤销/重做 200 层测试通过;手势(移动/裁剪)echo→提交→撤销浏览器可操作 | M1 |
|
||
| **M3 预设插件** | 8 个 editor 插件逐个人轨 + detail.tsx 接线 | 编辑器三段式布局完整:时间线/预览/检查器/素材库/字幕/转写/导出逐区域可见可操作 | M2、M4(API 面) |
|
||
| **M4 后端任务** | 转写 + 渲染异步任务(服务端) | `go test ./...` 通过;前端任务客户端(创建/轮询/SSE)接 editor-transcription/export | M3 前需 API 面 |
|
||
| **M5 权限执行** | plugin-permission-check + fail-closed 接入 | permission-denied 测试通过;未声明 `timeline.command` 的插件写操作被拒;v1 插件权限语义不变 | M1、M3 |
|
||
| **M6 AI 编辑交互** | ai-command-schema + timeline-summary + editor-ai-assistant | 代码侧完成(schema 黄金测试 / 摘要确定性 / 助手面板 M6.3 已实现,tsc + 全量测试 + 构建通过);待用户浏览器冒烟:命令执行→撤销、非法命令整批拒绝、批量 diff 预览 | M2、M3、M5 |
|
||
|
||
## 2. 原子步明细
|
||
|
||
> 每步的「验证」是**最轻**的通过标准;「完成标准」是这一步真正做完的标志。
|
||
|
||
### M0 准备与基线
|
||
|
||
| 步骤 | 改动文件 | 内容 | 验证 | 完成标准 |
|
||
|---|---|---|---|---|
|
||
| M0.1 | `web/src/pages/projects/detail.tsx` | `DetailView` 加 `"editor"`;`views` 数组加「剪辑」项(常驻,插在「项目画布」后);`editor` 视图渲染**占位页**(`editor-slot-registry` 为空时显示空态壳) | `bun run typecheck`;浏览器 `/projects/:id/editor` | tab 存在且可切换,占位页可见,其余 6 视图不受影响 |
|
||
| M0.2 | `web/src/pages/projects/detail/overview.tsx` | `productionSteps` 在「镜头视频」与「交付与打包」间插入「剪辑成片」步骤卡(`href: /projects/:id/editor`) | `bun run typecheck`;浏览器概览页 | 步骤卡可见可跳转;完成态 = 镜头视频齐备且已有时间线 |
|
||
| M0.3 | `web/test/editor-commands.test.ts`(新建骨架) | 空测试壳 + `bun test` 跑通;确认 `web/test/` 下测试基建 | `bun test test/editor-commands.test.ts` | 测试可运行(1 个占位断言通过) |
|
||
|
||
### M1 插件 SDK v2
|
||
|
||
| 步骤 | 改动文件 | 内容 | 验证 | 完成标准 |
|
||
|---|---|---|---|---|
|
||
| M1.1 | `web/src/lib/plugins/plugin-types.ts` | 加 `PLUGIN_API_VERSION "yingce.plugin/v2"` 类型、编辑器贡献类型(timeline-panel / preview-renderer / inspector / asset-ingest / subtitle-tool / transcription-provider / export-renderer / ai-assistant)、`timeline.*` / `export.run` / `ai.text` 权限字面量 | `bun run typecheck` | 纯类型增量,v1 类型零改动,既有代码零编译错误 |
|
||
| M1.2 | `web/src/lib/plugins/editor-slot-registry.ts`(新建) | 插槽注册表:`registerEditorSlot / unregisterEditorSlot / getEditorSlot`,按插槽类型 + 优先级排序,幂等注册 | 新增 `web/test/editor-slot-registry.test.ts`:注册/去重/排序/未知插槽 | 专项测试通过,无 UI 依赖(纯函数) |
|
||
| M1.3 | `web/src/lib/plugins/plugin-registry.ts` | 注册断言按 apiVersion 分支:v1 保持现状(kebab-case + ≥1 贡献);v2 增加插槽贡献合法性校验;重复权限检查对 v2 生效 | `bun test test/canvas-node-registry.test.ts`(既有)全过 + 新增 v2 注册测试 | v1 插件行为完全不变;v2 注册/拒绝路径测试通过 |
|
||
| M1.4 | `web/src/lib/plugins/builtin/editor/editor-shell.ts`(临时)+ `web/src/lib/plugins/builtin/index.ts` | 垂直切片:`editor-shell` 预设插件注册 1 个 timeline-panel 插槽,占位页渲染该插槽 | `bun run typecheck`;浏览器 `/projects/:id/editor` | 插槽注册→渲染链路全通(空面板可见),**这是第一条垂直切片** |
|
||
|
||
### M2 命令状态机(纯函数层,黄金文件先行)
|
||
|
||
| 步骤 | 改动文件 | 内容 | 验证 | 完成标准 |
|
||
|---|---|---|---|---|
|
||
| M2.1 | `web/src/lib/timeline/editor-commands.ts` + `web/test/fixtures/commands.golden.json`(新建) | 核心 op:`addClip / moveClip / trimClip / splitClip / removeClip / setClipProperty / addSubtitle / removeSubtitle`;reducer 纯函数;黄金文件 = 命令序列→期望状态快照 | 新增 `web/test/editor-commands.test.ts`:逐命令断言 + 黄金文件比对 | 全部 op 通过;非法 payload 抛错路径覆盖 |
|
||
| M2.2 | `web/src/lib/timeline/editor-history.ts`(新建) | 快照撤销栈:200 层上限、undo/redo、undo 后新命令清空 redo | 新增 `web/test/editor-history.test.ts` | 边界测试通过(0 层/200 层/undo 后 redo 清空) |
|
||
| M2.3 | `web/src/lib/timeline/editor-store.ts`(新建) | zustand store:命令入队→reducer→新状态;echo 预演与 release 提交;1.5s 防抖保存(localforage + 后端项目接口);保存失败向上抛 | 新增 `web/test/editor-store.test.ts`(mock 保存层) | 队列/防抖/失败传播测试通过 |
|
||
| M2.4 | 手势 echo 层(`web/src/components/editor/` 起步最小件) | 时间线区域支持移动、裁剪两个手势:拖动中本地 echo 预演,release 提交命令 | `bun run typecheck` + M2.1 测试 | 浏览器拖拽片段→撤销回退可操作(与画布拖拽不冲突,含 `data-canvas-no-zoom` 边界) |
|
||
|
||
### M3 预设插件(每步一个插件,每步后对应区域可见)
|
||
|
||
> 顺序按依赖:面板(数据展示)→ 预览 → 检查器(选中联动)→ 素材入轨(数据源)→ 字幕 → 转写/导出(依赖 M4 API 面,可先接 mock)。
|
||
|
||
| 步骤 | 改动文件 | 内容 | 验证 | 完成标准 |
|
||
|---|---|---|---|---|
|
||
| M3.1 | `web/src/lib/plugins/builtin/editor/editor-timeline-panel.ts` | `createTimelinePanel` → 注册 timeline-panel(bottom);渲染时间线轨道区(读 editor-store,接 M2.4 手势) | typecheck + `bun test test/editor-*.test.ts` | 轨道/片段渲染、选中联动、撤销重做 UI |
|
||
| M3.2 | `web/src/lib/plugins/builtin/editor/editor-preview-monitor.ts` | `createPreviewMonitor` → 注册 preview-renderer(center);浏览器原生近似渲染 + 播放头/时间码/帧步进 | typecheck;浏览器预览可播放 | 预览与时间线播放头同步(走 `buildTimelineRenderPlan` 预览近似路径) |
|
||
| M3.3 | `web/src/lib/plugins/builtin/editor/editor-inspector.ts` | `createInspector` → 注册 inspector(right);选中片段属性(起止/音量/变速/滤镜参数模型) | typecheck;浏览器选中联动 | 属性编辑经 `setClipProperty` 命令入队,可撤销 |
|
||
| M3.4 | `web/src/lib/plugins/builtin/editor/editor-asset-ingest.ts` | `createAssetIngest` → 注册 asset-ingest(left);角色资产/项目资产/本地上传 → 拖入时间线(`asset-ingest` 权限 + directMedia) | typecheck;浏览器拖拽入轨 | 素材入轨生成 clip(`addClip` 命令),复用现有资产层 |
|
||
| M3.5 | `web/src/lib/plugins/builtin/editor/editor-subtitle-tools.ts` | `createSubtitleTool` → 注册 subtitle-tool(right 折叠);SRT 导入导出(复用 `srt-parser`/`srt-resegment`)、AI 高亮、批量样式、**「从节点重建字幕片段」命令(§3.1 快照契约)** | typecheck + 新增字幕命令测试 | SRT 往返一致;重建命令替换过期快照(单向显式同步) |
|
||
| M3.6 | `web/src/lib/plugins/builtin/editor/editor-transcription.ts` | `createTranscription` → 注册 transcription-provider;调 M4 转写任务(API 面未就绪时先 mock) | typecheck;浏览器触发转写→字幕入轨 | 转写结果落字幕轨道(ADR-0004 协议) |
|
||
| M3.7 | `web/src/lib/plugins/builtin/editor/editor-export.ts` | `createExporter` → 注册 export-renderer;导出面板 → `buildTimelineRenderPlan` + 后端渲染任务 / ffmpeg.wasm 降级 | typecheck;浏览器导出 MP4 | 导出走单一滤镜图计划;`nodeId` 悬空引用按 §3.1 降级/报错二分 |
|
||
| M3.8 | `web/src/pages/projects/detail.tsx` + `web/src/lib/plugins/builtin/editor/index.ts` + `web/src/lib/plugins/builtin/index.ts` | editor 视图从 `editor-slot-registry` 渲染全部插槽;8 个插件静态注册(含 editor-ai-assistant 的 M6 先注册占位贡献,M6 补实现) | `bun run build`(全量)+ 浏览器三段式布局 | 布局完整、停用插件显示空态、v1 插件不受影响 |
|
||
|
||
### M4 后端任务(服务端)
|
||
|
||
| 步骤 | 改动文件 | 内容 | 验证 | 完成标准 |
|
||
|---|---|---|---|---|
|
||
| M4.1 | `backend/`:`task_timeline.go` + `transcription_whisper.go`;路由 POST `/api/timeline/transcriptions`(注册于 `RegisterTaskRoutes`) | 转写异步任务:`CreateTimelineTranscriptionTask`(feature 门控/资源归属/可转写类型校验)配额入队(local/whisper.cpp,不走模型路由与计费);worker 顶部按类型分叉 `processTimelineTranscription`,本地 whisper.cpp HTTP 转写,结果 `ResultJSON{segments,srt,language}` 落盘 | 转写相关单测 PASS(`go test ./internal/service/ ./internal/handler/`) | 未配置 `CANVAS_WHISPER_BASE_URL` 时任务明确失败并提示 |
|
||
| M4.2 | `backend/`:`task_render.go` + `timeline_render_plan.go`;路由 POST `/api/timeline/renders`(注册于 `RegisterTaskRoutes`) | 渲染任务:接收 v2 快照 → `buildRenderPlan`(含可渲染媒体校验)→ 服务端 ffmpeg 合成 → 产物入资源存储,`ResultJSON` 记 resourceId | render 单测 PASS | 60 分钟超时;`CANVAS_FFMPEG_PATH` 或 PATH |
|
||
| M4.3 | `web/src/services/api/timeline-tasks.ts` + `editor-transcription.tsx` | 转写任务客户端:创建 → `waitForGenerationTask` 轮询(25min)→ 结果 segments 映射 SrtEntry 写入字幕轨道(去 mock) | typecheck 通过 | 浏览器端到端:发任务→进度→字幕入轨待用户确认 |
|
||
|
||
### M5 权限执行(横切,fail-closed)
|
||
|
||
| 步骤 | 改动文件 | 内容 | 验证 | 完成标准 |
|
||
|---|---|---|---|---|
|
||
| M5.1 | `web/src/lib/plugins/plugin-permission-check.ts`(新建) | 纯函数:`checkPermission(plugin, required, context)`——声明缺失 → 拒绝(fail-closed);`timeline.write` 拆分为 `timeline.read`/`timeline.command` 粒度 | 新增 `web/test/plugin-permission-check.test.ts`(10 用例,含 editor-shell 真实回归) | 缺失/越权/边界测试通过 ✅ |
|
||
| M5.2 | 接入执行路径:`editor-slot-registry` 渲染时 + 命令队列入口 + 模型调用 | 插槽渲染校验、命令入队校验、AI 命令校验(`timeline.command` 未声明 → 拒绝) | `bun test test/plugin-permission-check.test.ts test/editor-*.test.ts`;**v1 插件全量既有测试回归** | 未声明权限的写操作被拒;v1 插件行为零回归(权限语义不收紧存量) |
|
||
| M5.3 | **执行路径接入(边界说明)**:`web/src/pages/projects/detail/editor.tsx` SlotStack fail-closed 过滤(缺权限插槽 → 一行诊断条,不渲染);`editor-shell` manifest 权限补全为 `timeline.read/timeline.command/export.run` | 命令队列入口与模型调用是**宿主全局命令**(M2 editor-commands 注册表,v1 语义);v2 插件不代发宿主命令,仅贡献插槽。M5 权限域(timeline.*/export.run)只约束 v2 插件插槽贡献与后续 M6 AI 命令代发通道,宿主 UI 手势不受权限校验——与 ADR-0007「v1 权限语义一字不动」一致 | editor-shell 8 插槽真实回归可渲染;全量 bun test 无新增失败 | 缺权限插槽被拒渲染且有诊断;editor-shell 权限齐备可渲染全部 8 插槽 ✅ |
|
||
|
||
### M6 AI 编辑交互(ADR-0007)
|
||
|
||
| 步骤 | 改动文件 | 内容 | 验证 | 完成标准 |
|
||
|---|---|---|---|---|
|
||
| M6.1 | `web/src/lib/timeline/ai-command-schema.ts` + `web/test/fixtures/ai-commands.golden.json`(新建) | per-op 命令 JSON schema(与 M2.1 handler 同源维护)、`aiCommandSchemaVersion` 版本化;fail-closed 校验函数 | 新增 `web/test/ai-command-schema.test.ts` | 合法/非法 payload 全覆盖;单条非法整批拒绝;错误回填格式稳定 ✅(14 用例全绿;golden op 集与 EDITOR_COMMAND_OPS 黄金同步断言通过) |
|
||
| M6.2 | `web/src/lib/timeline/timeline-summary.ts`(新建) | 时间线结构化精简摘要(轨道/片段/时长/关键属性,不含媒体二进制) | 新增 `web/test/timeline-summary.test.ts` | 摘要确定性强(同输入同输出);大对象排除 ✅(16 用例全绿;折叠上限/中文字幕统计/空态覆盖) |
|
||
| M6.3 | `web/src/lib/plugins/builtin/editor/editor-ai-assistant.tsx` | AI 助手:右上悬浮对话面板(OpenAI Canvas 风格,由顶部工具栏 Sparkles 入口开合;面板内文本模型选择器 + 消息流 + 空态引导 + 输入条)、命令执行链路(≤3 条直执行 / >3 条 diff 预览待确认)、宿主 registry 拒绝 failTurn 回填失败标记 | typecheck + M6.1/M6.2 测试 + `bun run build` ✅ | 自然语言→命令→撤销全链路代码侧就绪(≤3 条直执行按条入撤销栈,撤销按条回退);非法命令回填错误;与画布会话上下文隔离(历史存 `historyRef`,仅时间线上下文入提示词)✅(浏览器冒烟待用户确认) |
|
||
|
||
## 3. 依赖关系
|
||
|
||
```
|
||
M0(壳+骨架)──▶ M1(SDK v2 + 插槽链路)
|
||
│
|
||
▼
|
||
M2(命令状态机)
|
||
│
|
||
┌───────────┴───────────┐
|
||
▼ ▼
|
||
M4(后端任务)◀───(API 面先行,M3.6/3.7 可先 mock)
|
||
│ │
|
||
▼ ▼
|
||
M3(8 插件逐个)──▶ M5(权限执行,横切接入)
|
||
│
|
||
▼
|
||
M6(AI 编辑交互)
|
||
```
|
||
|
||
- M3.6 / M3.7 只依赖 M4 的 **API 面**(可先用 mock 接口),不必等 M4 全部完成。
|
||
- M5 横切:在 M3 完成后接入执行路径,避免在插件开发期被权限拦截干扰;但 M1.3 注册期就应声明权限字段。
|
||
- M6 依赖 M2(命令)、M3(面板)、M5(权限),最后做——它复用全部前置协议,是收尾验证。
|
||
|
||
## 4. 验证命令速查(以当前 package.json / go.mod 为准)
|
||
|
||
| 场景 | 命令 |
|
||
|---|---|
|
||
| 前端类型检查(每步) | `cd web && bun run typecheck` |
|
||
| 前端专项测试 | `cd web && bun test test/editor-commands.test.ts`(按文件替换) |
|
||
| 前端全量构建(M3.8 / 里程碑验收) | `cd web && bun run build`(含 build:bridge + tsc + vite build,较重,非每步跑) |
|
||
| 后端测试 | `cd backend && go test ./...` |
|
||
| 浏览器冒烟 | 对照实施规格 §7.4 清单(编辑器三路由、明暗主题、空态、快捷键不冲突) |
|
||
|
||
## 5. 每步通用完成检查
|
||
|
||
- [ ] diff 只含本步目标文件,无顺手改动
|
||
- [ ] 类型/调用方/文档同步(本步涉及的方案章节如有出入,当场修正)
|
||
- [ ] 错误与权限路径完整(不 `catch { return defaultValue }` 吞错)
|
||
- [ ] 验证如实记录(通过的命令 + 结果;不能浏览器验证时写明替代依据)
|
||
- [ ] 按 AGENTS.md 提交规范 commit(`feat(editor): ...` / `test(editor): ...`)
|
||
|
||
## 6. 高风险步与回退
|
||
|
||
| 步骤 | 风险 | 缓解 / 回退 |
|
||
|---|---|---|
|
||
| M1.3 注册器 v1/v2 分支 | 破坏既有 v1 插件(eagle/prompt-optimizer/workflows/portrait-clearance) | 分支隔离 + 既有测试全量回归;异常时 revert 该 commit(v1 路径零改动原则) |
|
||
| M3.8 detail.tsx 接线 | editor 视图影响其余 6 视图 | 视图按 id 分发,editor 分支独立渲染;回退 = revert M3.8,其余插件贡献无损 |
|
||
| M5.2 权限收紧 | v1 插件未声明新权限域被误拒 | **fail-closed 只针对新权限域**(timeline.* / export.run / ai.text),v1 既有权限语义一字不动;先加测试再接入 |
|
||
| M4.1 转写 provider | ~~部署路径未定~~ 已定:本地 whisper.cpp HTTP(`CANVAS_WHISPER_BASE_URL`),不接云 ASR | 语音数据不出本机;未配置仅影响转写任务(任务明确失败并提示) |
|
||
| M2.4 手势 echo 与画布拖拽冲突 | 时间线内拖拽与画布手势系统互扰 | 复用画布事件忽略选择器约定(modal/popover/dropdown)与 `data-canvas-no-zoom` 边界,冒烟清单含快捷键不冲突项 |
|
||
|
||
## 7. 里程碑验收顺序(推荐推进节奏)
|
||
|
||
1. 完成 M0+M1 → **第一次演示**:`/projects/:id/editor` 占位页有真实插槽渲染(证明插件链路成立)
|
||
2. 完成 M2 → **第二次演示**:纯命令驱动的可撤销时间线(无 UI 也能证明核心正确)
|
||
3. 完成 M3(含 M4 API 面)→ **第三次演示**:完整三段式编辑器可编辑可导出
|
||
4. 完成 M5+M6 → **第四次演示**:权限收紧 + 对话式剪辑(ADR-0007 完整落地)
|
||
|
||
每两次演示之间建议不超过一个里程碑;若一个里程碑内停滞超过 3 次同类失败,停下记录现象、已排除项与新假设(AGENTS.md §8),再切换路径。
|