# Short Drama Agent Front 短剧 Agent 的前端工作台。支持 `create-drama`、`breakdown` 与 `storyboard` 工作区,使用真实后端 API,不包含演示数据或浏览器端模型调用。 ## 启动 需要 Node.js >= 22.18 和 pnpm 11。 ```bash pnpm install cp .env.example .env pnpm dev ``` 浏览器访问 http://localhost:5173。先在后端仓库启动 `pnpm dev`,默认监听 **3412**。 ```dotenv VITE_API_BASE_URL=/api API_PROXY_TARGET=http://localhost:3412 ``` 更改环境变量后重启 Vite。后端若运行在其他机器或端口,请修改 `API_PROXY_TARGET`。不能把 API Key、数据库密码等秘密写入任何 `VITE_*` 变量。 ## 功能 | 工作区 | 已实现 | | --- | --- | | 项目列表 | 真实项目读取、搜索、状态筛选、新建剧本 | | 剧本创作 | 主题/风格/集数配置、剧集目录与正文、角色、世界观、审核、执行记录、正文导出 | | 创作恢复 | 恢复剧集生成、恢复改写,操作前确认 | | 拆解配置 | 每组集数、人物/场景/道具模块选择、后端分组预览 | | 拆解结果 | 主体、别名、视觉形态、Episode → Beat → Shot、主体绑定、抽取任务状态、校验问题、JSON 导出 | | 拆解恢复 | 失败抽取重试、缺失剧集镜头补齐、分镜引用与 Form 绑定修复 | | 分镜检查 | 按剧集、Beat、正式 Shot ID 浏览;导演设计、环境与主体即时状态;单集覆盖率与 JSON 导出 | | 分镜生成 | Direction / VisualState 单集生成、项目批量补齐、覆盖重生成;并发和自动修复次数;部分失败诊断 | | 镜头工具 | 主体参考图与缺失原因、确定性 GenerationSpec 查询与导出、单镜头/批量视频提示词 | | 形态图片 | 正式形态图库、主图/候选图预览、图片历史与失败记录、Seedream 单图/批量生图、切换主参考图 | 尚未实现:手工编辑剧本或分镜设计(后端暂无对应写接口)、实际视频生成、用户登录。模型输出按纯文本显示,避免执行不可信 HTML。参考图只接受 HTTP(S) 或后端 `/storage/` 路径。 ## 技术与规范 - Vite 8、Vue 3、TypeScript、Vue Router - Tailwind CSS 4,通过 `@tailwindcss/vite` 集成 - Reka UI:Dialog、Tabs、Checkbox、Progress 等无样式基础组件 - Oxlint:脚本质量检查;ESLint:补充 Vue 模板语义 - Oxfmt(不是 `oxformat`):沿用后端四空格、单引号、无分号、无尾逗号的风格 - Vitest + Vue Test Utils + happy-dom:API 契约、组件交互和异步生命周期测试 - Lefthook + Commitlint:提交前执行检查,提交信息使用 `feat:`、`fix:`、`refactor:` 等 ```bash pnpm check # lint + format:check + typecheck + test pnpm format # 格式化 pnpm lint:fix # 修复可自动处理的问题 pnpm build # Vue 类型检查 + 生产构建 pnpm preview # 预览 dist,不含开发代理 ``` Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue SFC 的类型检查由 `vue-tsc` 承担。未照搬后端 `--type-aware` 来冒充完整 Vue 类型检查。 ## 目录 | 目录 | 职责 | | --- | --- | | `src/features/projects` | 项目接口、类型、列表、新建弹窗、项目布局与共享上下文 | | `src/features/create-drama` | 剧本创作 graph 页面 | | `src/features/breakdown` | 拆解 graph 的 API、类型、配置页面、主体与分镜组件 | | `src/features/storyboard` | 分镜 API、类型、生成操作、依赖判断、设计与状态、镜头资源、生成回执 | | `src/features/subject-images` | 正式形态图库、图片生成参数、历史图片、主图选择与批量回执 | | `src/features/workflows` | checkpoint 选择、执行记录、长请求互斥与操作确认 | | `src/components/ui` | 跨业务使用的 Reka UI 封装与公共展示组件 | | `src/composables` | 具备取消和竞态保护的轮询 | | `src/lib` | HTTP、错误格式、日期和导出工具 | | `src/router` | 按 graph 拆分的懒加载路由 | 每个功能模块通过 `index.ts` 暴露公共入口。业务类型和行为函数均添加中文职责注释。不要把下一条 graph 的服务继续堆进 `App.vue`。 ## 接口基线 对齐后端 `qianlanse/short-drama-agent` 的 `dev`: `eac8dc3b1b2ce1ff2bdfeb01d7281cd838f07285`,包含本次新增的只读 `GET /projects/:projectId/subject-forms` 接口。请同步更新前后端 `dev` 后重启后端。 | HTTP | 路径(以 /api 为前缀) | 用途 | | --- | --- | --- | | GET / POST | `/projects` | 项目列表 / 创建剧本 | | GET | `/projects/:id` | 正式数据库详情、剧集、角色、世界观与审核 | | GET | `/projects/:id/checkpoints` | 两条 graph 的 checkpoint;前端按 workflowName 过滤 | | POST | `/projects/:id/resume-generation` | 继续生成未完成剧集 | | POST | `/projects/:id/resume-rewrite` | 恢复审核与改写 | | GET | `/projects/:id/breakdown-preview?groupSize=3&modules=character,scene,prop` | 真实分组与任务预览 | | POST | `/projects/:id/breakdown/start` | 启动拆解 | | POST | `/projects/:id/breakdown/retry` | 仅重试失败的抽取 Task | | POST | `/projects/:id/breakdown/resume-shots` | 复用已成功剧集镜头,生成缺失剧集 | | POST | `/projects/:id/breakdown/resume-storyboard` | 修复已有镜头的 SubjectRef / Form,再持久化 | | GET | `/projects/:id/storyboard-directions?episodeNo=N` | 正式 Shot ID、导演设计、单集覆盖率 | | POST | `/projects/:id/storyboard-directions/generate-test` | 单集导演设计;明确发送 `persist: true` | | POST | `/projects/:id/storyboard-directions/generate` | 项目批量;`concurrency`、`force` | | GET | `/projects/:id/storyboard-visual-states?episodeNo=N` | 已持久化的环境、主体状态、单集覆盖率 | | POST | `/projects/:id/storyboard-visual-states/generate-test` | 单集即时状态;`persist: true`、`maxRepairAttempts` | | POST | `/projects/:id/storyboard-visual-states/generate` | 项目批量;增加 `maxRepairAttempts` | | GET | `/storyboard-shots/:shotId/references` | 主体形态参考图、缺失原因 | | GET | `/storyboard-shots/:shotId/generation-spec` | 后端确定性编译结果,不调用模型 | | POST | `/storyboard-shots/:shotId/video-prompt` | 复用已有 Prompt 或调用模型生成;`force` 控制覆盖 | | POST | `/projects/:id/video-prompts/generate` | 项目批量 Prompt;`concurrency`、`force` | | GET | `/projects/:id/subject-forms` | 正式 SubjectForm ID、主体名称/引用、主图与图片元数据;本次新增只读接口 | | GET | `/subject-forms/:formId/images` | 此形态的全部图片历史及实际 Prompt | | POST | `/subject-forms/:formId/images` | 单图生成;显式 `provider: seedream`,可选 Prompt 和成对尺寸,`setPrimary` | | PUT | `/subject-forms/:formId/images/:imageId/primary` | 选择已完成图片作为主参考图,不调用模型 | | POST | `/projects/:id/subject-images/generate` | 全项目批量生图;`provider`、`concurrency`、`force` | API 层也提供 `state` 与 `breakdown/latest` 方法。页面通过共享 checkpoint 查询取得阶段状态,避免重复拉取同样内容。 ### 异步与恢复约定 1. `POST /projects` 返回顶层 `202 { projectId, status }`,其他接口通常返回 `{ data }`;HTTP 层分别兼容。 2. Breakdown、Storyboard 和恢复接口目前是**长 HTTP 请求**。不设客户端短超时,不自动重试 POST。关闭页面只会丢失请求连接,**不会取消后端任务**。 3. 项目列表每 12 秒、项目详情每 6 秒串行刷新。隐藏页面暂停自动查询。切换项目或离开布局时取消查询,旧响应不会覆盖新项目。 4. 抽取任务进度不是全流程进度。抽取完成之后还需要主体合并、形态、节拍、镜头和持久化。 5. 后端失败 checkpoint 可能只有错误。页面回看最近可用的完整快照以保留成果,同时使用最新记录的 execution 状态。显示的是**最近可用快照**,并不保证全部成果来自最后一次执行。 6. 后端并不为每个阶段提供持久化的 running 状态。没有明确终态时,页面显示“阶段快照 · 执行状态待确认”,不会猜测成功或失败。 7. 恢复操作要求确认后台已停止。当前只做浏览器会话内、项目级互斥,不能替代后端跨浏览器/跨用户的任务锁。断网后先检查后台和 checkpoint,不要立即重复点击。 8. 修改分组或模块后,必须重新预览再启动;重新拆解会调用完整流程,可能替换旧的主体和分镜。 ### Storyboard 约定 1. 入口为 `/projects/:projectId/storyboard`。先完成 Breakdown 的主体、形态、镜头与持久化,再生成 Direction,最后生成 VisualState。 2. 当前 Direction 服务读取**最新** Breakdown 的 `state.breakdownResult.storyboardEpisodeShots`;VisualState 读取顶层 `state.storyboardEpisodeShots`、`subjectCandidates`、`subjectForms`。页面不使用历史可恢复快照代替这些生成前置条件。历史快照只补充镜头标题和剧情描述。 3. 镜头 ID 来自正式数据库 GET,按 `shotId` 关联 Direction 和 VisualState。`shotNo` 仅在 Beat 内唯一,不能用它拼装接口 ID。VisualState 查询响应的环境字段是扁平结构,与单集生成返回的领域结构不同。 4. 单集入口虽然名为 `generate-test`,也是后端目前唯一的单集生成接口。页面发送 `persist: true`,通过校验才保存;**单集始终重生成**,不受批量的覆盖开关影响。 5. 批量默认 `force: false`,跳过完整剧集。重新点击“补齐”用于重试未完成项,没有虚构独立 retry 接口。`force: true` 才覆盖全部。VisualState 可设置非负整数修复次数,`0` 表示不修复。 6. VisualState 单集生成需要该集所有正式镜头都有 Direction;批量接口由后端逐集检查,缺少前置条件的集数会单独失败。HTTP 200 也可能包含校验失败或部分失败,页面展示逐集诊断和修复次数,不视为全成功。 7. 上游重生成**不会自动使下游旧数据失效**。覆盖 Direction 后请重生成 VisualState,修改设计/状态/参考图后按需覆盖 Prompt。覆盖率只说明字段已保存,不能证明下游与新版本一致。 8. 每 6 秒查询当前剧集的两类正式结果;这不是全项目实时进度。后端没有独立 Storyboard running / executionId 查询。回执只保留当前浏览器会话,切换页面不丢失,刷新浏览器会丢失。 9. 参考图和 GenerationSpec 按需 GET,切换镜头或设计变化时取消旧查询。GenerationSpec 需要有效的 Direction、VisualState 和形态关联,不生成图片或视频。 10. 后端当前的 Video Prompt 仍使用镜头描述和参考图,**尚未消费 GenerationSpec**。没有独立 GET Prompt 接口,“读取/生成提示词”仍是 POST;不存在 Prompt 时会调用模型,因此必须确认。`force: false` 复用已有正文;批量响应只有计数和失败列表,不能凭计数构造 Prompt 正文。`generated` 是后端处理成功数,可能包含已存在但状态不为 `prompt_ready` 的复用项。 ### 形态图片约定 1. 入口为 `/projects/:projectId/subject-images`;项目导航、拆解主体列表和分镜参考图缺失提示均可进入。图库自动读取后端已生成图片,不需要重新生图才能显示。 2. 生图使用正式数据库 `SubjectForm.id`。Checkpoint 的领域 `formId` 不是数据库 ID,不能直接调用图片接口。新增查询从项目关联读取正式形态,返回轻量图片元数据,不包含图片的 `rawJson` 或长 Prompt;打开历史弹窗后再读取详细记录。 3. 单图明确发送 `provider: 'seedream'`,避免后端单图入口默认使用 mock。模型和凭证仅配置在后端,前端不保存 API Key。默认使用后端的 generationPrompt,其次 appearancePrompt;可为本次输入自定义 Prompt,详情显示后端最终实际使用的文本。 4. 单图宽高同时留空使用后端默认 2K,自定义时必须同时提供正整数,具体尺寸限制仍由模型校验。当前 Seedream Provider 没有使用 negativePrompt,所以页面不提供无效的负向提示词输入。 5. 单图生成新增历史记录,不删除旧图。无主图时默认勾选“生成成功后设为主图”;已有主图时默认不替换。候选图可以在历史弹窗二次确认后通过 PUT 设为主图,只允许已完成图片。 6. 批量默认跳过已有主图的形态;勾选“已有主图也新增候选图”发送 `force: true`。后端批量 force 只生成新图,**不会替换已有主图**。批量作用于整个项目,不受类型/文字/缺图筛选影响,也不接收单图的尺寸与自定义 Prompt。 7. 图库每 6 秒读取一次真实记录,弹窗只在打开时查询该形态图片。数据库存在 pending / generating 时暂停再次生图;网络错误不自动重试有费用的请求。HTTP 200 中的批量部分失败单独展示。 8. 图片使用持久化后的 `/storage/...` 地址,开发代理已配置。生产环境也必须代理 `/storage/`,不能让 SPA 回退返回 index.html。无效 URL、加载失败、无图和后端生成失败有各自提示;不会自动使用可能过期的 Provider 临时地址。 9. 设为主图后,回到分镜点击“刷新参考图”读取最新绑定。已有视频提示词不会自动重生成,需要按需覆盖。本页面不会调用 `/production/start` 或实际视频生成。 ### 后端限制 目前 `checkpoints` 接口返回完整历史与 state,没有分页或轻量状态接口。长篇剧本会增加轮询流量。下一步建议后端增加: - 持久化 executionId、运行状态、跨请求互斥和幂等键; - 精简的进度接口及按 graph 分页的 checkpoint; - 将 Breakdown 改为 202 异步任务提交,再以轮询或 SSE 读取进度。 前端未模拟这些能力,不调用 `stream-test` 来伪造进度。Storyboard 的单集 `generate-test` 仅在用户明确确认生成后调用。 ## 部署 `pnpm build` 生成 `dist`,使用 Nginx 等静态服务器部署。SPA 深层路由需要回退到 `index.html`。 开发代理只存在于 Vite dev server,`vite preview` 与生产环境需配置独立后端地址或反向代理。 ```nginx server { listen 80; root /var/www/short-drama-agent-front/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { # 保留 /api 前缀,不在 proxy_pass 后追加斜杠。 proxy_pass http://127.0.0.1:3412; proxy_http_version 1.1; proxy_set_header Host $host; proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_buffering off; } location /storage/ { # 参考图由同一后端的静态资源服务提供。 proxy_pass http://127.0.0.1:3412; proxy_set_header Host $host; } } ``` 实际生成时间可能超过代理限制,仍建议将后端改为异步提交协议。部署前应给应用加访问控制;当前后端没有身份认证,不建议直接对公网开放。 ## 本地联调验收 1. 后端启动成功,前端项目列表能显示数据库项目。 2. 新建 3 集剧本,确认获取项目 ID 后进入创作页,完成后能读到正文。 3. 查看角色、世界观与审核,再进入拆解页;预览分组与模块。 4. 启动拆解,查看 checkpoint、主体、形态、每集 Beat / Shot 和绑定。 5. 在可控测试项目中触发故障,确认 retry / resume-shots / resume-storyboard 各自对应正确失败阶段。 6. 进入分镜设计,确认正式镜头 ID 与 Beat 对应;先生成单集 Direction,再生成 VisualState,检查覆盖率和主体状态。 7. 用小型测试项目验证批量补齐、跳过完整集、失败诊断和明确覆盖;确认关闭覆盖后不会重生成完整设计。 8. 给主体形态准备参考图,读取镜头参考图和 GenerationSpec;确认 Prompt 无正文时经确认生成、有正文且不覆盖时复用。不提交实际视频任务。 9. 打开“形态图片”,检查已有主图自动显示;单个形态新增候选图,查看历史、失败原因和实际 Prompt,再设为主图。 10. 在小型测试项目中验证批量补齐与“已有主图也新增候选图”;回到分镜刷新参考图,确认使用新选择的主图。 自动测试使用模拟 API,覆盖既有流程、分镜接口与竞态,以及形态图片自动读取、正式 ID 生图、主图 PUT、费用确认、尺寸校验、候选图选择、批量部分失败和图片加载错误。不等同于真实模型、MySQL 或浏览器视觉联调。请勿用正式项目随意制造失败以测试恢复。