FSD M2 · 任务与拆解
← 总则 | 附录 → | 上游:PRD §4 M2 实现落点:
backend/internal/domain/task/(model/repo/service/service_gen)+cmd/server/task_handlers.go(38 handlers 全集)+cmd/server/task_assembly.go(LLM/模板装配)+cmd/server/routes.go:324-361(挂载)。事实基准=Go 代码;冻结版qmtv/server/index.js:228-366/routes2.js/routes3.js仅差异对拍参照。
M2 任务与拆解
38 端点全量(基础信封见总则 §2.2)。本域是创作主链的起点:剧本 → 拆解(规则/LLM 双引擎)→ 场景/分镜 → 资产提取与生图。计费面集中在拆解(token 制/按次 20)与图片生成(动态价);SSE 仅 split 一类事件;上传面 2 条(slots/assets,20MB)。属主判定全 SQL 谓词,命中失败一律 404 不用 403。分镜双表通路中本域只写 shots(director_shots 归 M10,零触及)。
F2.01 任务列表
对应 PRD:§4 M2 · GET /api/tasks | 状态:实现 | 实现落点:task_handlers.go:51-56、task/repo.go:120-125 | UI:
/dashboard(DashboardView 任务列表)| 场景挂钩:E2E-DASH-01(A1 回归锁:wire=驼峰 shotCount)/E2E-NAV-01
1 功能定义
当前用户任务列表倒序读,每行附镜数(shotCount 驼峰 wire——二十六期 A1 回归根因,e2e 回归锁钉死)。
2 触发条件与前置状态
- 进入拆解台/任务列表刷新 →
GET /api/tasks(Bearer);降级模式 503。
3 输入与校验
无参数。身份取自 JWT claims。
4 处理流程
SELECT t.*, (子查询 COUNT shots) AS shot_count … WHERE t.user_id=? ORDER BY t.created_at DESC → TaskWithCount 数组。宽扫描 t.* 依赖 0002 DDL 收紧(W8b 守卫 integration_test.go:461-484)。
5 输出与结果状态
200 {"ok":true,"data":[TaskWithCount…]}。行 wire:id/user_id/title/status/error/script_type/face_style/min_duration/max_duration/notes/extract_status/assets_data/split_status/style_id/extract_scope/extract_notes/created_at/updated_at(蛇形)+ shotCount(驼峰,model.go:60-63);script_text 指针 nil→JSON null。空列表 data:null(gorp nil slice——冻结恒 [],DIF-29-1)。
6 状态流转
无(纯读)。status 取值域见 F2.03 §6。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 无任务 | 200 data:null(非 [],DIF-29-1) | — |
| DB 错误 | 500 | internal server error |
8 权限与数据规则
- 权限:登录态;
WHERE user_id=?谓词内联。 - 数据规则:倒序
created_at DESC(无次级排序键,同秒行序可能抖动——冻结同构)。
F2.02 剧本拆解·旧版同步
对应 PRD:§4 M2 · POST /api/tasks | 状态:实现 | 实现落点:task_handlers.go:62-82、task/service.go:148-167/301-327 | UI:拆解台旧版提交入口| 场景挂钩:无(V2 为主链,本端点为冻结 legacy 保真)
1 功能定义
同步规则拆解:扣固定 20 积分 → 确定性 preprocess 切分场次/分镜/资产 → 立即返回统计。无 LLM、无异步。
2 触发条件与前置状态
- 拆解台提交(legacy 形态)→
POST /api/tasks(Bearer)。
3 输入与校验
| 字段 | 来源 | 必填 | 校验 |
|---|---|---|---|
| title | JSON body | 否 | 缺省「未命名」,截 60 rune(冻结 INSERT 用「未命名剧本」而台账 desc 用「未命名」——Go 把 desc 缺省同化进 title,DIF-29-13) |
| script_text | JSON body | 是 | TrimSpace=="" → 400 剧本内容不能为空(冻结 index.js:236 同文案) |
4 处理流程
扣费在插任务前,Insert 失败不退(service.go:159-163,冻结同序)。三次 Insert 均 _ = 吞错(service.go:314/318/324)。INSERT 显式写 18 列零值(script_type:''/min_duration:0/split_status:'' 等覆盖 DDL 默认——冻结走 SQLite DEFAULT,DIF-29-5)。
5 输出与结果状态
200 {"ok":true,"data":{"id","sceneCount","shotCount"}}(sceneCount/shotCount 驼峰,task_handlers.go:77-81;冻结 index.js:252 同)。
6 状态流转
tasks.status:插入即 'complete'(无 processing 中间态);split_status:INSERT 显式写 ''(18 列零值,DIF-29-5——非 DDL 默认 'done');本端点不消费该列。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 剧本空白 | 400 | 剧本内容不能为空 |
| 余额 <20 | 402 | 积分不足(零台账) |
| Mock 拆解 | 确定性:场次按「第X场」标记切块(无标记按空行 >10 rune);每场 3 | — |
8 权限与数据规则
- 权限:登录态;创建即本人(user_id 取 claims)。
- 数据规则:写
tasks/shots/assets/shot_images/credit_records五表。
F2.03 剧本拆解·V2 双引擎
对应 PRD:§4 M2 · POST /api/tasks/v2 | 状态:实现 | 实现落点:task_handlers.go:140-160、task/service.go:241-299/330-366 | UI:拆解台主提交(DashboardView)| 场景挂钩:E2E-DASH-01(Mock 同步分支:toast「拆解完成」+镜数>0)
1 功能定义
token 制计费的剧本拆分:LLM 可用走异步拆解(SSE split 事件+站内通知,失败回退规则拆解);LLM 不可用走规则拆解同步返回。计费 ceil(tokens/1000) 上限 1。
2 触发条件与前置状态
- 拆解台提交(主链)→
POST /api/tasks/v2(Bearer)。 - LLM 可用判定
llm.Config.Available()(三元全非空,llm.go:24);配置三级链 active_model_text→model_endpoints→KV llm_*(task_assembly.go:49-59)。
3 输入与校验
| 字段 | 来源 | 必填 | 校验 |
|---|---|---|---|
| title | JSON body | 否 | 缺省「未命名」截 60 |
| script_text | JSON body | 是 | 空 → 400 剧本内容不能为空。唯一校验 |
| script_type / face_style | JSON body | 否 | orDefault 缺省 domestic/asian_chinese——无冻结二值化归一(任意值透传,DIF-29-14) |
| min_duration / max_duration | JSON body | 否 | maxInt(入参,1)/maxInt(入参,3) |
| notes / style_id | JSON body | 否 | style_id 缺省 realfilm |
4 处理流程
runLLMSplit 后台 goroutine defer recover() 兜底(service.go:332);重建三删序 ClearShotImages→DeleteShotsByTask→DeleteRootAssetsByTask(service.go:411-419)。
5 输出与结果状态
V2Result 键序 id, sceneCount(omitempty), shotCount(omitempty), async, engine, cost, tokens_estimated(service.go:243-251):LLM 分支无 sceneCount/shotCount 键(冻结同);Mock 分支 async:false + 两计数。
6 状态流转
tasks.status:processing(仅本端点插入)→complete(Mock/LLM 完成/回退成功/restart/启动恢复)|failed(LLM 双重失败、mark-failed)。tasks.split_status:DDL 默认'done'|'processing'(LLM 可用)→'done'(完成/回退)|'failed'(双重失败)。代码无'idle'/'fallback'写入点(PRD :142 状态机表含此二态——PRD 该行已加注「冻结与 Go 均无写入点」,用户裁决〔三十期〕判定非系统需要不实现:前端零消费〔DashboardView 仅认 processing/failed〕+fallback 系同步内联回退无可观察窗口,勿照抄亦勿实现)。- 服务重启悬挂行:
RecoverProcessingSplits(service.go:444-463)扫split_status='processing'→ fillWithMock 直插不删基座 → 恢复完成,通知「拆解已回退完成」,无 SSE 帧。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 剧本空白 | 400 | 剧本内容不能为空 |
| 余额不足 | 402 | 积分不足 |
| LLM 失败 | 不回 HTTP(异步已 200);回退规则拆解+通知+SSE fallback | — |
| 回退再失败 | status/split_status 双 failed(已扣费不退——冻结同) | — |
| token 估算 | rune 数×1.6(冻结 UTF-16 .length,BMP 外计数不同,DIF-29-15);计费 ceil(冻结 round——credits.TextCost 正确 round 实现存续未用 billing.go:118-131,DIF-29-6) | — |
| 模型名缺配置 | 台账 desc 出现「…tokens / ,-N)」空串形态(冻结缺行回「星芒文本引擎」,DIF-29-7) | — |
8 权限与数据规则
- 权限:登录态;写
tasks/shots/assets/shot_images/credit_records/notifications。 - 数据规则:TryDeduct 带 task_id=id(冻结 routes2.js:158 同);通知两文案冻结逐字(service.go:352-353/359-361)。
F2.04 任务详情读取(:id / full / full2)
对应 PRD:§4 M2 · GET /api/tasks/:id、GET /api/tasks/:id/full、GET /api/tasks/:id/full2 | 状态:实现 | 实现落点:task_handlers.go:84-97/275-297、service_gen.go:300-427 | UI:VideoView/DashboardView 任务详情| 场景挂钩:E2E-NAV-01(读面巡检)
1 功能定义
任务详情三形态:基础(shots+原始 assets)、full(assets 带图 URL+assets_data 缓存)、full2(variants 衍生体+风格名/缩略图+组间尾帧衔接)。
2 触发条件与前置状态
- 详情页/分镜工作台加载 → 三端点(Bearer);任务不存在或非本人 → 404。
3 输入与校验
路径 :id。无 body。
4 处理流程
- :id:
ByID(WHERE id=? AND user_id=?)→ShotsWithSlots(shots ORDER index_no + 槽位按 shot 分组 ORDER slot_no + refAssetCount=COUNT assets)→ 原始 assets(无 image_url,task_handlers.go:95——冻结 index.js:261 同 quirk)。 - full:ByID → ShotsWithSlots → AssetsByTask(LEFT JOIN images 取 image_url)→ assets_data=缓存优先(坏 JSON 宽容 nil)否则从根资产现拼(5 键 item:id/name/description/image_id/image_url——冻结塞原始全列行,DIF-29-16)。
- full2:再加 VariantsByTask(parent_id<>’’)合并进缓存/fallback;
tail_by_group组间尾帧 map(组内最后有图镜 image_id,首镜且 groupNo>1 配prev_tail_image_id,service_gen.go:355-372);style_name=StyleName(19 风格目录缺省「真人电影」)、style_thumb幂等落盘style_<id>.svg(不在目录→null)。
5 输出与结果状态
- :id →
{"task","shots":[ShotView],"assets":[原始行]};shots 空为[](make 初始化)、slots/assets 空null(DIF-29-1)。 - full →
{"task","shots","assets","assets_data"}。 - full2 →
{"task":{…Task,"style_name","style_thumb"},"shots","assets","variants","assets_data","tail_by_group"}——style_name 嵌套在 task 内(前端 curTask.style_name 消费)。
6 状态流转
无(纯读)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 非本人/不存在 | 404 | not found |
| assets_data 缓存坏 JSON | 静默忽略走 fallback 拼装 | — |
| style_id 不在目录 | style_name=「真人电影」、style_thumb=null | — |
8 权限与数据规则
- 权限:登录态 + 任务属主。
- 数据规则:ShotView 含
slots/refAssetCount(驼峰)/prev_tail_image_id(full2 组首镜);variants 键映射 parent_id→item 数组。
F2.05 任务生命周期管理(title/delete/duplicate/mark-failed/restart)
对应 PRD:§4 M2 · PUT /api/tasks/:id/title、DELETE /api/tasks/:id、POST /api/tasks/:id/duplicate、POST /api/tasks/:id/mark-failed、POST /api/tasks/:id/restart | 状态:实现 | 实现落点:task_handlers.go:99-138、task/service.go:168-230/583-587、task/repo.go:146-175/234-248 | UI:任务卡操作菜单| 场景挂钩:无独立 e2e
1 功能定义
任务五个管理动作:改名、删除(三删序+孤儿保留)、复制(含分镜/资产快照)、标记失败、重启清图。
2 触发条件与前置状态
- 任务卡操作 → 对应端点(Bearer);title/delete/duplicate 404 面,mark-failed 无存在性检查。
3 输入与校验
| 端点 | 输入 | 校验 |
|---|---|---|
| PUT /tasks/:id/title | {"title":string} | 截 60 rune;无 404 面(0 行仍 200,冻结同) |
| DELETE /tasks/:id | — | ByID 谓词内联;0 行 → 404 not found |
| POST /tasks/:id/duplicate | — | ByID 404 面 |
| POST /tasks/:id/mark-failed | — | 无校验(0 行也 200,冻结 routes2.js:250 同) |
| POST /tasks/:id/restart | — | ByID 404 面 |
4 处理流程
- title:
UPDATE tasks SET title=?, updated_at=NOW(3) WHERE id=? AND user_id=?。 - delete:三删序 shots→assets→tasks(子查询属主谓词);不删 shot_images/image_histories/images/videos(孤儿保留,repo.go:151 注释冻结 §1A)。
- duplicate:新任务 title=
原题+"-副本"截 60(DIF-29-10),status/script_text 沿源(nil 透传);逐镜复制(image_id/video_id 清空);仅复制源镜存在 slot_no=1 的槽位(service.go:200-204,冻结每镜无条件插——DIF-29-10);assets 全复制但 parent_id 不带(衍生变根资产,冻结 index.js:290-292 同 quirk)。 - mark-failed:
UPDATE tasks SET status='failed';不触 split_status。 - restart:
RestartClears四步——shots.image_id=’’+status=‘idle’、shot_images.image_id=’’、tasks status=‘complete’+extract_status=’’+assets_data=’’、assets.image_id=’’;保留 shots/assets/槽位行(NULL 语义翻译 ‘’,model.go:4-5)。
5 输出与结果状态
五者均 {"ok":true,"data":true};duplicate 另返 {"id":"<新任务>"}。
6 状态流转
- delete:行删除(级联仅 shots/assets 两层)。
- mark-failed:
complete|processing → failed。 - restart:
任意 → complete;shotsdone → idle;extract_status →''。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| title 改他人任务 | 0 行 200 true(无存在性检查) | — |
| delete/mark-failed 非本人 | delete 404;mark-failed 0 行 200 | not found |
| duplicate 长 title | 截 60(冻结不截,DIF-29-10) | — |
8 权限与数据规则
- 权限:登录态;delete/duplicate/restart 谓词内联属主。
- 数据规则:restart 是「清成果不删结构」语义——与 delete(删结构留孤儿)互补。
F2.06 智能资产提取(extract / extract2 / extract-settings)
对应 PRD:§4 M2 · POST /api/tasks/:id/assets/extract、POST /api/tasks/:id/assets/extract2、PUT /api/tasks/:id/extract-settings | 状态:实现 | 实现落点:task_handlers.go:174-208、service_gen.go:34-159 | UI:拆解台资产区「智能提取」| 场景挂钩:无 e2e(人工清单 T2 族)
1 功能定义
对拆解产出的角色/场景/道具资产批量生图:v1 按资产类型模板出图并写 assets_data 缓存;v2 支持 scope 范围+自定义要求+风格前缀;extract-settings 持久化提取范围与要求。
2 触发条件与前置状态
- 资产区按钮 → extract/extract2(Bearer);设置面板保存 → extract-settings。前置:任务已有资产行。
3 输入与校验
| 端点 | 输入 | 校验 |
|---|---|---|
| extract (v1) | 无 body | 资产为空 → 400 该任务没有可提取的资产 |
| extract2 (v2) | 无 body | scope 内(根资产 ∩ extract_scope)为空 → 400 提取范围内没有资产(可在自定义提取设置中调整范围) |
| extract-settings | {"scope":[...],"notes":string|null} | scope 白名单过滤 character/scene/prop 后 join(空数组→’’,冻结同);notes 非 null 才覆盖 |
4 处理流程
- 计费:unit=ImageCost(settings
image_generation默认 10);v1 cost=unit×资产数(含衍生体),desc智能资产提取(N 个资产 × 10,-<cost>);v2 cost 同式,desc智能资产提取 ×N(风格:<风格名>,-<cost>);type=image。 - v2 prompt=「视觉风格:<风格名>。」+AssetPrompt+(ExtractNotes?
\n[生成要求] <notes>)。 - v1 失败 502 文案金额用总额(冻结 v1 用单价——退款金额本身正确,DIF-29-2);v2 失败退全额(冻结 v2 传总额笔误的保真对齐,service_gen.go:121-123 注释)。
5 输出与结果状态
{"extract_status":"done","assets_data":{"characters":[],"scenes":[],"props":[]}};item 键 id/name/description/image_id/image_url/prompt——v2 冻结显式带 variants:[],Go omitempty 键缺失(DIF-29-3)。
6 状态流转
tasks.extract_status:'' → 'done'(成功路径先行置位,失败不回滚——下次重试覆盖);assets_data 缓存同步覆盖。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 无资产 | 400 | 该任务没有可提取的资产 |
| scope 内为空 | 400 | 提取范围内没有资产(可在自定义提取设置中调整范围) |
| 余额不足 | 402 | 积分不足 |
| 单张生成失败 | 退单价 continue + 最终 502 | 图片生成失败:<cause>(<cost> 积分已退回) |
8 权限与数据规则
- 权限:登录态+任务属主;写
images/assets/tasks/credit_records。 - 数据规则:v2 targets=根资产(parent_id=’’)∩scope;衍生体在 v1 中也计费(含衍生体)。
F2.07 AI 重新拆解(resplit)
对应 PRD:§4 M2 · POST /api/tasks/:id/resplit | 状态:实现 | 实现落点:task_handlers.go:514-533、service.go:488-573 | UI:分镜区「AI 重新拆解」| 场景挂钩:无 e2e
1 功能定义
按自然语言指令对已有分镜做密度调整重切:更多/细致/丰富→每场 5 镜、精简/减少/合并→每场 2 镜、缺省 3~4;固定扣 20 积分。
2 触发条件与前置状态
- 分镜区操作 →
POST /api/tasks/:id/resplit(Bearer);前置:任务存在(不要求 complete)。
3 输入与校验
| 字段 | 来源 | 必填 | 校验 |
|---|---|---|---|
| instruction | JSON body | 否 | trim 后密度匹配;无 400 面 |
4 处理流程
ByID → 扣 20(desc AI 重新拆解(<instruction截16>)(-20),空指令 AI 重新拆解(-20),type=preprocess)→ 三删重建(同 F2.03)→ preprocess 密度调参重切 → 重插 shots(每组首镜 independent 余 anchor_prev)+slot1 → SetSplitInstruction(存 trim 值)。
5 输出与结果状态
{"instruction","sceneCount","shotCount"}——instruction 回显的是请求原始值非 trim 值(task_handlers.go:524-532;冻结回显 trim 后值,DIF-29-8)。
6 状态流转
shots 全量重建(id 全新);split_instruction 列更新;status/split_status 不变。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 非本人 | 404 | not found |
| 余额 <20 | 402 | 积分不足 |
| 指令无密度词 | 缺省密度 3~4 镜/场 | — |
8 权限与数据规则
- 权限:登录态+属主;写
shots/shot_images/tasks/credit_records。
F2.08 分镜图批量与续生成(continue / auto-generate / cancel / storyboard-batch)
对应 PRD:§4 M2 · POST /api/tasks/:id/continue、POST /api/tasks/:id/scenes/auto-generate、POST /api/tasks/:id/scenes/auto-generate/cancel、POST /api/tasks/:id/storyboard-batch | 状态:实现 | 实现落点:task_handlers.go:299-334/346-357/535-553、service.go:590-703、service_gen.go:634-695 | UI:分镜区批量操作| 场景挂钩:无 e2e(人工清单 T2/T3 族)
1 功能定义
四个批量/续生成入口:continue 补缺图镜、auto-generate 带连续性提示词串联生成、cancel 纯 stub 停止提示、storyboard-batch 按模式(全部/补缺/选中)批量生图。
2 触发条件与前置状态
- 分镜区批量按钮 → 对应端点(Bearer)。
3 输入与校验
| 端点 | 输入 | 校验 |
|---|---|---|
| continue | 无 body | 404 面;无缺图镜时 cost=0 不扣(len>0 才 TryDeduct) |
| auto-generate | 无 body | 404 面;len=0 也 TryDeduct(0) 落 0 元台账行(冻结 util.js:86-91 同 quirk) |
| auto-generate/cancel | 无 body | 纯 stub 无校验 |
| storyboard-batch | {"mode":"missing"|"selected"|"all","shot_ids":[]} | mode 空→missing;空列表 → 400 没有缺失的图片(missing)/未选择任何分镜(selected);all+ids 取交集 |
4 处理流程
- continue:pending=缺图镜(image_id=’’/NULL)→ cost=unit×len(desc
继续生成未完成分镜 ×N(-<cost>))→ 逐镜 Generate(“16:9”,“分镜<index_no>”) → settleShotImage(InsertImage kind=storyboard+SetShotImage done+slot1 回填+InsertHistory)。 - auto-generate:list=pending 空则全量镜;prompt 追加连续性段——非首镜
\n[连续性] 与上一镜头(<prevURL||前帧>)保持人物/光影/机位连续。、首镜\n[起始镜头] 由智能体判断开场构图。;desc故事板全自动串联生成 ×N(-<cost>)。 - cancel:
{"cancelled":true,"note":"已停止后续镜头的排队(已产生的费用不退)"},不写任何表。 - storyboard-batch:desc
批量生图(全部|仅补缺失|选中 ×N,-<cost>);连续性措辞与 auto-generate 不同源(「与上一镜(<prevURL||前帧>)保持一致。」/首镜「智能体判断开场构图」);失败退单价(冻结传总额系笔误,Go 统一单价——PRD v1.3 已登记,service_gen.go:632-633)。
settle 失败(DB 错)时该张已生成不退费、仅 failed++(service.go:616-619/689-694,冻结同)。
5 输出与结果状态
continue/auto-generate/storyboard-batch → {"generated":N,"failed":N};cancel → 上述 stub 体。
6 状态流转
成功镜:shots.status idle → done(SetShotImage);失败镜 status 不动(仅计数)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 非本人 | 404 | not found |
| 余额不足 | 402 | 积分不足 |
| selected 空数组 | 400 | 未选择任何分镜 |
| missing 无缺图 | 400 | 没有缺失的图片 |
| 单张失败 | 退单价 continue | 502 图片生成失败:… |
8 权限与数据规则
- 权限:登录态+属主;写
images/shots/shot_images/image_histories/credit_records。 - 数据规则:三批量端点计价均为动态 unit(settings 可调)——与 F2.09 的静态价不同源。
F2.09 单镜故事板生成(images/storyboard/generate)
对应 PRD:§4 M2 · POST /api/images/storyboard/generate(v1.3 W10 归组 M2——物理在冻结 index.js 生成段)| 状态:实现 | 实现落点:task_handlers.go:420-441、service_gen.go:581-623 | UI:分镜卡「生成」/「2K 预览」| 场景挂钩:间接 E2E-NAV-01
1 功能定义
单镜故事板图/2K 预览生成:静态价(图 10/预览 15,无 settings 键),可选关联 shot 回填。
2 触发条件与前置状态
- 分镜卡生成按钮 →
POST /api/images/storyboard/generate(Bearer)。
3 输入与校验
| 字段 | 来源 | 必填 | 校验 |
|---|---|---|---|
| shot_id | JSON body | 否 | 非空→ShotByID+属主校验(err→404 shot not found);空则跳过校验也可用(冻结 index.js:345 同) |
| prompt | JSON body | 否 | 缺省取 shot.ImagePrompt |
| ratio | JSON body | 否 | — |
| preview | JSON body | 否 | true→2K 形态 |
4 处理流程
计费 cost=10(COSTS.image)/15(COSTS.image_2k 恒定无 settings 键,billing.go:72-73);kind=‘storyboard’/‘preview_2k’;desc 分镜图生成(-10)/2K预览(-15)(冻结「2K预览生成(-15)」缺「生成」二字,DIF-29-9);task_id=shot 有则其 task 否则 ‘’。Generate → 失败退同额+502;成功 InsertImage(prompt=请求原值非解析值)→ sh != nil && !preview 才回填 shots/slot1/histories(linkShotImage 不重复插 images——W3 冒烟双插主键冲突修,service_gen.go:615-621 注释)。
5 输出与结果状态
{"id","url"}。
6 状态流转
关联 shot 非预览时:shots.status → done、slot1 回填、histories +1。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| shot_id 给了但不存在/非本人 | 404 | shot not found |
| 余额不足 | 402 | 积分不足 |
| 生成失败 | 退款+502 | 分镜图生成(-10)失败自动退回族文案 |
8 权限与数据规则
- 权限:登录态;shot 关联时校验属主。
- 数据规则:静态价与 F2.08 动态价刻意不同源(billing.go:72-73 注释);Mock label=desc 全串(冻结 label「分镜图」/「2K预览」——影响 Mock SVG 文件名,DIF-29-9)。
F2.10 场景分组管理(scenes2 / rename / delete)
对应 PRD:§4 M2 · GET /api/tasks/:id/scenes2、PUT /api/tasks/:id/scenes/:groupNo、DELETE /api/tasks/:id/scenes/:groupNo | 状态:实现 | 实现落点:task_handlers.go:472-512、repo.go:356-371 | UI:分镜区场景分组栏| 场景挂钩:无 e2e
1 功能定义
场景分组聚合读与改名/删除:scenes2 按 group_no 聚合统计,rename/delete 按 group_no 批量作用。
2 触发条件与前置状态
- 分镜区场景栏 → 三端点(Bearer)。
3 输入与校验
| 端点 | 输入 | 校验 |
|---|---|---|
| GET scenes2 | — | ByID 404 面 |
| PUT scenes/:groupNo | {"scene_title":string} 截 60 | groupNo 非数字→归 0(task_handlers.go:493-494;冻结 NaN 绑定 TypeError 500——Go 200 零行,DIF-29-11) |
| DELETE scenes/:groupNo | — | 同上;0 行也 200 |
4 处理流程
- scenes2:
SELECT group_no, MIN(scene_title), COUNT(*) shot_count, MIN(index_no) first_index … GROUP BY group_no ORDER BY group_no(MIN(scene_title) 字典序最小而非首镜标题——冻结同 SQL quirk 保真)。 - rename:
UPDATE shots SET scene_title=? WHERE task_id=? AND group_no=?。 - delete:
DELETE FROM shots WHERE task_id=? AND group_no=?。
5 输出与结果状态
scenes2 → [{"group_no","scene_title","shot_count","first_index"}](全蛇形,空→null DIF-29-1);rename/delete → data true。
6 状态流转
delete 组删除后 shots 行消失(槽位/histories 孤儿保留)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| groupNo=abc | 折 0 匹配零行 200(DIF-29-11) | — |
| 组不存在 | rename/delete 0 行 200 | — |
8 权限与数据规则
- 权限:登录态+属主(task_id 谓词)。
F2.11 分镜编辑与槽位(shots / slots / shot-images / histories / shots2)
对应 PRD:§4 M2 · PUT /api/shots/:id、DELETE /api/shots/:id、POST /api/shots/:id/slots、DELETE /api/shot-images/:slotId、GET /api/shots/:id/histories、PUT /api/shots2/:id/params | 状态:实现 | 实现落点:task_handlers.go:359-470、service.go:492-501/570-578、service_gen.go:504-559、repo.go:322-354/408-437/465-470 | UI:分镜卡编辑器| 场景挂钩:无独立 e2e
1 功能定义
单镜六动作:三提示词编辑、删除、参考槽位添加(上传或生图)、槽位删除(slot1 保护)、历史读、分辨率参数。
2 触发条件与前置状态
- 分镜卡操作 → 六端点(Bearer);slots 上传限额 20MB(up20)。
3 输入与校验
| 端点 | 输入 | 校验 |
|---|---|---|
| PUT /shots/:id | image_prompt/video_prompt/transition_mode 均可空指针 | null=跳过该键(COALESCE 部分更新契约 §6);属主两步查(ShotByID+task 属主)失败 404 shot not found |
| DELETE /shots/:id | — | 404 面;不删槽位/histories/images |
| POST /shots/:id/slots | multipart file + form prompt/kind/ratio/label | 无文件≠错误——无文件走生图分支扣 10 |
| DELETE /shot-images/:slotId | — | slot_no=1 → 400 故事板槽位不可删除;其余 404 面 |
| GET /shots/:id/histories | — | 404 面 |
| PUT /shots2/:id/params | {"resolution":string} | 非 720p/1080p 静默忽略仍 200(冻结 routes3.js:341-343 同) |
4 处理流程
- slots 双路:有文件→images.Upload 零费;无文件→静态价 10(desc
镜头图片生成(-10),task_id=’’)→Generate→失败退+502。成功:slotNo=MAX(slot_no)+1、InsertImage(kind=orDefault(kind,“anchor”))、InsertShotImages(label=orDefault(label,“参考图”));生成路另 SetShotImage(done)+InsertHistory( _ =吞错)。 - shotOwned(service.go:492-501):ShotByID err→ErrShotNotFound;task 属主 err→同——两步均 404
shot not found。
5 输出与结果状态
- slots →
{"slot_no":int,"image_id","url"};histories →[{"id","shot_id","image_id","prompt","created_at","file_path"}](空→null DIF-29-1);其余 data true。
6 状态流转
- 生图路 slots/SetShotImage:shots.status → done。
- params:resolution 列更新(白名单内才生效)。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| shot 非本人 | 404 | shot not found(六端点同哨兵) |
| 删 slot1 | 400 | 故事板槽位不可删除 |
| slots 无文件且余额不足 | 402 | 积分不足 |
| slots 生图失败 | 退单价 | 502 |
| resolution=480p | 静默忽略 200 | — |
8 权限与数据规则
- 权限:登录态+shot→task 属主链。
- 数据规则:本域写 images 表为跨域共表直插(10 列形态,repo.go:551-556,主归属 generation——总则 §1 术语);histories 行 id 前缀
si_(冻结ih_;ids.PrefixImageHistory=“ih_” 仅 director 域消费(repo.go:319 InsertImageHistory),前缀双轨并存——DIF-29-4,99-附录:57)。
F2.12 资产图编辑与衍生(assets upload / regenerate / derive)
对应 PRD:§4 M2 · POST /api/assets/:id/image/upload、POST /api/assets/:id/regenerate、POST /api/assets/:id/derive | 状态:实现 | 实现落点:task_handlers.go:213-272、service_gen.go:162-276 | UI:资产卡操作| 场景挂钩:无 e2e
1 功能定义
资产图三动作:手动上传替换(同步 assets_data 缓存)、局部修改生图、衍生变体(新建子资产行)。
2 触发条件与前置状态
- 资产卡操作 → 三端点(Bearer);upload 限额 20MB。
3 输入与校验
| 端点 | 输入 | 校验 |
|---|---|---|
| upload | multipart file | 无文件 → 400 缺少文件(冻结 routes2.js:217 同文案);资产 404 not found |
| regenerate | {"instructions":string} | trim 空 → 400 请填写局部修改说明 |
| derive | {"variant_desc":string} | trim 空 → 400 请描述变体(如:换一套服装 / 中老年形象) |
4 处理流程
- upload:AssetByID(JOIN tasks 属主)→ Upload(FixFilename)→ InsertImage(kind=‘asset’) → UpdateAssetImage → 同步 assets_data 缓存(读改写 JSON 三组逐项改 image_id/image_url,坏 JSON 静默跳过)。
- regenerate:扣 unit(desc
资产图局部编辑:<名>(-<unit>))→ prompt=AssetPrompt+\n[局部修改] <ins>(其余部分保持原图不变)→ Generate → 失败退+502 → InsertImage(kind=‘asset_edit’) → UpdateAssetImage(不写缓存,冻结同)。 - derive:扣 unit(desc
角色衍生:<名>·<variant截12>(-<unit>))→ 先插子资产行(name=<名>_衍生<N>,parent_id=源)→ prompt=AssetPrompt+\n[衍生变体] <v>。保持角色五官与身份一致,仅按描述改变形态。视觉风格:<风格名>。→ Generate → 失败退+502(子资产行残留无回滚——冻结同序 routes3.js:170-172)→ InsertImage(kind=‘asset_variant’) → UpdateAssetImage(子)。
5 输出与结果状态
upload/regenerate → {"image_id","url"};derive → {"asset_id","image_id","url","name"}。
6 状态流转
- derive:assets 树新增子节点(parent_id 链)。
- upload:assets.image_id 替换+缓存同步;regenerate 仅替换 image_id。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 缺文件 | 400 | 缺少文件 |
| 说明/变体描述空 | 400 | 上述两句 |
| 余额不足 | 402 | 积分不足 |
| 生成失败 | 退单价 | 502 图片生成失败:… |
| derive 生成失败 | 退款但子资产行残留 | — |
8 权限与数据规则
- 权限:登录态+资产→任务属主链(AssetByID JOIN tasks)。
- 数据规则:FixFilename 处理中文文件名(总则坑表);缓存同步只覆盖命中的 name 项。
F2.13 任务台账与资产库(credit-records / library-apply / asset-library)
对应 PRD:§4 M2 · GET /api/tasks/:id/credit-records、POST /api/tasks/:id/library-apply/:libId、GET/POST /api/asset-library、POST /api/asset-library/:id/share、DELETE /api/asset-library/:id | 状态:实现 | 实现落点:task_handlers.go:555-631、service_gen.go:705-764、repo.go:569-605 | UI:任务详情台账页/素材库页| 场景挂钩:E2E-CRD-01(台账键形同源 credits.Record)
1 功能定义
任务维度台账对账读(LIMIT 200+汇总)、资产库条目应用到任务、素材库个人/组织共享 CRUD。
2 触发条件与前置状态
- 任务详情台账 tab → credit-records;素材库页 → asset-library 四动作;「应用到任务」→ library-apply。
3 输入与校验
| 端点 | 输入 | 校验 |
|---|---|---|
| GET credit-records | — | 任务 404 not found |
| POST library-apply/:libId | — | 任务 → 404 task not found;库条目 → 404 asset not found(双哨兵分叉) |
| GET /asset-library | — | orgIDOf 查失败按无组织 |
| POST /asset-library | {"name","type","description","image_id","asset_id"} | trim(name)=="" → 400 名称必填;asset_id 查不到静默忽略;type 缺省 character |
| POST …/:id/share | — | 非本人 404 not found;无组织 → 400 请先加入企业组织 |
| DELETE …/:id | — | 不存在也 200(service_gen.go:743-746 注释) |
4 处理流程
- credit-records:credits.RecordsByTask(LIMIT 200 ORDER created_at DESC,id DESC)+ 汇总(负额和+by_type 分组,service.go:79-93)。
- library-apply:双 404 面 → InsertAssets(desc=lib.Description+
(来自资产库),image_id 沿库)。 - asset-library 列表:
(l.user_id=? OR (org_shared=1 AND l.user_id IN (SELECT id FROM users WHERE org_id=?)))ORDER created_at DESC LIMIT 200 JOIN users 取 owner_email(共享子查询走 users.org_id 单组织模型——冻结 org_members 双向 JOIN,语义等价登记 repo.go:574)。 - share:翻转 org_shared → 返回
{"org_shared":0|1}(整数)。
5 输出与结果状态
- credit-records →
{"list":[Record…],"summary":{"total_spent","by_type"},"task_title"};Record wireid,user_id,task_id,amount,balance_after,type,description,created_at(task_id NULL 行 wire “")。 - asset-library →
[LibraryView…](+owner_email,空→null DIF-29-1);add →{"id"};apply →{"asset_id"}。
6 状态流转
- library-apply:assets 表新增行(任务下)。
- share:org_shared 0⇄1 翻转。
7 边界与异常
| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 任务不存在 | credit-records 404 not found;apply 404 task not found | — |
| 库条目不存在 | apply 404 asset not found | — |
| 名称空 | 400 | 名称必填 |
| 无组织 share | 400 | 请先加入企业组织 |
| 删不存在条目 | 200 true | — |
8 权限与数据规则
- 权限:登录态;asset-library 写面全部
user_id=?谓词(share 的组织可见性靠 org_shared 列+列表子查询)。 - 数据规则:台账读面与 M1 F1.06 全局流水同源 credits 域;LIMIT 200。
M2 端点覆盖对账
| # | 端点 | 功能节 |
|---|---|---|
| 1 | GET /api/tasks | F2.01 |
| 2 | POST /api/tasks | F2.02 |
| 3 | POST /api/tasks/v2 | F2.03 |
| 4 | GET /api/tasks/:id | F2.04 |
| 5 | PUT /api/tasks/:id/title | F2.05 |
| 6 | DELETE /api/tasks/:id | F2.05 |
| 7 | POST /api/tasks/:id/duplicate | F2.05 |
| 8 | POST /api/tasks/:id/assets/extract | F2.06 |
| 9 | POST /api/tasks/:id/assets/extract2 | F2.06 |
| 10 | PUT /api/tasks/:id/extract-settings | F2.06 |
| 11 | GET /api/tasks/:id/full | F2.04 |
| 12 | GET /api/tasks/:id/full2 | F2.04 |
| 13 | POST /api/tasks/:id/mark-failed | F2.05 |
| 14 | POST /api/tasks/:id/restart | F2.05 |
| 15 | POST /api/tasks/:id/continue | F2.08 |
| 16 | POST /api/tasks/:id/scenes/auto-generate | F2.08 |
| 17 | POST /api/tasks/:id/scenes/auto-generate/cancel | F2.08 |
| 18 | GET /api/tasks/:id/scenes2 | F2.10 |
| 19 | PUT /api/tasks/:id/scenes/:groupNo | F2.10 |
| 20 | DELETE /api/tasks/:id/scenes/:groupNo | F2.10 |
| 21 | POST /api/tasks/:id/resplit | F2.07 |
| 22 | POST /api/tasks/:id/storyboard-batch | F2.08 |
| 23 | GET /api/tasks/:id/credit-records | F2.13 |
| 24 | POST /api/tasks/:id/library-apply/:libId | F2.13 |
| 25 | PUT /api/shots/:id | F2.11 |
| 26 | DELETE /api/shots/:id | F2.11 |
| 27 | POST /api/shots/:id/slots | F2.11 |
| 28 | DELETE /api/shot-images/:slotId | F2.11 |
| 29 | GET /api/shots/:id/histories | F2.11 |
| 30 | PUT /api/shots2/:id/params | F2.11 |
| 31 | POST /api/assets/:id/image/upload | F2.12 |
| 32 | POST /api/assets/:id/regenerate | F2.12 |
| 33 | POST /api/assets/:id/derive | F2.12 |
| 34 | POST /api/images/storyboard/generate | F2.09 |
| 35 | GET /api/asset-library | F2.13 |
| 36 | POST /api/asset-library | F2.13 |
| 37 | POST /api/asset-library/:id/share | F2.13 |
| 38 | DELETE /api/asset-library/:id | F2.13 |
38/38 对账 routes.go:324-361 与 PRD §4 M2。注:/tasks/:id/export/* 6 条共享前缀归 M4 分册(echo radix 静态段优先语义见 m04);director_shots 双表通路另一半归 M10。
DIF 新发现(本期取证,PRD §3/§4 未登记——入 99-附录 B):DIF-29-1 空列表 wire null vs 冻结 [](六读面)/DIF-29-2 extract v1 失败 502 文案金额用总额(冻结单价,退款额正确)/DIF-29-3 extract2 item 缺 variants 键/DIF-29-4 image_histories 前缀 si_ vs 冻结 ih_(ids.go:60 PrefixImageHistory 定义未用)/DIF-29-5 legacy/duplicate INSERT 18 列零值覆盖 DDL 默认/DIF-29-6 V2 计费 ceil vs 冻结 round(TextCost 正确实现并存未用)/DIF-29-7 V2 台账模型名可空串/DIF-29-8 resplit 回显原始 instruction/DIF-29-9 preview 台账 desc 缺「生成」+Mock label 偏差/DIF-29-10 duplicate 截 60+槽位条件复制/DIF-29-11 groupNo 非数字冻结 500 vs Go 200/DIF-29-12 bind 失败文案统一化/DIF-29-13 legacy 缺省标题「未命名」vs 冻结「未命名剧本」/DIF-29-14 V2 script_type/face_style 无二值化/DIF-29-15 token 估算 rune vs UTF-16/DIF-29-16 full fallback 拼装 5 键收窄。