# 编辑器预设插件化实施规格 > 文档状态:待实施(决策已定,见 ADR-0001 ~ ADR-0005)。 > > 目标读者:后续负责实现、评审和验收的 AI 或开发者。 > > 关联决策:[ADR-0001 整合边界](../adr/0001-editor-integration-boundary.md)、[ADR-0002 命令协议](../adr/0002-edit-command-protocol.md)、[ADR-0003 预览导出分层](../adr/0003-preview-export-layering.md)、[ADR-0004 转写服务化](../adr/0004-autocaption-transcription-service.md)、[ADR-0005 预设插件架构](../adr/0005-editor-preset-plugin-architecture.md)、[ADR-0006 语音滤镜与模板](../adr/0006-voice-filters-and-templates.md)。 > > 硬约束:遵循 AGENTS.md 的目录职责、API 合同(`request.ts` / `custom-channel-relay`)、三层设计 token 与验证纪律;编辑器能力一律以预设插件贡献,不在核心库写编辑器专属硬编码分支。 ## 0. 产品视图:实现完成后的编辑器 > 本节描述目标形态,帮助实现者理解"做完是什么样";与 ADR-0001 ~ 0006 决策一一对应。UI 依据 `../design/workspace-shell-design.mdx` 三段式布局与既有设计 token,不引入新设计体系。 ### 0.1 编辑器形态 编辑器是项目详情下的独立视图,对标 Concat 面板结构(时间线 / 预览 / 素材库 / 右侧面板),落在三段式工作区壳内: ```text ┌─ 顶部栏 ──────────────────────────────────────────────┐ │ ◀ 返回项目 剪辑中 · 已保存 ↩撤销 ↪重做 导出成片 ▸ │ ├────────┬──────────────────────────────────┬───────────┤ │ 左侧栏 │ 中央预览监视器 │ 右侧检查器 │ │ 素材库 │ 播放/暂停/时间码/帧步进 │ 选中片段 │ │ ·角色资产│ (浏览器原生近似渲染) │ 起止/音量 │ │ ·本地上传│ │ 变速/滤镜 │ │ ·转场/效│──────────────────────────────────│ 字幕文本 │ │ 果分类 │ 时间线面板(底部) │ │ │ │ ┌──轨道区:视频/音频/字幕/文字──┐│ │ │ │ │ 拖拽移动 裁剪手柄 分割 吸附 ││ │ │ │ └──────────────────────────────┘│ │ └────────┴──────────────────────────────────┴───────────┘ ``` 每个区域都是**插件插槽**(§5.2):时间线面板(bottom)、预览监视器(center)、右侧检查器(right)、素材入轨(left)、字幕工具(right 折叠)、转写/导出(弹层);语音滤镜与模板为增量预设插件(见 ADR-0006)。停用某插件时对应区域显示空态,其余区域不受影响。 **与画布内 timeline-dialog 的定位差异(避免两套时间线 UI)**:画布 `canvas-timeline-dialog.tsx` 是**画布级快速组装**——单画布、轻量,从当前画布节点就近组装预览;编辑器是**项目级工作台**——跨画布、命令协议(ADR-0002)、撤销栈、插件面板(ADR-0005)。两者读写**同一 `TimelineProject`**、走**同一 `buildTimelineRenderPlan`**,形态层级不同、协议同源;不另立数据模型、不复制实现。 **编辑器 ↔ 画布 ↔ 短剧 关联图景**(数据流,均基于既有实现): ```text 短剧(章节 → 分镜) │ chapterId / sceneId / storyboardRowId 挂在画布节点 metadata;shortDramaEnabled → guided 工作流(canvas-starter) ▼ 画布(分镜画布) │ buildTimelineFromNodes / syncNodeSubtitlesToTimeline(timeline-build.ts) ▼ 时间线(TimelineProject v2,挂在 project 上,非画布私有) │ clip.nodeId 引用画布节点;字幕 clip = subtitleEntryIndex + text 快照 ▼ 编辑器(/projects/:projectId/editor,本方案) │ buildTimelineRenderPlan 单一滤镜图 → MP4 ▼ 成片(onCreateAssembledNode 合成节点放回画布;导出可落项目资产/交付,见 M 阶段待定项) ``` ### 0.2 入口(三层) - **主入口 · 顶部导航 tab**:项目详情页视图切换器新增 `editor`("剪辑"),路由 `/projects/:projectId/editor`,与「分镜制作」「项目画布」并列——从项目任何视图随时可达。实现点:`detail.tsx` 的 `DetailView` 联合类型加 `"editor"`、`views` 数组加对应项(图标建议 `Film`/`Scissors`,插在「项目画布」之后),路由复用既有 `/projects/:projectId/:view` 通配(`router.tsx`),无需新路由。 - **到达路径(完整链路)**:全局侧边栏「短剧创作」→ `/projects` 项目列表 → 项目详情页 → 顶部 tab「剪辑」。侧边栏入口实际显示名是「短剧创作」(`navigation-tools.ts`:`slug: "projects"`、`label: "短剧创作"`、Clapperboard 图标),不是「项目」;编辑器是短剧创作功能域内、项目详情页下的一个视图,全局导航不新增任何入口。 - **入口常驻(决策,2026-09-01)**:侧边栏与命令面板的「短剧创作」入口(`/projects`,即短剧创作模块)**不随功能开关渲染**——「短剧创作/剪辑」是普通用户核心功能,入口可见性不依赖管理员后台配置。`shortDramaEnabled` 仅保留为**部署级兜底**:管理员明确关闭短剧功能时,由 `RequireFeature` 渲染「暂未开放」提示页(带返回按钮,`require-feature.tsx`),而非隐藏入口。编辑器 `views` 项同理常驻。 - **流程入口 · 概览流程步骤卡**:`overview.tsx` 的 productionSteps 在「镜头视频」与「交付与打包」之间插入「剪辑成片」步骤卡(`href: /projects/:projectId/editor`,完成态 = 镜头视频齐备且已有时间线),快速入口区补「进入剪辑」。 - **状态入口 · 当前任务 CTA**:镜头视频全部就绪后,概览当前任务阶段(`overviewStage`)由"检查交付"扩展为"剪辑成片",主 CTA 变为「开始剪辑」。 ### 0.3 使用流程 ```text ① 项目概览 → 切"剪辑"视图 → 进入编辑器(默认 7+2 插件全开) ② 左侧素材库选素材(角色资产/项目视频/本地上传)→ 拖入时间线(asset-ingest → directMedia) ③ 时间线编辑:拖拽排布、裁剪手柄、分割、变速、吸附对齐 —— 手势先 echo 预演,松手才提交命令,可一键撤销(200 层快照) ④ 预览监视器实时看近似效果(滤镜/转场为 CSS/canvas/Web Audio 近似) ⑤ 右侧检查器精调选中片段(起止、音量、变速、滤镜) ⑥ 字幕:字幕工具导入 SRT / AI 高亮;自动字幕按钮 → 后端转写任务 —— 进度展示 → 完成后 SRT 自动写回字幕轨道 ⑦ 导出成片:导出面板选分辨率/格式/烧录字幕 → 后端 ffmpeg 任务 → 产物下载 ``` 与画布工作流的闭环:**分镜/AI 生成的视频 → 项目资产 → 入轨 → 剪辑 → 烧录字幕 → 成片**,是 AI 影视创作工作台的成片出口。 ### 0.4 功能覆盖对照(Concat 已实现功能) | Concat 功能 | 覆盖度 | 说明 | | --- | --- | --- | | 多轨编辑(视频/音频/字幕/文字) | ✅ | TimelineProject v2 轨道类型齐全 | | 剪切工具 split/trim/merge | ✅ | M2 命令集含分割/裁剪/移动/删除;merge 需在 M2 命令集确认补一条 | | 变速 speed control | ✅ | 命令集含 setSpeed | | 自动字幕 | ✅(形态不同) | Concat 本地 whisper;影策服务端任务(ADR-0004),浏览器不跑模型 | | 字幕高亮 | ✅(领先) | 已有 subtitle-highlight-* 库;Concat 尚在 roadmap | | 标题与样式文本 | 🟡 | text 轨道 + 字幕工具批量样式;复杂标题动画依赖画布文本节点 | | 转场 transitions | 🟡 | 单一滤镜图计划含转场降级;效果库是渲染器插槽扩展点,首版基础淡入淡出 | | 语音滤镜 | ➕ 见 ADR-0006 | 音频 DSP:Web Audio 预览近似 + ffmpeg 音频滤镜图导出 | | 模板 Templates | ➕ 见 ADR-0006 | 项目快照:存为模板 / 从模板新建;模板中心暂不做 | | TTS 配音 | 🟡 能力在别处 | 生成式语音走既有模型渠道(ADR-0004 明确不属编辑器范围) | | 效果/转场库扩展(Concat roadmap) | ✅ 架构即答案 | 渲染器/滤镜图插件化正是为其设计 | ## 1. 目标与范围 ### 1.1 目标 把编辑器(时间线编辑、预览、导出、自动字幕)做成**预设插件(preset plugins)**,参考 astravia「万物皆可插件」理念:编辑器全部 UI 与能力都是插件贡献,宿主核心只保留协议层(时间线状态机、编辑命令、滤镜图计划、任务协议)。现有 `yingce.plugin/v1` 演进为 v2(UI 插槽 + 预设分发 + 权限执行校验),编辑器域是 v2 的第一个完整消费方。 ### 1.2 首版必须交付 - 插件 SDK v2:`yingce.plugin/v2` 清单类型、UI 插槽注册 API、预设分发字段、权限执行校验(首个校验点:编辑器宿主 API)。 - 时间线命令状态机:可序列化编辑命令 + 单队列 + 有界快照撤销(200 层)+ 手势 echo。 - editor 预设插件(`builtin/editor/`):时间线面板、预览监视器、右侧检查器、素材入轨、字幕工具、自动字幕(转写)、导出、AI 助手,共 8 个(7 核心 + editor-ai-assistant,见 ADR-0007)。 - 后端任务:转写任务(ASR 可插拔)+ ffmpeg 导出任务,复用现有任务队列与配额。 - 测试与验证:命令黄金文件、渲染计划黄金文件、插槽渲染冒烟、权限拒绝路径、AI 命令 schema 校验测试、浏览器验证清单。 ### 1.3 明确不做 - 不做外部插件安装、插件市场、zip 上传、沙箱隔离(astravia 的租户选择机制待真正出现外部插件需求时再补)。 - 不做多端协同命令同步(命令日志协议预留,不实现服务端广播)。 - 不在浏览器跑 whisper;转写一律走后端任务。 - 不新增第二套滤镜串生成路径(单一 `buildTimelineRenderPlan` 约束对插件强制生效)。 - 不迁移既有画布 v1 插件(v1 贡献类型与字段保持兼容,无需改动)。 ## 2. 现状盘点(实现者必须知道的事实) 以下为 2026-09-01 勘察结论,以当前代码为准: ### 2.1 插件体系现状(`web/src/lib/plugins/`) - `plugin-types.ts`:`PLUGIN_API_VERSION = "yingce.plugin/v1"`;贡献类型 `provider | workflow | canvas-node | transform | command | asset-source | usage-observer | ai-capability | agent | import-export`;权限 `canvas.* / asset.* / generation.run / ai.text / media.read / usage.read / external.open`;`RegisteredPlugin` 提供 `activate/deactivate/createAssetSource/createPromptOptimizer`;`PluginManifest` 含 `surfaces`(仅声明字段,无插槽注册实现)。 - `plugin-registry.ts`:`registerPlugin`(`assertManifest` 校验 kebab-case id、apiVersion 必须等于 v1、权限不重复、至少一种贡献)→ `Map` 存储;canvasNodes 会同步注册到 `@/lib/canvas/node-registry`。 - `plugin-storage.ts`:启用态与配置持久化。 - `builtin/`:`index.ts` 静态 import 5 个插件(eagle、prompt-optimizer、workflows、portrait-clearance),`application.tsx` 顶层 `import "@/lib/plugins/builtin"` 完成注册。 - `use-plugin-store.ts`:插件启用态管理。 **结构性缺口(v1 不可直接支撑编辑器插件化)**:无 UI 插槽注册 API;无 editor 域贡献类型;无预设分发语义;内置插件为源码硬编码导入;权限只有清单字符串、宿主无调用点校验。 ### 2.2 时间线现状(`web/src/types/timeline.ts` + `web/src/lib/timeline/`) - `TimelineProject`(v2 数据形状):轨道/片段/directMedia 已定义;一期字幕数据已落地。 - 纯函数库:`timeline-build.ts`(构建)、`timeline-placement.ts`(放置)、`timeline-snap.ts`(吸附)、`timeline-tracks.ts`、`timeline-view.ts`、`srt-parser.ts`、`srt-resegment.ts`、`subtitle-highlight-*`(AI 字幕高亮)。 - 导出规划:`timeline-to-ffmpeg.ts` 导出 `buildTimelineRenderPlan(timeline, sources, context): TimelineRenderPlan`(含 `steps` 与 `SUBTITLE_FILE`),是**唯一的**滤镜图计划来源。 - 画布 ↔ 时间线引用:`timeline-build.ts` 的 `buildTimelineFromNodes`(画布节点 → 时间线)与 `syncNodeSubtitlesToTimeline`(节点字幕 → 时间线字幕轨道,`project.tsx` 字幕保存时调用);`clip.nodeId` 引用画布节点、字幕 clip 带 `subtitleEntryIndex` + `text` 快照;画布内 `canvas-timeline-dialog.tsx` 已有组装/裁剪/入轨/成片回画布闭环(编辑器为其**项目级形态**,同一 `TimelineProject`,见 §3.1)。 - 尚无编辑命令层:当前修改模型是直接写状态,没有命令/撤销/echo。 ### 2.3 后端任务现状(`backend/internal/`) - 任务模型在 `backend/internal/model/models_task.go`;任务输出在 `service/task_output.go`;provider 调度在 `service/provider.go`。 - 后端目前不执行 ffmpeg;无 ASR/转写能力。 ### 2.4 设计资产 - `docs/design/workspace-shell-design.mdx`:三段式布局(侧栏 / 主区 / 顶部栏)与设计 token,编辑器面板复用,不引入新设计体系。 ## 3. 目标架构 ```text ┌──────────────────────── Web / React ─────────────────────────┐ │ 宿主核心(协议层,冻结扩展) │ │ ├─ timeline 状态机(真相源)——命令队列/apply/快照撤销 │ │ ├─ 滤镜图计划 buildTimelineRenderPlan(单一来源) │ │ ├─ 任务协议(转写/导出任务客户端) │ │ └─ 插件宿主:editor-slot-registry 插槽渲染 + 权限执行校验 │ ├──────────────────────────────────────────────────────────────┤ │ 插件 SDK v2(yingce.plugin/v2) │ │ ├─ definePlugin 统一形态(v1 字段兼容) │ │ ├─ UI 插槽注册 API(面板/渲染器/工具/快捷键域) │ │ ├─ 预设分发(preset: true, defaultEnabled) │ │ └─ 权限执行(宿主调用点 fail-closed 校验) │ ├──────────────────────────────────────────────────────────────┤ │ editor 预设插件(builtin/editor/,默认启用不可卸载) │ │ ├─ editor-timeline-panel 时间线面板 │ │ ├─ editor-preview-monitor 预览监视器(近似渲染) │ │ ├─ editor-inspector 右侧检查器 │ │ ├─ editor-asset-ingest 素材入轨 │ │ ├─ editor-subtitle-tools 字幕工具 │ │ ├─ editor-transcription 自动字幕(转写面板) │ │ ├─ editor-export 导出面板 │ │ └─ editor-ai-assistant AI 助手(对话式剪辑,ADR-0007)│ └──────────────────────────────────────────────────────────────┘ │ 任务协议(前端不变) ┌───────▼───────────── Go 后端 ───────────────┐ │ 任务队列 + 配额:transcription / render 任务 │ │ ├─ ASR provider(whisper.cpp / 云 ASR 可插拔)│ │ └─ ffmpeg 导出(主路径;wasm 为浏览器降级) │ └──────────────────────────────────────────────┘ ``` 数据流(编辑):UI 手势 → 本地 echo 预演 → `release` 提交一个编辑命令 → 单队列 → reducer → 新 `TimelineProject`(真相源)→ 快照撤销栈 + 1.5s 防抖保存(localforage + 后端项目接口)。 ### 3.1 引用契约:画布节点 ↔ 时间线片段 时间线片段与画布节点是**层间引用**而非合并模型:clip 以 `nodeId` 引用画布节点(素材来源),字幕以 `subtitleEntryIndex` + `text` 快照引用节点字幕。现有实现已暴露 3 个真实耦合点,编辑器实现必须立契约束缚: 1. **媒体元数据双份**:`timeline-build.ts` 构建 clip 时把节点 `durationMs` 复制为 `sourceDurationMs`,构建后节点时长变化不自动同步。 2. **字幕双份(快照契约)**:节点 `subtitleEntries` 是实体,字幕 clip `text` 是快照。`syncNodeSubtitlesToTimeline` 已提供「节点 → 时间线」单向同步(`project.tsx` 字幕保存时调用);「时间线 → 节点」方向**不自动回写**——编辑后由 `editor-subtitle-tools` 提供显式「从节点重建字幕片段」命令,把漂移变成用户可操作的动作,不做双向隐式同步。 3. **nodeId 悬空引用**:节点删除/替换后 clip 引用失效。render 层(`buildTimelineRenderPlan` sources 解析)必须区分两种结果:**可降级**(跳过该 clip 并提示)与**不可解析**(报错并定位到具体 clip),禁止静默产出错误渲染。 契约总原则:**时间线以「快照 + 可重建」持有来自画布的数据,单向显式同步,双向不同步**;画布数据变更不自动改写时间线,避免隐式漂移。与画布内 `canvas-timeline-dialog`(画布级快速组装,单画布、轻量)不同,编辑器是**项目级工作台**(跨画布、命令协议、撤销栈、插件面板)——两者读写同一 `TimelineProject`、同一 `buildTimelineRenderPlan`,形态层级不同、协议同源。 ## 4. 分阶段实施计划 ### M1 插件 SDK v2(前端插件体系演进) 目标:v1 → v2 类型与注册机制,向后兼容;编辑器域贡献类型与插槽注册表就位。 任务: 1. `web/src/lib/plugins/plugin-types.ts` - 新增 `PLUGIN_API_VERSION_V2 = "yingce.plugin/v2"`;`PluginManifest.apiVersion` 允许 v1 | v2。 - 新增 editor 贡献类型:`timeline-panel | preview-renderer | export-renderer | transcription-provider | subtitle-tool | inspector-panel | asset-ingest | shortcut-scope`(并入 `PluginContributionKind` 与 `PluginContributions`,v1 插件不声明即为空)。 - 新增权限:`timeline.read / timeline.write / timeline.command / transcription.run / export.run`(并入 `PluginPermission` 联合类型)。 - 新增预设分发字段:`preset?: boolean`、`defaultEnabled?: boolean`(默认 `preset: false`,v1 清单不设即保持现状)。 - 新增 `RegisteredPlugin` 的 editor 工厂入口:`createTimelinePanel? / createPreviewRenderer? / createExportRenderer? / createTranscriptionProvider? / createSubtitleTool?`(与 `createAssetSource` 同构)。 2. 新建 `web/src/lib/plugins/editor-slot-registry.ts`:UI 插槽注册表与渲染入口(接口草案见 §5.2),含插槽去重(同一 pluginId+slotId 幂等覆盖)、顺序(`order` 字段)、卸载(`unregisterPlugin` 时同步清理)。 3. `web/src/lib/plugins/plugin-registry.ts`:`assertManifest` 按 apiVersion 分支校验;v2 清单允许空 `contributes`(若仅贡献 UI 插槽,插槽在注册时单独校验);注册/注销时联动 `editor-slot-registry`。 4. `web/src/lib/plugins/builtin/index.ts`:保持静态导入,语义化为"预设插件入口"(注释标明);新增 `builtin/editor/` 子目录。 产出:v1 插件零改动可运行;`listRegisteredManifests` 可枚举 v2 清单。 验证:`cd web && bun run build` 通过;现有 5 个 builtin 插件注册无回归。 ### M2 时间线命令状态机(宿主核心协议) 目标:时间线唯一修改入口命令化,撤销/echo/保存就位;命令 handler 可插件注册。 任务: 1. 新建 `web/src/lib/timeline/editor-commands.ts` - `type EditCommand = { op: string; payload: unknown }`;`type CommandHandler = (state: TimelineProject, payload: unknown) => TimelineProject`(纯函数,禁止依赖组件实例)。 - `createEditorCommandRegistry()`:`register(op, handler)`(宿主与插件共用,插件经宿主 API 注册)、`apply(state, cmd)`(未知 op 抛错,fail-closed)、黄金命令集合(`addClip / moveClip / trimClip / splitClip / deleteClip / setVolume / setSpeed / addSubtitle / updateSubtitle / deleteSubtitle / setDirectMedia / ...`,首版以一期/二期能力为准)。 2. 新建 `web/src/lib/timeline/editor-history.ts`:有界快照撤销(`undoStack` / `redoStack` 各 200 层,快照只存结构化模型,媒体大对象以 directMedia 引用/存储 key 入快照);`undo() / redo() / push(state)`;重做栈在 push 时清空。 3. 新建 `web/src/lib/timeline/editor-store.ts`:命令单队列(串行 apply)、`dispatch(cmd)` 入口、快照撤销接入、1.5s 防抖保存(localforage user-scope + 后端项目接口;写失败必须上报,不吞错)。 4. 手势 echo:在 `editor-store` 提供 `previewGesture(state)` 只读预演(拖拽/裁剪位置),`commitGesture(cmd)` 提交唯一命令;撤销粒度是手势。 5. 锁定滤镜图计划:`timeline-to-ffmpeg.ts` 的 `buildTimelineRenderPlan` 标注为协议模块(新增 JSDoc:插件不得绕过);`timeline-export.ts` 保持降级路径。**render 层按 §3.1 契约处理 `nodeId` 悬空引用**:可降级(跳过 + 提示)与不可解析(报错 + 定位 clip)两条路径,禁止静默错误渲染。 产出:任意编辑操作可回放为命令序列;撤销/重做可用;插件可注册自定义 op。 验证:命令黄金文件测试(§7.1);既有字幕一期功能经命令层无回归。 ### M3 editor 预设插件(UI 与能力插件化) 目标:7 个核心预设插件全部以 v2 清单 + 插槽贡献形态实现(AI 助手插件在 M6,见 ADR-0007),宿主核心不再新增编辑器 UI 硬编码分支。 任务(每个插件 = `builtin/editor/.ts`,`manifest.apiVersion = "yingce.plugin/v2"`,`preset: true`,`defaultEnabled: true`): 1. `editor-timeline-panel.ts`:`createTimelinePanel` → 注册 `timeline-panel` 插槽(dock: bottom);轨道/片段渲染、拖拽移动、裁剪手柄、吸附复用 `timeline-snap`、右键菜单;组件只消费状态机视图并派发命令。 2. `editor-preview-monitor.ts`:`createPreviewRenderer` → 注册 `preview-renderer`;`