Compare commits

...

80 Commits

Author SHA1 Message Date
XiaoXia Bot 852268f069 debug(ci): v2测试 - 复制ci-build完整结构
Debug Build Schedule / Test Matrix A (push) Successful in 0s
Debug Build Schedule / Test Simple Job (push) Successful in 0s
Debug Build Schedule / Test Matrix B (push) Successful in 0s
Debug Build Schedule / Test If Condition (push) Failing after 0s
Debug Build Schedule v2 - Copy CI Build Structure / Build Staging API Image (push) Failing after 1s
Debug Build Schedule v2 - Copy CI Build Structure / Build Staging Web Image (push) Failing after 0s
Debug Build Schedule v2 - Copy CI Build Structure / Build Staging Worker Image (push) Failing after 0s
Debug Build Schedule v2 - Copy CI Build Structure / Build Production API Image (push) Failing after 0s
Debug Build Schedule v2 - Copy CI Build Structure / Deploy Staging (push) Failing after 0s
2026-07-15 20:53:38 +08:00
XiaoXia Bot f319be75c5 debug(ci): 添加调试workflow定位构建调度问题
Debug Build Schedule / Test Simple Job (push) Successful in 0s
Debug Build Schedule / Test Matrix A (push) Successful in 0s
Debug Build Schedule / Test Matrix B (push) Successful in 0s
Debug Build Schedule / Test If Condition (push) Failing after 0s
2026-07-15 20:51:40 +08:00
XiaoXia Bot 2526f18890 test(ci): 测试runs-on字符串格式
CI/CD Pipeline / Frontend Lint (push) Successful in 55s
CI/CD Pipeline / Unit Tests (push) Successful in 1m44s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m3s
CI/CD Pipeline / Integration Tests (push) Successful in 1m23s
CI Build & Deploy Pipeline / Build Staging API Image (push) Blocked by required conditions
CI Build & Deploy Pipeline / Build Staging Web Image (push) Blocked by required conditions
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Blocked by required conditions
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Blocked by required conditions
CI Build & Deploy Pipeline / Staging E2E Tests (push) Blocked by required conditions
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Blocked by required conditions
CI Build & Deploy Pipeline / Build Production API Image (push) Blocked by required conditions
CI Build & Deploy Pipeline / Build Production Web Image (push) Blocked by required conditions
CI Build & Deploy Pipeline / Build Production Worker Image (push) Blocked by required conditions
CI Build & Deploy Pipeline / Deploy Production (push) Blocked by required conditions
CI Build & Deploy Pipeline / Production Browser E2E (push) Blocked by required conditions
2026-07-15 20:43:00 +08:00
XiaoXia Bot e24636d2dd fix(ci): 将build-farm标签改为build-only以匹配新Runner
CI/CD Pipeline / Frontend Lint (push) Successful in 48s
CI/CD Pipeline / Unit Tests (push) Successful in 1m43s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m6s
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been cancelled
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been cancelled
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been cancelled
CI Build & Deploy Pipeline / Build Production API Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been cancelled
CI Build & Deploy Pipeline / Deploy Production (push) Has been cancelled
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
2026-07-15 20:39:35 +08:00
XiaoXia Bot ad37a1420f fix(ci): 修复ci-build.yml中checkout脚本的YAML解析问题
CI/CD Pipeline / Frontend Lint (push) Successful in 50s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m48s
CI/CD Pipeline / Unit Tests (push) Successful in 1m52s
CI/CD Pipeline / Integration Tests (push) Successful in 1m20s
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been cancelled
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been cancelled
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been cancelled
CI Build & Deploy Pipeline / Build Production API Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been cancelled
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been cancelled
CI Build & Deploy Pipeline / Deploy Production (push) Has been cancelled
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been cancelled
2026-07-15 20:29:47 +08:00
XiaoXia Bot 86663150ae fix(ci): 修复ci-build.yml中checkout脚本的YAML解析问题
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 58s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 51s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m58s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m50s
CI/CD Pipeline / Unit Tests (push) Successful in 2m5s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m5s
CI/CD Pipeline / Integration Tests (push) Successful in 1m26s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m29s
将checkout步骤的run字段从单行双引号字符串改为多行块标量,
避免act_runner对长字符串的解析bug导致Python语法错误
2026-07-15 20:25:40 +08:00
XiaoXia Bot ecf457ecdb Merge PR #374: 修复ci-build.yml checkout步骤Python语法错误
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m46s
CI/CD Pipeline / Unit Tests (push) Successful in 1m54s
CI/CD Pipeline / Frontend Lint (push) Successful in 51s
CI/CD Pipeline / Integration Tests (push) Successful in 1m39s
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (push) Failing after 30m1s
CI Build & Deploy Pipeline / Build Staging Web Image (push) Successful in 4m47s
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Successful in 38m51s
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
2026-07-15 20:16:21 +08:00
XiaoXia Bot e5a96db948 fix(ci): 修复ci-build.yml checkout步骤Python语法错误
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 47s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 49s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m17s
CI/CD Pipeline / Unit Tests (push) Successful in 2m24s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m20s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m38s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m34s
CI/CD Pipeline / Integration Tests (push) Successful in 1m34s
2026-07-15 20:16:08 +08:00
XiaoXia Bot f499f4a0e7 Merge PR #373: fix(ci): 修复ci-build.yml的YAML语法错误
CI Build & Deploy Pipeline / Build Staging API Image (push) Failing after 0s
CI Build & Deploy Pipeline / Build Staging Web Image (push) Failing after 0s
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Failing after 0s
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 36s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m47s
CI/CD Pipeline / Unit Tests (push) Successful in 1m50s
CI/CD Pipeline / Integration Tests (push) Successful in 1m14s
2026-07-15 20:02:10 +08:00
XiaoXia Bot 6978ec66ed fix(ci): 修复ci-build.yml的YAML转义错误(buildx引号+deploy单引号)
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 36s
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 47s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m50s
CI/CD Pipeline / Unit Tests (push) Successful in 2m4s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m45s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m9s
CI/CD Pipeline / Integration Tests (push) Successful in 1m26s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m20s
2026-07-15 20:01:00 +08:00
CI Bot 9c1bcd93d2 fix: 生成失败时进度弹窗不显示 — Modal open 条件补上 genError
CI/CD Pipeline / Frontend Lint (push) Successful in 38s
CI/CD Pipeline / Unit Tests (push) Successful in 1m39s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m9s
CI/CD Pipeline / Integration Tests (push) Successful in 1m15s
根因:Modal open={generating || generated},失败时两者都 false,
弹窗关闭导致 genError 错误信息不可见。
修复:open={generating || generated || !!genError},
title 同步区分失败/完成/进行中三种状态。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 19:57:55 +08:00
XiaoXia Bot 7748604e76 Merge PR #372: feat(ci): 拆分构建workflow + buildx动态命名
CI/CD Pipeline / Frontend Lint (push) Successful in 44s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m33s
CI/CD Pipeline / Unit Tests (push) Successful in 1m39s
CI/CD Pipeline / Integration Tests (push) Successful in 1m12s
2026-07-15 19:48:46 +08:00
XiaoXia Bot 5471ed473e feat(ci): 拆分构建workflow + buildx动态命名(解决cancel-in-progress踩踏)
CI/CD Pipeline / Frontend Lint (push) Successful in 46s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 44s
CI/CD Pipeline / Unit Tests (push) Successful in 1m55s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m12s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m24s
CI/CD Pipeline / Integration Tests (push) Successful in 1m17s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m17s
- ci-cd.yml:仅保留4个门禁(validate/unit/integration/frontend-lint),cancel-in-progress: true
- ci-build.yml:构建/部署/E2E,cancel-in-progress: false,避免频繁推送取消构建
- buildx builder动态命名:ci-builder--,避免并发job冲突
- 已同步阿里云ACR配置
2026-07-15 19:40:52 +08:00
CI Bot 488928f6eb ci: 触发 staging 部署(Docker registry 已修复)
CI/CD Pipeline / Frontend Lint (push) Successful in 40s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m37s
CI/CD Pipeline / Unit Tests (push) Successful in 1m45s
CI/CD Pipeline / Integration Tests (push) Successful in 1m16s
CI/CD Pipeline / Build Staging API Image (push) Successful in 4m38s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 23m57s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 34m25s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 51s
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
2026-07-15 18:58:07 +08:00
CI Bot 81779c4e1f feat(edit-plans): 列表页新增 result_count 视频数列
CI/CD Pipeline / Frontend Lint (push) Successful in 36s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
- EditPlan interface 新增 result_count: number 字段
- EditPlans.tsx 表格在时长列后新增「视频数」列
- 居中展示,有值显示数字,无值显示 —

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 18:44:21 +08:00
CI Bot 833ea8e9d8 style: prettier 格式化 GenerationHistoryModal
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 36s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m27s
CI/CD Pipeline / Unit Tests (push) Successful in 1m36s
CI/CD Pipeline / Integration Tests (push) Successful in 1m13s
CI/CD Pipeline / Build Staging API Image (push) Successful in 6m30s
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
2026-07-15 18:37:22 +08:00
CI Bot 018358cbb1 fix: EditPlanResponse暴露result_count字段(列表/详情接口)
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
2026-07-15 18:36:40 +08:00
CI Bot ba288f2e8b fix: EditPlanGeneration 类型对齐后端 + 生成历史弹窗字段修复
CI/CD Pipeline / Frontend Lint (push) Failing after 31s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
- EditPlanGeneration interface 改为匹配实际 API 响应(id 即 task_id)
- 补充 result_count/progress/error_message/logs 等字段
- GenerationHistoryModal 改用 gen.id 替代不存在的 gen.generation_task_id
- 彻底修复 P0 崩溃:generation_task_id undefined → slice 报错

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 18:35:19 +08:00
CI Bot 86a868acfb fix: 修复生成历史弹窗 generation_task_id 为 null 时页面崩溃
CI/CD Pipeline / Frontend Lint (push) Successful in 36s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m30s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 1s
CI/CD Pipeline / Unit Tests (push) Successful in 1m36s
CI/CD Pipeline / Build Staging API Image (push) Failing after 9s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 11s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m15s
- GenerationHistoryModal 中 gen.generation_task_id.slice() 未做空值保护
- 当后端返回的 generation_task_id 为 undefined 时直接崩溃
- 添加空值检查,为 null 时显示 '—'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 18:23:24 +08:00
XiaoXia Bot c18146287e feat(ci): 构建切换到阿里云ACR,解决registry并发瓶颈
CI/CD Pipeline / Frontend Lint (push) Successful in 40s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m37s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 1s
CI/CD Pipeline / Unit Tests (push) Successful in 1m43s
CI/CD Pipeline / Build Staging API Image (push) Failing after 9s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 9s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Has been cancelled
2026-07-15 18:21:05 +08:00
XiaoXia Bot e1b6ccaf0a fix(ci): 修复check_migration_safety.py的import排序(isort I001)
CI/CD Pipeline / Frontend Lint (push) Successful in 35s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 1m35s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m12s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m8s
CI/CD Pipeline / Integration Tests (push) Successful in 1m15s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
2026-07-15 18:16:06 +08:00
xiaoxia ab5d3bd251 chore(ci): 恢复cancel-in-progress=true
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 43s
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 47s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 1m46s
CI/CD Pipeline / Integration Tests (push) Successful in 1m26s
2026-07-15 17:28:58 +08:00
CI Bot 13883511f2 chore(ci): 删除误提交的bak文件
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 1m16s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 1m55s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m22s
CI/CD Pipeline / Integration Tests (push) Successful in 1m34s
2026-07-15 16:59:37 +08:00
CI Bot 8ebe970615 fix(ci): 重构migration增量检查 — 改用Gitea API替代git diff,彻底解决CI卡住问题
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
- 移除 Prepare git for migration diff step(git init+fetch+diff方案在host runner上不稳定)
- check_migration_safety.py 改用 Gitea Contents API 对比 develop 分支迁移文件列表
- 轻量可靠:1-2秒完成,不依赖git,不会卡住
- API失败时自动降级为全量检查
2026-07-15 16:59:00 +08:00
CI Bot cebb33c2e2 fix(ci): 彻底修复migration diff step卡住问题 — 用后台进程+轮询替代timeout命令
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
- Gitea Actions v0.2.6不支持step级continue-on-error和timeout-minutes
- timeout命令在host runner环境下行为不可靠(可能不生效)
- 改用后台进程+30秒轮询超时,确保无论fetch成功/失败/卡住都在30秒内返回
- set +e + exit 0确保step永远不会失败,真正做到准备工作不阻断主流程
2026-07-15 16:21:27 +08:00
xiaoxia 8ffc22f348 chore(ci): 临时关闭cancel-in-progress,跑一轮完整CI验证构建修复
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
2026-07-15 16:17:46 +08:00
xiaoxia c99f3f75ad fix: 硬切场景改用concat filter替代xfade,提升多素材性能与稳定性 (#350)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m47s
CI/CD Pipeline / Unit Tests (push) Successful in 1m47s
CI/CD Pipeline / Integration Tests (push) Successful in 1m10s
CI/CD Pipeline / Frontend Lint (push) Successful in 44s
CI/CD Pipeline / Build Staging API Image (push) Failing after 30m1s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 30m1s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 40m1s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-07-15 16:16:54 +08:00
CI Bot 0382c4e697 fix(ci): migration diff step失败不阻断Validate — 加continue-on-error + 2分钟超时
CI/CD Pipeline / Unit Tests (push) Successful in 1m53s
CI/CD Pipeline / Frontend Lint (push) Successful in 42s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m24s
CI/CD Pipeline / Integration Tests (push) Successful in 1m36s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
- Prepare git for migration diff只是准备工作,失败应降级为全量检查
- 加timeout-minutes:2防止git fetch卡住拖慢CI
- continue-on-error:true确保后续migration safety检查正常运行
2026-07-15 16:10:49 +08:00
CI Bot 45e7cfe7c9 fix(ci): 修复migration diff步骤 - 分支名从main改为develop + 加30s超时
CI/CD Pipeline / Frontend Lint (push) Successful in 39s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m3s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m42s
CI/CD Pipeline / Integration Tests (push) Successful in 1m19s
CI/CD Pipeline / Build Staging API Image (push) Successful in 14m26s
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
- Prepare git for migration diff 步骤写死了 fetch main 分支
- 项目实际开发分支是 develop,导致 fetch 卡住(之前因 URL 拼错立即失败未暴露)
- 改为 fetch develop + 30秒超时保护
- Check migration safety 也同步改 origin/develop
2026-07-15 15:41:47 +08:00
XiaoXia Bot 370c3923ae fix: migration diff步骤真正修复 — 传入GITHUB_SERVER_URL
CI/CD Pipeline / Frontend Lint (push) Successful in 34s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m33s
CI/CD Pipeline / Unit Tests (push) Successful in 1m37s
CI/CD Pipeline / Integration Tests (push) Successful in 1m10s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
- 根因:Gitea Actions不提供GITHUB_SERVER_URL环境变量
- PR #361只加了set +e && exit 0绕过,未解决根因
- 现在通过env传入github.server_url,恢复set -eu错误处理
2026-07-15 15:20:20 +08:00
XiaoXia Bot 771c5ba579 fix: 修复matrix构建参数错位 — 去掉行续反斜杠\n问题
CI/CD Pipeline / Frontend Lint (push) Successful in 37s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
- \\n在YAML->shell多层转义后变成字面字符n,导致参数错位
- dockerfile路径被当作image tag,API/Worker 3秒即挂
- 去掉行续反斜杠,所有参数写在一行,空格分隔
2026-07-15 15:19:07 +08:00
XiaoXia Bot 2e59d4a275 fix: 精确修复matrix构建Build and push步骤换行问题
CI/CD Pipeline / Frontend Lint (push) Successful in 35s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m9s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m15s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 3s
CI/CD Pipeline / Build Staging API Image (push) Failing after 3s
CI/CD Pipeline / Integration Tests (push) Successful in 1m27s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
- 仅修改build-staging和build-production的Build and push步骤
- 将多行双引号字符串改为
转义单行格式
- 之前的修复误改了checkout等其他步骤,导致全链路CI崩溃
2026-07-15 15:13:33 +08:00
XiaoXia Bot f905c3ef8d fix: 构建前清理本地旧镜像,解决 buildx --load image already exists 错误
CI/CD Pipeline / Unit Tests (push) Failing after 0s
CI/CD Pipeline / Frontend Lint (push) Failing after 0s
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 2s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Failing after 0s
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
2026-07-15 15:11:31 +08:00
XiaoXia Bot 46c7c351af Merge remote-tracking branch 'origin/develop' into develop
CI/CD Pipeline / Unit Tests (push) Failing after 0s
CI/CD Pipeline / Frontend Lint (push) Failing after 0s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 3s
CI/CD Pipeline / Integration Tests (push) Failing after 0s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-07-15 14:50:23 +08:00
XiaoXia Bot f411dd0e1f fix: 修复matrix构建run命令换行符丢失问题
- matrix重构时Build and push步骤用了YAML多行双引号字符串
- act_runner解析时实际换行被替换为空格,导致变量未定义(set -eu)
- 改为\n转义格式,与其他step保持一致
- 修复build-staging和build-production两处matrix job
2026-07-15 14:50:08 +08:00
CI Bot 09a4dbe04c fix: formatDuration 不取整导致显示浮点数秒(如 9.542993秒)
CI/CD Pipeline / Frontend Lint (push) Successful in 36s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 1m36s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m4s
CI/CD Pipeline / Build Staging API Image (push) Failing after 2s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 2s
CI/CD Pipeline / Integration Tests (push) Successful in 1m10s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m58s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
全局搜索发现 4 处 formatDuration 函数中 seconds % 60 未做取整,
当后端返回浮点数 total_duration 时直接拼接显示。

修复:先 Math.round(seconds) 再取模,影响页面:
- 剪辑计划列表页(EditPlans.tsx)
- 模板库页面(TemplateLibrary.tsx)
- 查重详情页(DuplicationDetail.tsx)
- 查重结果页(DuplicationResults.tsx)
2026-07-15 14:39:55 +08:00
XiaoXia Bot 533a64e215 feat(ci): M-3 三镜像matrix并行构建重构 (#365)
CI/CD Pipeline / Frontend Lint (push) Successful in 34s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 1m35s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m57s
CI/CD Pipeline / Build Staging API Image (push) Failing after 2s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 2s
CI/CD Pipeline / Integration Tests (push) Successful in 1m11s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
- 6个独立构建job → 2个matrix job(build-staging + build-production)
- workflow从1186行→937行(-21%)
- 三镜像从串行约25min → 并行约12min(预计)
- 变量覆盖service/service_display/dockerfile/image_name/cache_name/timeout
- 条件步骤正确:npm build仅web、Cleanup old images仅production web
2026-07-15 14:22:34 +08:00
CI Bot 90f834e03a fix: refresh schema-metadata-snapshot.json — add missing result_count column to edit_plans
CI/CD Pipeline / Frontend Lint (push) Successful in 43s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m0s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 2s
CI/CD Pipeline / Build Staging API Image (push) Failing after 2s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m15s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m29s
The check_schema_metadata.py CI step was failing because the
edit_plans table gained a result_count column (INTEGER) in the
SQLAlchemy model but the snapshot was never updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 14:08:03 +08:00
XiaoXia Bot 33f08b465b fix: 补充edit_plans.result_count字段的Alembic迁移 (#366)
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
2026-07-15 14:07:57 +08:00
CI Bot 388341b522 Merge branch 'feature/product-center-ui-upgrade' into develop
CI/CD Pipeline / Frontend Lint (push) Successful in 26s
CI/CD Pipeline / Unit Tests (push) Successful in 1m41s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 1m43s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m16s
2026-07-15 14:00:47 +08:00
CI Bot 56c7d9c95a fix: Prettier formatting for TemplateLibrary.tsx
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 48s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 56s
CI/CD Pipeline / Frontend Lint (push) Successful in 59s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m10s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m13s
CI/CD Pipeline / Integration Tests (push) Successful in 1m36s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 3m34s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m13s
修复 CI/CD frontend-lint Prettier check 失败

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 14:00:39 +08:00
CI Bot 05730e6c56 Merge branch 'feature/product-center-ui-upgrade' into develop
CI/CD Pipeline / Frontend Lint (push) Failing after 36s
CI/CD Pipeline / Unit Tests (push) Successful in 1m46s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 1m54s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m33s
2026-07-15 13:50:37 +08:00
CI Bot d2ea8ba579 fix: TemplateItem 类型 estimated_duration 字段对齐后端 API
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 47s
CI/CD Pipeline / Frontend Lint (push) Failing after 58s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 58s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m55s
CI/CD Pipeline / Unit Tests (push) Successful in 2m3s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m5s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m26s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m12s
- TemplateItem 新增 estimated_duration 字段,匹配后端实际返回字段名
- target_duration 标记为 deprecated,保留向后兼容
- TemplateLibrary 两处引用改为 estimated_duration ?? target_duration 回退
- 新增 mode/user_id 可选字段,description/clip_count/is_active 改 optional

修复模板卡片时长显示 NaN 问题(根因:前端读 target_duration 但后端返回 estimated_duration)
2026-07-15 13:45:22 +08:00
CI Bot 1486c7028a fix: 模板库两个bug修复 — handleUse改为导航到剪辑编辑器 + formatDuration NaN防护
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 1m3s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m6s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m9s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m15s
CI/CD Pipeline / Unit Tests (push) Successful in 2m19s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m22s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m34s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m20s
1. handleUse: 从调用不存在的 POST /templates/{id}/generate 改为导航到 /app/editing-planner?templateId=xxx
2. formatDuration: 参数类型扩展为 number|undefined|null,防止 target_duration 为空时显示 NaN
3. 清理未使用的 generateMutation 和 generateFromTemplate import

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 13:38:58 +08:00
CI Bot 73826d2f81 fix: EditPlan增加result_count字段,渲染成功后回写=1
CI/CD Pipeline / Frontend Lint (push) Successful in 38s
CI/CD Pipeline / Unit Tests (push) Successful in 1m37s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 2m9s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m12s
CI/CD Pipeline / Deploy Production (push) Failing after 14m38s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
- domain层EditPlan新增result_count字段(int,默认0)
- ORM模型EditPlanModel新增result_count列(Integer,默认0)
- repository层create/update/_model_to_entity三处同步
- 渲染成功回调与total_duration一起回写result_count=1
2026-07-15 13:28:25 +08:00
CI Bot 64833c8225 feat(ci): B 规则正式加入 ruff 阻断级 - P0-5 Step 2 完成
CI/CD Pipeline / Frontend Lint (push) Successful in 37s
CI/CD Pipeline / Unit Tests (push) Successful in 1m40s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m44s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Failing after 1s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 2s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m13s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m28s
- ruff select 加入 B(flake8-bugbear)
- B008 加入 ignore(FastAPI 依赖注入模式,335 处,非 bug)
- B904: 71 处已修复(raise from 补全)
- B007: 12 处已修复(未使用循环变量改下划线)
- B011: 6 处已修复(assert False 改 raise AssertionError)

P0-5 Step 2 全部完成:ruff 门禁从 E/F/W 升级为 E/F/W/B
下一步 Step 3:UP + SIM 等扩展规则集
2026-07-15 13:24:25 +08:00
CI Bot 7ff7ae6c7d fix/b007-unused-loop-var 合并
CI/CD Pipeline / Frontend Lint (push) Successful in 38s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m35s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Failing after 2s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 2s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 1m41s
CI/CD Pipeline / Integration Tests (push) Successful in 1m11s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m11s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
2026-07-15 13:20:58 +08:00
CI Bot b04345c7ff fix/b011-assert-false 合并 2026-07-15 13:20:55 +08:00
XiaoXia Bot a330ce50c0 feat(ci): M-2 local cache为主 + registry cache兜底 - 解决缓存导入慢247s问题 (#362)
CI/CD Pipeline / Frontend Lint (push) Successful in 28s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m38s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 2s
CI/CD Pipeline / Build Staging API Image (push) Failing after 2s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 1m38s
CI/CD Pipeline / Integration Tests (push) Successful in 1m11s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 1m43s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
2026-07-15 13:17:48 +08:00
xiaoxia 5e2800d360 chore(ci): 同步 develop 最新 ci-cd.yml - 解决冲突
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 40s
CI/CD Pipeline / Frontend Lint (push) Successful in 34s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 32s
CI/CD Pipeline / Unit Tests (push) Successful in 1m58s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m40s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m59s
CI/CD Pipeline / Integration Tests (push) Successful in 1m35s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m35s
2026-07-15 13:14:27 +08:00
CI Bot 83a3d072fc fix(ci): 修复 migration diff step 失败 - 增加容错避免阻断 CI
CI/CD Pipeline / Frontend Lint (push) Successful in 46s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m11s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 2s
CI/CD Pipeline / Build Staging API Image (push) Failing after 2s
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m17s
CI/CD Pipeline / Build Staging Web Image (push) Failing after 2m11s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m35s
- Prepare git for migration diff step 改为 set +e + exit 0
- 该 step 失败不影响后续检查(Check migration safety 有全量检查 fallback)
- 解决 tarball checkout 环境下 git init/commit 失败导致 Validate 全挂的问题
2026-07-15 12:58:59 +08:00
CI Bot 7f8193fbc6 fix(ci): 增强 Frontend Lint 缓存验证 - 检查 tsc/prettier/vitest 完整性
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
- 缓存有效性检查增加 tsc/prettier/vitest 二进制文件存在性校验
- npm ci 安装后增加完整性检查,关键工具缺失则自动清理重装
- 解决 node_modules 缓存脏数据导致 typescript/prettier/vitest 随机失败的问题
2026-07-15 12:58:10 +08:00
xiaoxia d3ed0bb4ba Merge pull request 'fix(ci): 修复registry不稳定导致镜像构建失败 - 解耦缓存推送' (#357) from ci/registry-cache-stability-fix into develop
CI/CD Pipeline / Unit Tests (push) Successful in 2m14s
CI/CD Pipeline / Frontend Lint (push) Successful in 59s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m43s
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
Merge PR #357: fix(ci): 修复registry不稳定导致构建失败 - 构建与缓存推送解耦
2026-07-15 12:55:13 +08:00
xiaoxia 8107b2a255 chore(ci): 同步 develop 最新 ci-cd.yml - 增量扫描修复
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 1m15s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m17s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m4s
CI/CD Pipeline / Unit Tests (push) Successful in 3m4s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m45s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m27s
CI/CD Pipeline / Integration Tests (push) Successful in 1m43s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m27s
2026-07-15 12:40:00 +08:00
xiaoxia 2977e27ede fix(ci): 修复增量扫描误检 - 改用 Gitea API 获取 PR 改动文件
CI/CD Pipeline / Frontend Lint (push) Successful in 59s
CI/CD Pipeline / Unit Tests (push) Successful in 3m3s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 3m49s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m26s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m21s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
2026-07-15 12:39:33 +08:00
XiaoXia Bot 99fa146a5c fix(ci): 修复registry不稳定导致构建失败 - 构建与缓存推送解耦
CI/CD Pipeline / Frontend Lint (push) Successful in 1m32s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m31s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m44s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m49s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m52s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m54s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m29s
CI/CD Pipeline / Integration Tests (push) Successful in 1m25s
- 新增docker_build_push.sh通用脚本
- 构建镜像(只读缓存)与推送缓存分离
- 缓存推送失败重试3次,不影响主构建结果
- 使用zstd压缩缓存层
2026-07-15 12:33:02 +08:00
CI Bot d99cda8bc2 fix: 剪辑计划P0+P1修复 - asset_ids传递 + result_count修正 + total_duration回写 + logs埋点
CI/CD Pipeline / Frontend Lint (push) Successful in 44s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m33s
CI/CD Pipeline / Unit Tests (push) Successful in 2m33s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m39s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 4m53s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m40s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 14m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
P0修复:
- 触发生成时将plan.config.asset_ids传递到GenerationTask
- 同时补全project_id字段(之前为空字符串)

P1修复:
- result_count: 剪辑计划多片段合成1个成片,改为1(之前误存为clip数)
- total_duration: 渲染成功后回写实际输出时长到EditPlan.total_duration
- logs: 新增5个关键节点日志埋点(render_start/download_done/download_failed/render_complete/render_failed)
- 下载完成后进度推进到30%,便于前端展示素材下载中状态
2026-07-15 12:19:42 +08:00
xiaoxia 4b2f42f0be fix(worker): 修复unified_render_service.py的black格式化问题 (#359)
CI/CD Pipeline / Frontend Lint (push) Successful in 1m7s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m49s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m51s
CI/CD Pipeline / Integration Tests (push) Successful in 1m50s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m59s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
2026-07-15 11:52:51 +08:00
xiaoxia 531aacb57e fix(code-quality): 第二批 - B904 raise-without-from 批量修复 (71个) (#353)
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
2026-07-15 11:51:45 +08:00
xiaoxia 497ea8ca25 chore: 恢复 ci-cd.yml 到 develop 版本(移除调试代码)
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 53s
CI/CD Pipeline / Frontend Lint (push) Successful in 58s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 43s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m6s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 3m14s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m56s
CI/CD Pipeline / Integration Tests (push) Successful in 1m53s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m50s
2026-07-15 11:27:22 +08:00
xiaoxia 3ded5eb962 style: Prettier 格式化 EditingPlanner.tsx(修复 Frontend Lint 失败)
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 54s
CI/CD Pipeline / Frontend Lint (push) Successful in 49s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 47s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 59s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 3m8s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 3m10s
CI/CD Pipeline / Integration Tests (push) Successful in 2m17s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m56s
2026-07-15 11:26:56 +08:00
xiaoxia 7c67468115 debug: 加 node_modules 调试 step
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 58s
CI/CD Pipeline / Frontend Lint (push) Failing after 44s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 49s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 46s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m58s
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
2026-07-15 11:21:31 +08:00
xiaoxia 7c8748ece6 perf(ci): 扩展CI触发分支模式 + 增加手动触发入口 (#349)
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
2026-07-15 11:20:08 +08:00
CI Bot f0a5f4fe42 fix: 硬切场景改用concat filter替代xfade,提升多素材场景性能与稳定性
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 46s
CI/CD Pipeline / Frontend Lint (push) Successful in 48s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m29s
CI/CD Pipeline / Integration Tests (push) Successful in 2m24s
- 全硬切(no transition)场景下使用concat filter,性能远优于xfade
- 有转场效果时继续走xfade路径,保持兼容
- 修复硬切默认走xfade(fade fallback)的设计问题
- 新增单元测试覆盖concat和xfade两种路径
- 87个unified_render测试全绿,全套2147测试零回归
2026-07-15 11:18:28 +08:00
xiaoxia eba96fc864 feat(api): 剪辑计划生成状态API增强 - 新增progress/video_url/error_message等字段 (#351)
CI/CD Pipeline / Frontend Lint (push) Successful in 1m35s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m16s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m29s
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
2026-07-15 11:15:55 +08:00
xiaoxia 5f17a00318 chore: 同步 package-lock.json 到 develop(补充 prettier 等依赖)
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 1m0s
CI/CD Pipeline / Frontend Lint (push) Failing after 1m34s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 2m33s
CI/CD Pipeline / Unit Tests (push) Successful in 3m6s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 3m8s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 3m14s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 2m10s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m58s
2026-07-15 11:12:43 +08:00
xiaoxia 2a4aa46881 chore: 同步 package.json 到 develop(补充 prettier 等依赖)
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
2026-07-15 11:12:42 +08:00
xiaoxia 999ea2856a chore: 同步 ci-cd.yml 到 develop 最新版本(解决 merge 冲突 + 修复 Frontend Lint)
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m3s
CI/CD Pipeline / Frontend Lint (push) Failing after 1m4s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 1m13s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m22s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m27s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m34s
CI/CD Pipeline / Integration Tests (push) Successful in 1m34s
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
2026-07-15 11:09:07 +08:00
xiaoxia 68fe8aabd5 perf(ci): P1级Web构建缓存优化 - tsc增量 + Vite构建缓存 (#348)
CI/CD Pipeline / Frontend Lint (push) Successful in 1m5s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m41s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m56s
CI/CD Pipeline / Build Staging API Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 4m12s
CI/CD Pipeline / Integration Tests (push) Successful in 1m57s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 12m34s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 15m17s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 2m16s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m17s
2026-07-15 10:52:39 +08:00
xiaoxia d9e00992a6 fix(ci): 修复 Frontend Lint 拉错 eslint 版本的问题 (#347)
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
2026-07-15 10:52:26 +08:00
xiaoxia 2293adfbcf perf(ci): P0-4 Phase 1 - PR模式下lint增量扫描,只扫改动文件
CI/CD Pipeline / Frontend Lint (push) Successful in 2m0s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m38s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 2m47s
CI/CD Pipeline / Integration Tests (push) Successful in 1m25s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
2026-07-15 10:44:20 +08:00
xiaoxia 4397c670d8 perf(ci): 用 ruff 替换 flake8 - 规则完全对齐,速度提升10x+
CI/CD Pipeline / Frontend Lint (push) Successful in 39s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m19s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (push) Successful in 1m41s
CI/CD Pipeline / Integration Tests (push) Successful in 1m17s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m18s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m54s
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
2026-07-15 10:12:26 +08:00
xiaoxia 9c155d0561 fix(ci): 修复Frontend Lint缓存脏数据导致npm ci失败 (#345)
CI/CD Pipeline / Frontend Lint (push) Successful in 1m37s
CI/CD Pipeline / Unit Tests (push) Successful in 1m51s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m10s
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m37s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m59s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
2026-07-15 10:04:55 +08:00
CI Bot 7da09bfcfd feat: 打通剪辑计划生成链路 — EditingPlanner 生成按钮对接真实 API
CI/CD Pipeline / Frontend Lint (push) Failing after 50s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m7s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m54s
CI/CD Pipeline / Unit Tests (push) Successful in 1m57s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 2m19s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m25s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m10s
CI/CD Pipeline / Integration Tests (push) Successful in 1m9s
- 生成按钮调用 generateEditPlan(planId) 而非跳转一键生成页面
- 进度弹窗 + 轮询 getGenerationStatus(每2秒)+ 视频结果展示
- 列表页编辑按钮正确加载计划数据到编辑器(planId URL参数)
- 生成历史弹窗正常工作
- 列表页状态自动同步(已有5秒 refetch)
- 修复所有 TypeScript 编译错误(tsc 零错误)
2026-07-15 09:49:24 +08:00
xiaoxia 535e525a0e fix(worker): 修复瘦身后shared模块无法导入的PYTHONPATH问题
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m48s
CI/CD Pipeline / Unit Tests (push) Successful in 1m50s
CI/CD Pipeline / Integration Tests (push) Successful in 1m9s
CI/CD Pipeline / Frontend Lint (push) Failing after 16s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
PR #343: 修复Worker镜像瘦身后shared模块无法导入的PYTHONPATH问题

将ENV PYTHONPATH=/app改为ENV PYTHONPATH=/app:/app/packages,解决ModuleNotFoundError: No module named "shared"问题。
2026-07-15 09:16:35 +08:00
xiaoxia e705cbdd8a perf(ci): 修复Frontend Lint的npm缓存假命中问题 (#341)
CI/CD Pipeline / Frontend Lint (push) Successful in 40s
CI/CD Pipeline / Unit Tests (push) Successful in 1m32s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 1m54s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m54s
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
2026-07-15 09:04:03 +08:00
CI Bot 1af8c7ae9e fix(ci): use npx for prettier since it is not a project dependency
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m26s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m36s
CI/CD Pipeline / Unit Tests (push) Successful in 2m9s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m10s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m26s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m37s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m7s
CI/CD Pipeline / Integration Tests (push) Successful in 1m7s
Prettier is not listed in apps/web/package.json devDependencies,
so ./node_modules/.bin/prettier does not exist after npm ci.
Use npx --yes prettier@3 to download and run on-the-fly.
2026-07-15 08:43:30 +08:00
CI Bot 27681c785a fix(ci): 使用本地 ESLint/Prettier/TypeScript 二进制,绕过损坏的 npm cache 卷
CI/CD Pipeline / Frontend Lint (push) Failing after 1m10s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m16s
CI/CD Pipeline / Unit Tests (push) Successful in 2m8s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m8s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m9s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m13s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m14s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m11s
- Install 步骤: rm -rf node_modules (非 /*) + --cache /tmp/npm-cache 绕过 Docker 卷缓存
- ESLint: npx eslint → ./node_modules/.bin/eslint (避免 npx 下载 eslint@10 不兼容 flat config)
- Prettier: npx prettier → ./node_modules/.bin/prettier
- TypeScript: npx tsc → ./node_modules/.bin/tsc
2026-07-15 08:36:16 +08:00
CI Bot 639f73b16d Merge remote-tracking branch 'origin/develop' into feature/product-center-ui-upgrade
CI/CD Pipeline / Frontend Lint (push) Failing after 53s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 59s
CI/CD Pipeline / Unit Tests (push) Successful in 1m55s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m4s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m26s
CI/CD Pipeline / Build Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m33s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 1m11s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m8s
# Conflicts:
#	apps/web/src/api/products.ts
#	apps/web/src/pages/products/ProductLibrary.tsx
2026-07-15 08:24:44 +08:00
CI Bot a7b2cbfc8c feat(products): 成片中心 UI 4 大功能升级
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 45s
CI/CD Pipeline / Unit Tests (push) Successful in 1m19s
CI/CD Pipeline / Frontend Lint (push) Failing after 1m10s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (push) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m16s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m23s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 5m52s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 11m34s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 10m1s
1. 封面预览 — 卡片已支持 thumbnailUrl 展示
2. 复核状态标签 — 右上角显示待复核(灰)/已通过(绿)/需修改(红),点击循环切换
3. 批量操作 — 批量下载改用 batch-download API + job_id 轮询
4. 筛选栏 — 新增按项目、按复核状态筛选 Select

API 层新增:
- updateReviewStatus: PATCH /products/{id}/review
- batchDownload: POST /products/batch-download
- getBatchDownloadStatus: GET /products/batch-download/{jobId}

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 09:42:08 +08:00
74 changed files with 1922 additions and 1107 deletions
File diff suppressed because one or more lines are too long
Regular → Executable
+28 -785
View File
File diff suppressed because one or more lines are too long
+37
View File
@@ -0,0 +1,37 @@
name: Debug Build Schedule
on:
push:
branches:
- debug/ci-build-schedule
jobs:
test-simple:
name: Test Simple Job
runs-on: host
steps:
- name: Hello
shell: sh
run: echo "Hello from simple job"
test-matrix:
name: Test Matrix ${{ matrix.name }}
runs-on: host
strategy:
fail-fast: false
matrix:
include:
- name: A
timeout: 5
- name: B
timeout: 5
timeout-minutes: ${{ matrix.timeout }}
steps:
- name: Hello
shell: sh
run: echo "Hello from matrix job ${{ matrix.name }}"
test-if:
name: Test If Condition
runs-on: host
if: github.event_name == "push" && github.ref_name == "debug/ci-build-schedule"
steps:
- name: Hello
shell: sh
run: echo "Hello from if job"
+64
View File
@@ -0,0 +1,64 @@
name: Debug Build Schedule v2 - Copy CI Build Structure
on:
push:
branches:
- debug/ci-build-schedule
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: false
jobs:
build-staging:
name: Build Staging ${{ matrix.service_display }} Image
runs-on: host
timeout-minutes: ${{ matrix.timeout }}
if: github.event_name == "push" && github.ref_name == "debug/ci-build-schedule"
strategy:
fail-fast: false
matrix:
include:
- service: api
service_display: API
timeout: 10
- service: worker
service_display: Worker
timeout: 10
- service: web
service_display: Web
timeout: 10
steps:
- name: Step 1
shell: sh
run: echo "Step 1"
- name: Step 2
shell: sh
run: echo "Step 2"
- name: Step 3
shell: sh
run: echo "Step 3"
deploy-staging:
name: Deploy Staging
runs-on: host
timeout-minutes: 15
if: github.event_name == "push" && github.ref_name == "debug/ci-build-schedule"
needs:
- build-staging
steps:
- name: Deploy
shell: sh
run: echo "Deploy"
build-production:
name: Build Production ${{ matrix.service_display }} Image
runs-on: host
timeout-minutes: ${{ matrix.timeout }}
if: startsWith(github.ref, "refs/tags/v")
strategy:
fail-fast: false
matrix:
include:
- service: api
service_display: API
timeout: 10
steps:
- name: Step 1
shell: sh
run: echo "Step 1"
@@ -0,0 +1,29 @@
"""add result_count to edit_plans
Revision ID: 041_result_count
Revises: 040_playback_speed
Create Date: 2026-07-15 14:05:00.000000
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "041_result_count"
down_revision = "040_playback_speed"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column(
"edit_plans",
sa.Column("result_count", sa.Integer(), nullable=False, server_default="0"),
)
def downgrade() -> None:
op.drop_column("edit_plans", "result_count")
+4 -2
View File
@@ -239,7 +239,9 @@ def get_duplication_detail(
return _to_detail_response(record) return _to_detail_response(record)
@router.delete("/records/{record_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) @router.delete(
"/records/{record_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response
)
def delete_duplication_record( def delete_duplication_record(
record_id: str, record_id: str,
authenticated_user: AuthenticatedUser = Depends(get_current_user), authenticated_user: AuthenticatedUser = Depends(get_current_user),
@@ -287,7 +289,7 @@ def retry_duplication(
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e), detail=str(e),
) ) from e
if updated is None: if updated is None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
+14 -8
View File
@@ -72,6 +72,7 @@ class EditPlanResponse(BaseModel):
name: str name: str
status: str status: str
total_duration: float total_duration: float
result_count: int = 0
project_id: str = "" project_id: str = ""
created_by_user_id: str = "" created_by_user_id: str = ""
config: dict[str, Any] config: dict[str, Any]
@@ -108,6 +109,10 @@ class EditPlanGenerationStatusResponse(BaseModel):
plan_id: str plan_id: str
plan_status: str plan_status: str
generation_task_id: Optional[str] = None generation_task_id: Optional[str] = None
generation_task_status: Optional[str] = None
progress: float = 0.0
video_url: str = ""
error_message: str = ""
clips: List[ClipStatusItem] clips: List[ClipStatusItem]
@@ -237,6 +242,7 @@ def _to_response(p: EditPlan) -> EditPlanResponse:
name=p.name, name=p.name,
status=p.status.value if hasattr(p.status, "value") else p.status, status=p.status.value if hasattr(p.status, "value") else p.status,
total_duration=p.total_duration, total_duration=p.total_duration,
result_count=getattr(p, "result_count", 0),
project_id=p.project_id or "", project_id=p.project_id or "",
created_by_user_id=p.created_by_user_id or "", created_by_user_id=p.created_by_user_id or "",
config=p.config, config=p.config,
@@ -275,11 +281,11 @@ def list_plans(
if status_filter: if status_filter:
try: try:
status_enum = EditPlanStatus(status_filter) status_enum = EditPlanStatus(status_filter)
except ValueError: except ValueError as _e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的筛选条件,请选择正确的状态", detail="无效的筛选条件,请选择正确的状态",
) ) from _e
# 项目鉴权:如果指定了 project_id,校验用户是否有权访问 # 项目鉴权:如果指定了 project_id,校验用户是否有权访问
if project_id: if project_id:
@@ -322,7 +328,7 @@ def get_plan(
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=str(exc), detail=str(exc),
) ) from exc
# 项目鉴权 # 项目鉴权
if plan.project_id: if plan.project_id:
check_project_access(plan.project_id, current_user.user.id, project_repository) check_project_access(plan.project_id, current_user.user.id, project_repository)
@@ -358,7 +364,7 @@ def create_plan(
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail=str(exc), detail=str(exc),
) ) from exc
logger.info( logger.info(
"创建剪辑计划: id=%s name=%s by user=%s", "创建剪辑计划: id=%s name=%s by user=%s",
created.id, created.id,
@@ -401,11 +407,11 @@ def update_plan(
if body.status is not None: if body.status is not None:
try: try:
target_status = EditPlanStatus(body.status) target_status = EditPlanStatus(body.status)
except ValueError: except ValueError as _e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的状态值,请选择正确的状态", detail="无效的状态值,请选择正确的状态",
) ) from _e
svc.transition_status(plan_id, target_status) svc.transition_status(plan_id, target_status)
except ValueError as exc: except ValueError as exc:
err_msg = str(exc) err_msg = str(exc)
@@ -413,11 +419,11 @@ def update_plan(
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=err_msg, detail=err_msg,
) ) from exc
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail=err_msg, detail=err_msg,
) ) from exc
# 返回最新状态 # 返回最新状态
result = svc.get_plan_or_raise(plan_id) result = svc.get_plan_or_raise(plan_id)
+4 -4
View File
@@ -58,7 +58,7 @@ def ai_recommend_clips(
try: try:
plan = svc.get_plan_or_raise(plan_id) plan = svc.get_plan_or_raise(plan_id)
except ValueError as exc: except ValueError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
if plan.project_id: if plan.project_id:
check_project_access(plan.project_id, current_user.user.id, project_repository) check_project_access(plan.project_id, current_user.user.id, project_repository)
@@ -103,7 +103,7 @@ def ai_recommend_clips(
config=normalized_config, config=normalized_config,
total_duration=result["total_duration"], total_duration=result["total_duration"],
) )
except Exception: except Exception as _e:
logger.exception("AI 推荐写入失败,plan_id=%s 数据可能不一致", plan_id) logger.exception("AI 推荐写入失败,plan_id=%s 数据可能不一致", plan_id)
try: try:
db.rollback() db.rollback()
@@ -116,7 +116,7 @@ def ai_recommend_clips(
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="AI推荐结果保存失败,请稍后重试", detail="AI推荐结果保存失败,请稍后重试",
) ) from _e
logger.info( logger.info(
"AI 推荐片段方案: plan_id=%s clips=%d duration=%.1f by user=%s", "AI 推荐片段方案: plan_id=%s clips=%d duration=%.1f by user=%s",
@@ -167,7 +167,7 @@ def generate_cover(
try: try:
plan = svc.get_plan_or_raise(plan_id) plan = svc.get_plan_or_raise(plan_id)
except ValueError as exc: except ValueError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
if plan.project_id: if plan.project_id:
check_project_access(plan.project_id, current_user.user.id, project_repository) check_project_access(plan.project_id, current_user.user.id, project_repository)
+25 -9
View File
@@ -183,9 +183,7 @@ def _auto_fallback_auto_material_mode(
def _check_queue_limits(gen_task_repo, user_id: str) -> None: def _check_queue_limits(gen_task_repo, user_id: str) -> None:
"""队列限流预检查""" """队列限流预检查"""
try: try:
has_count = hasattr(gen_task_repo, "count_pending_by_user") and hasattr( has_count = hasattr(gen_task_repo, "count_pending_by_user") and hasattr(gen_task_repo, "count_pending_total")
gen_task_repo, "count_pending_total"
)
if has_count: if has_count:
user_pending = gen_task_repo.count_pending_by_user(user_id) user_pending = gen_task_repo.count_pending_by_user(user_id)
global_pending = gen_task_repo.count_pending_total() global_pending = gen_task_repo.count_pending_total()
@@ -241,7 +239,7 @@ def generate_plan(
try: try:
can_gen, reason = svc.can_generate(plan_id) can_gen, reason = svc.can_generate(plan_id)
except ValueError as exc: except ValueError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
if not can_gen: if not can_gen:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=reason) raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=reason)
@@ -255,12 +253,15 @@ def generate_plan(
gen_task_use_case = CreateGenerationTaskUseCase(gen_task_repo) gen_task_use_case = CreateGenerationTaskUseCase(gen_task_repo)
plan = svc.get_plan_or_raise(plan_id) plan = svc.get_plan_or_raise(plan_id)
# 从 plan.config 中读取 asset_ids 并传递给 GenerationTask
config_asset_ids = (plan.config or {}).get("asset_ids", [])
gen_task = gen_task_use_case.execute( gen_task = gen_task_use_case.execute(
CreateGenerationTaskCommand( CreateGenerationTaskCommand(
project_id="", project_id=plan.project_id or "",
template_id=plan.template_id, template_id=plan.template_id,
created_by_user_id=current_user.user.id, created_by_user_id=current_user.user.id,
source_edit_plan_id=plan_id, source_edit_plan_id=plan_id,
asset_ids=list(config_asset_ids) if config_asset_ids else [],
) )
) )
@@ -286,7 +287,7 @@ def generate_plan(
) )
except HTTPException: except HTTPException:
raise raise
except Exception: except Exception as _e:
logger.exception("触发剪辑计划生成失败: plan_id=%s", plan_id) logger.exception("触发剪辑计划生成失败: plan_id=%s", plan_id)
try: try:
svc.transition_status(plan_id, EditPlanStatus.FAILED) svc.transition_status(plan_id, EditPlanStatus.FAILED)
@@ -295,7 +296,7 @@ def generate_plan(
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="生成失败,请稍后重试", detail="生成失败,请稍后重试",
) ) from _e
@router.get( @router.get(
@@ -313,7 +314,7 @@ def get_generation_status(
try: try:
gen_status = svc.get_generation_status(plan_id) gen_status = svc.get_generation_status(plan_id)
except ValueError as exc: except ValueError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
plan = gen_status["plan"] plan = gen_status["plan"]
if plan.project_id: if plan.project_id:
@@ -333,10 +334,25 @@ def get_generation_status(
for c in clips for c in clips
] ]
# 从 plan.config 中取渲染结果 URL
video_url = (plan.config or {}).get("rendered_url", "")
# 从 gen_status 中取进度、错误信息、任务状态
progress = gen_status.get("progress", 0.0)
error_message = gen_status.get("error_message", "")
gen_task_status = gen_status.get("generation_task_status")
# 如果计划已完成但进度还是0,补100
plan_status_val = plan.status.value if hasattr(plan.status, "value") else plan.status
if plan_status_val == "completed" and progress < 100:
progress = 100.0
return EditPlanGenerationStatusResponse( return EditPlanGenerationStatusResponse(
plan_id=plan_id, plan_id=plan_id,
plan_status=plan.status.value if hasattr(plan.status, "value") else plan.status, plan_status=plan_status_val,
generation_task_id=gen_status["generation_task_id"], generation_task_id=gen_status["generation_task_id"],
generation_task_status=gen_task_status,
progress=progress,
video_url=video_url,
error_message=error_message,
clips=clip_items, clips=clip_items,
) )
@@ -173,7 +173,7 @@ def generate_from_template(
try: try:
template = template_svc.get_template_or_raise(body.template_id) template = template_svc.get_template_or_raise(body.template_id)
except ValueError as exc: except ValueError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
clip_configs = template_svc.list_clip_configs(body.template_id, skip=0, limit=200) clip_configs = template_svc.list_clip_configs(body.template_id, skip=0, limit=200)
+6 -6
View File
@@ -105,7 +105,7 @@ async def list_feature_flags(
return sorted(result, key=lambda x: x.name) return sorted(result, key=lambda x: x.name)
except Exception as exc: except Exception as exc:
logger.error("Failed to list feature flags: %s", exc) logger.error("Failed to list feature flags: %s", exc)
raise HTTPException(status_code=500, detail=f"Failed to list flags: {exc}") raise HTTPException(status_code=500, detail=f"Failed to list flags: {exc}") from exc
@router.get("/{name}", response_model=FeatureFlagResponse) @router.get("/{name}", response_model=FeatureFlagResponse)
@@ -120,7 +120,7 @@ async def get_feature_flag(
return FeatureFlagResponse.from_config(config) return FeatureFlagResponse.from_config(config)
except Exception as exc: except Exception as exc:
logger.error("Failed to get feature flag %s: %s", name, exc) logger.error("Failed to get feature flag %s: %s", name, exc)
raise HTTPException(status_code=500, detail=f"Failed to get flag: {exc}") raise HTTPException(status_code=500, detail=f"Failed to get flag: {exc}") from exc
@router.get("/{name}/check", response_model=FeatureFlagCheckResponse) @router.get("/{name}/check", response_model=FeatureFlagCheckResponse)
@@ -136,7 +136,7 @@ async def check_feature_flag(
return FeatureFlagCheckResponse(name=name, active=active, identifier=identifier) return FeatureFlagCheckResponse(name=name, active=active, identifier=identifier)
except Exception as exc: except Exception as exc:
logger.error("Failed to check feature flag %s: %s", name, exc) logger.error("Failed to check feature flag %s: %s", name, exc)
raise HTTPException(status_code=500, detail=f"Failed to check flag: {exc}") raise HTTPException(status_code=500, detail=f"Failed to check flag: {exc}") from exc
@router.put("/{name}", response_model=FeatureFlagResponse) @router.put("/{name}", response_model=FeatureFlagResponse)
@@ -170,7 +170,7 @@ async def update_feature_flag(
return FeatureFlagResponse.from_config(config) return FeatureFlagResponse.from_config(config)
except Exception as exc: except Exception as exc:
logger.error("Failed to update feature flag %s: %s", name, exc) logger.error("Failed to update feature flag %s: %s", name, exc)
raise HTTPException(status_code=500, detail=f"Failed to update flag: {exc}") raise HTTPException(status_code=500, detail=f"Failed to update flag: {exc}") from exc
@router.delete("/{name}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) @router.delete("/{name}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response)
@@ -178,7 +178,7 @@ async def delete_feature_flag(
name: str, name: str,
_: bool = Depends(_verify_internal_api_key), _: bool = Depends(_verify_internal_api_key),
store: RedisFeatureFlagStore = Depends(_get_feature_flag_store), store: RedisFeatureFlagStore = Depends(_get_feature_flag_store),
) : ):
"""删除 Feature Flag。 """删除 Feature Flag。
只允许删除 ALLOWED_FLAGS 列表中的 flag。 只允许删除 ALLOWED_FLAGS 列表中的 flag。
@@ -191,4 +191,4 @@ async def delete_feature_flag(
pass pass
except Exception as exc: except Exception as exc:
logger.error("Failed to delete feature flag %s: %s", name, exc) logger.error("Failed to delete feature flag %s: %s", name, exc)
raise HTTPException(status_code=500, detail=f"Failed to delete flag: {exc}") raise HTTPException(status_code=500, detail=f"Failed to delete flag: {exc}") from exc
+6 -5
View File
@@ -43,6 +43,7 @@ logger = logging.getLogger(__name__)
router = APIRouter() router = APIRouter()
def _to_generation_task_response(task) -> GenerationTaskResponse: def _to_generation_task_response(task) -> GenerationTaskResponse:
return GenerationTaskResponse( return GenerationTaskResponse(
id=task.id, id=task.id,
@@ -282,28 +283,28 @@ def create_generation_task(
created_tasks.append(task) created_tasks.append(task)
else: else:
failed_tasks.append(task) failed_tasks.append(task)
except UserPendingLimitExceeded: except UserPendingLimitExceeded as _e:
# 兜底:如果预检查后又并发提交了,在这里也拦住 # 兜底:如果预检查后又并发提交了,在这里也拦住
failed_tasks.append(task) failed_tasks.append(task)
if not created_tasks: if not created_tasks:
raise HTTPException( raise HTTPException(
status_code=429, status_code=429,
detail="您的待处理任务过多,请等待完成后再提交", detail="您的待处理任务过多,请等待完成后再提交",
) ) from _e
break break
except GlobalQueueFull: except GlobalQueueFull as _e:
failed_tasks.append(task) failed_tasks.append(task)
if not created_tasks: if not created_tasks:
raise HTTPException( raise HTTPException(
status_code=503, status_code=503,
detail="系统繁忙,请稍后再试", detail="系统繁忙,请稍后再试",
) ) from _e
break break
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
logger.error("[生成任务] 创建失败: %s", e, exc_info=True) logger.error("[生成任务] 创建失败: %s", e, exc_info=True)
raise HTTPException(status_code=500, detail="创建生成任务失败,请稍后重试或查看任务日志") raise HTTPException(status_code=500, detail="创建生成任务失败,请稍后重试或查看任务日志") from e
items = [_to_generation_task_response(t) for t in created_tasks + failed_tasks] items = [_to_generation_task_response(t) for t in created_tasks + failed_tasks]
return BatchGenerationTaskResponse(items=items, total=len(items)) return BatchGenerationTaskResponse(items=items, total=len(items))
+2 -2
View File
@@ -81,11 +81,11 @@ def delete_project(
use_case = DeleteProjectUseCase(project_repository) use_case = DeleteProjectUseCase(project_repository)
try: try:
deleted = use_case.execute(project_id, authenticated_user.user.id) deleted = use_case.execute(project_id, authenticated_user.user.id)
except PermissionError: except PermissionError as _e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail="Only the project owner can delete this project", detail="Only the project owner can delete this project",
) ) from _e
if not deleted: if not deleted:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found")
return return
+1 -1
View File
@@ -254,7 +254,7 @@ async def payment_callback(
return {"success": True, "message": "支付成功", "record_id": record_id} return {"success": True, "message": "支付成功", "record_id": record_id}
except Exception as e: except Exception as e:
session.rollback() session.rollback()
raise HTTPException(status_code=500, detail=f"支付处理失败: {str(e)}") raise HTTPException(status_code=500, detail=f"支付处理失败: {str(e)}") from e
finally: finally:
session.close() session.close()
+17 -15
View File
@@ -147,9 +147,9 @@ def get_template(
use_case = GetTemplateUseCase(template_repository) use_case = GetTemplateUseCase(template_repository)
template = use_case.execute(template_id, user_id) template = use_case.execute(template_id, user_id)
usage = template_repository.get_usage_count(template_id) usage = template_repository.get_usage_count(template_id)
except Exception: except Exception as _e:
logger.exception("get_template 查询失败: template_id=%s", template_id) logger.exception("get_template 查询失败: template_id=%s", template_id)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="模板查询失败") raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="模板查询失败") from _e
if template is None: if template is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
return _to_response(template, usage_count=usage) return _to_response(template, usage_count=usage)
@@ -186,7 +186,7 @@ def create_template(
try: try:
template = use_case.execute(command) template = use_case.execute(command)
except ValidationError as exc: except ValidationError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
return _to_response(template) return _to_response(template)
@@ -226,10 +226,10 @@ def update_template(
use_case = UpdateTemplateUseCase(template_repository) use_case = UpdateTemplateUseCase(template_repository)
try: try:
template = use_case.execute(command) template = use_case.execute(command)
except NotFoundError: except NotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e
except ValidationError as exc: except ValidationError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
return _to_response(template) return _to_response(template)
@@ -264,10 +264,10 @@ def copy_template(
use_case = CopyTemplateUseCase(template_repository) use_case = CopyTemplateUseCase(template_repository)
try: try:
template = use_case.execute(command) template = use_case.execute(command)
except NotFoundError: except NotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e
except ValidationError as exc: except ValidationError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
return _to_response(template) return _to_response(template)
@@ -299,9 +299,9 @@ def toggle_favorite(
use_case = GetTemplateUseCase(template_repository) use_case = GetTemplateUseCase(template_repository)
try: try:
template = use_case.execute(template_id, user_id) template = use_case.execute(template_id, user_id)
except Exception: except Exception as _e:
logger.exception("toggle_favorite 查询失败: template_id=%s", template_id) logger.exception("toggle_favorite 查询失败: template_id=%s", template_id)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e
if template is None: if template is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
return ToggleFavoriteResponse(id=template_id, is_favorite=False) return ToggleFavoriteResponse(id=template_id, is_favorite=False)
@@ -326,10 +326,10 @@ def validate_template(
use_case = ValidateTemplateUseCase(template_repository) use_case = ValidateTemplateUseCase(template_repository)
try: try:
result = use_case.execute(command) result = use_case.execute(command)
except NotFoundError: except NotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e
except ValidationError as exc: except ValidationError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
return ValidateTemplateResponse( return ValidateTemplateResponse(
template=_to_response(result.template), template=_to_response(result.template),
@@ -375,7 +375,9 @@ def create_category(
) )
@router.delete("/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response) @router.delete(
"/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response
)
def delete_category( def delete_category(
category_id: str, category_id: str,
authenticated_user: AuthenticatedUser = Depends(get_current_user), authenticated_user: AuthenticatedUser = Depends(get_current_user),
+3 -3
View File
@@ -148,7 +148,7 @@ def create_title(
raise HTTPException( raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS, status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail=f"标题库配额已满({exc.used}/{exc.limit}),请升级套餐", detail=f"标题库配额已满({exc.used}/{exc.limit}),请升级套餐",
) ) from exc
return _to_response(item) return _to_response(item)
@@ -172,8 +172,8 @@ def update_title(
use_case = UpdateTitleLibraryUseCase(title_repository) use_case = UpdateTitleLibraryUseCase(title_repository)
try: try:
item = use_case.execute(command) item = use_case.execute(command)
except NotFoundError: except NotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Title not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Title not found") from _e
return _to_response(item) return _to_response(item)
+7 -7
View File
@@ -236,8 +236,8 @@ def get_tts_job(
use_case = GetTTSJobUseCase(repository) use_case = GetTTSJobUseCase(repository)
try: try:
job = use_case.execute(job_id, user_id) job = use_case.execute(job_id, user_id)
except TTSJobNotFoundError: except TTSJobNotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") from _e
return _to_response(job, sign_url) return _to_response(job, sign_url)
@@ -253,8 +253,8 @@ def get_tts_job_status(
use_case = GetTTSJobStatusUseCase(repository) use_case = GetTTSJobStatusUseCase(repository)
try: try:
job = use_case.execute(job_id, user_id) job = use_case.execute(job_id, user_id)
except TTSJobNotFoundError: except TTSJobNotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") from _e
output_url = job.output_audio_url output_url = job.output_audio_url
if output_url: if output_url:
output_url = sign_url(output_url) output_url = sign_url(output_url)
@@ -309,8 +309,8 @@ def save_tts_job_to_library(
get_use_case = GetTTSJobUseCase(tts_repository) get_use_case = GetTTSJobUseCase(tts_repository)
try: try:
job = get_use_case.execute(job_id, user_id) job = get_use_case.execute(job_id, user_id)
except TTSJobNotFoundError: except TTSJobNotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="TTS job not found") from _e
# 校验已完成 # 校验已完成
if not job.is_completed: if not job.is_completed:
@@ -363,7 +363,7 @@ def save_tts_job_to_library(
raise HTTPException( raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS, status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail=f"配音库配额已满({exc.used}/{exc.limit}),请升级套餐", detail=f"配音库配额已满({exc.used}/{exc.limit}),请升级套餐",
) ) from exc
return SaveToLibraryResponse( return SaveToLibraryResponse(
id=item.id, id=item.id,
+8 -8
View File
@@ -141,8 +141,8 @@ def get_voice_clone(
use_case = GetVoiceCloneUseCase(repository) use_case = GetVoiceCloneUseCase(repository)
try: try:
profile = use_case.execute(clone_id, user_id) profile = use_case.execute(clone_id, user_id)
except VoiceCloneNotFoundError: except VoiceCloneNotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") from _e
return _to_response(profile) return _to_response(profile)
@@ -157,8 +157,8 @@ def get_voice_clone_status(
use_case = GetVoiceCloneStatusUseCase(repository) use_case = GetVoiceCloneStatusUseCase(repository)
try: try:
profile = use_case.execute(clone_id, user_id) profile = use_case.execute(clone_id, user_id)
except VoiceCloneNotFoundError: except VoiceCloneNotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") from _e
return VoiceCloneStatusResponse( return VoiceCloneStatusResponse(
id=profile.id, id=profile.id,
status=profile.status, status=profile.status,
@@ -201,13 +201,13 @@ def retry_voice_clone(
user_id = authenticated_user.user.id user_id = authenticated_user.user.id
try: try:
profile = workflow.retry_clone(clone_id, user_id) profile = workflow.retry_clone(clone_id, user_id)
except VoiceCloneNotFoundError: except VoiceCloneNotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice clone not found") from _e
except VoiceCloneNotRetryableError: except VoiceCloneNotRetryableError as _e:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail="Voice clone is not retryable (only failed clones can be retried)", detail="Voice clone is not retryable (only failed clones can be retried)",
) ) from _e
# 如果 profile 处于 processing 且有 task_id,触发 Celery 异步轮询 # 如果 profile 处于 processing 且有 task_id,触发 Celery 异步轮询
task_id = (profile.metadata or {}).get("cosyvoice_task_id", "") task_id = (profile.metadata or {}).get("cosyvoice_task_id", "")
+3 -3
View File
@@ -287,7 +287,7 @@ def create_voice(
raise HTTPException( raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS, status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail=f"配音库配额已满({exc.used}/{exc.limit}),请升级套餐", detail=f"配音库配额已满({exc.used}/{exc.limit}),请升级套餐",
) ) from exc
return _to_response(item, sign_url) return _to_response(item, sign_url)
@@ -317,8 +317,8 @@ def update_voice(
use_case = UpdateVoiceLibraryUseCase(voice_repository) use_case = UpdateVoiceLibraryUseCase(voice_repository)
try: try:
item = use_case.execute(command) item = use_case.execute(command)
except NotFoundError: except NotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Voice not found") from _e
return _to_response(item, sign_url) return _to_response(item, sign_url)
@@ -431,6 +431,8 @@ class EditPlanService:
"clips": List[EditPlanClip], "clips": List[EditPlanClip],
"generation_task_id": Optional[str], "generation_task_id": Optional[str],
"generation_task_status": Optional[str], "generation_task_status": Optional[str],
"progress": float,
"error_message": str,
} }
Raises: Raises:
@@ -442,17 +444,23 @@ class EditPlanService:
# 从 plan.config 中获取 generation_task_id # 从 plan.config 中获取 generation_task_id
generation_task_id = plan.config.get("generation_task_id") generation_task_id = plan.config.get("generation_task_id")
generation_task_status = None generation_task_status = None
progress = 0.0
error_message = ""
if generation_task_id: if generation_task_id:
task = self._generation_task_repo.get(generation_task_id) task = self._generation_task_repo.get(generation_task_id)
if task: if task:
generation_task_status = task.status.value if hasattr(task.status, "value") else task.status generation_task_status = task.status.value if hasattr(task.status, "value") else task.status
progress = getattr(task, "progress", 0.0) or 0.0
error_message = getattr(task, "error_message", "") or ""
return { return {
"plan": plan, "plan": plan,
"clips": clips, "clips": clips,
"generation_task_id": generation_task_id, "generation_task_id": generation_task_id,
"generation_task_status": generation_task_status, "generation_task_status": generation_task_status,
"progress": progress,
"error_message": error_message,
} }
def can_generate(self, plan_id: str) -> tuple[bool, str]: def can_generate(self, plan_id: str) -> tuple[bool, str]:
@@ -224,7 +224,7 @@ class PlanGeneratorService:
) )
order += 1 order += 1
# 剩余为 overlay # 剩余为 overlay
for i in range(1, n): for _ in range(1, n):
clips.append( clips.append(
EditPlanClip.create( EditPlanClip.create(
plan_id=plan_id, plan_id=plan_id,
@@ -237,7 +237,7 @@ class PlanGeneratorService:
elif editing_mode == EditingMode.VOICE_OVER.value: elif editing_mode == EditingMode.VOICE_OVER.value:
# N 个 main clipsB-roll # N 个 main clipsB-roll
for i in range(n): for _ in range(n):
clips.append( clips.append(
EditPlanClip.create( EditPlanClip.create(
plan_id=plan_id, plan_id=plan_id,
@@ -271,7 +271,7 @@ class PlanGeneratorService:
) )
order += 1 order += 1
# 剩余为 b_roll # 剩余为 b_roll
for i in range(2, n): for _ in range(2, n):
clips.append( clips.append(
EditPlanClip.create( EditPlanClip.create(
plan_id=plan_id, plan_id=plan_id,
@@ -284,7 +284,7 @@ class PlanGeneratorService:
else: else:
# ONE_TAKE: N 个 main clips # ONE_TAKE: N 个 main clips
for i in range(n): for _ in range(n):
clips.append( clips.append(
EditPlanClip.create( EditPlanClip.create(
plan_id=plan_id, plan_id=plan_id,
+17
View File
@@ -33,6 +33,7 @@
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7", "eslint-plugin-react-refresh": "^0.4.7",
"jsdom": "^24.1.0", "jsdom": "^24.1.0",
"prettier": "^3.0.0",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"vite": "^5.3.1", "vite": "^5.3.1",
"vitest": "^1.6.0" "vitest": "^1.6.0"
@@ -4828,6 +4829,22 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "3.9.5",
"resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.9.5.tgz",
"integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-format": { "node_modules/pretty-format": {
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+1
View File
@@ -42,6 +42,7 @@
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7", "eslint-plugin-react-refresh": "^0.4.7",
"jsdom": "^24.1.0", "jsdom": "^24.1.0",
"prettier": "^3.0.0",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"vite": "^5.3.1", "vite": "^5.3.1",
"vitest": "^1.6.0" "vitest": "^1.6.0"
+15 -6
View File
@@ -130,6 +130,8 @@ export interface EditPlan {
name: string; name: string;
status: EditPlanStatus; status: EditPlanStatus;
total_duration: number; total_duration: number;
/** 生成视频数量(后端 EditPlanResponse.result_count */
result_count: number;
config: EditPlanConfig; config: EditPlanConfig;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
@@ -161,14 +163,21 @@ export interface GenerateResponse {
clip_count: number; clip_count: number;
} }
/** 剪辑计划关联的生成记录 */ /** 剪辑计划关联的生成记录(实际是 GenerationTask 对象) */
export interface EditPlanGeneration { export interface EditPlanGeneration {
id: string; id: string; // 即 generation_task_id
edit_plan_id: string; source_edit_plan_id: string;
generation_task_id: string; template_id: string;
asset_ids: string[];
status: EditPlanStatus; status: EditPlanStatus;
created_at: string; progress: number;
updated_at: string; result_count: number;
error_message: string;
error_info: Record<string, unknown>;
logs: Array<Record<string, unknown>>;
retry_count: number;
created_at?: string;
updated_at?: string;
} }
/** 片段生成状态 */ /** 片段生成状态 */
+1
View File
@@ -1,5 +1,6 @@
/** /**
* 成品 / 视频相关 API * 成品 / 视频相关 API
* 包含:列表查询、复核状态、批量下载
* 后端无 /products 路由,实际从 /generation/tasks 端点获取数据 * 后端无 /products 路由,实际从 /generation/tasks 端点获取数据
*/ */
import apiClient from "./client"; import apiClient from "./client";
+9 -4
View File
@@ -16,17 +16,22 @@ import type { EditPlanConfig } from "./editPlans";
/** 模板条目(后端 TemplateResponse */ /** 模板条目(后端 TemplateResponse */
export interface TemplateItem { export interface TemplateItem {
id: string; id: string;
user_id?: string;
name: string; name: string;
description: string; description?: string;
mode?: string;
category: string; category: string;
tags?: string[]; tags?: string[];
target_duration: number; /** 预估时长(后端字段名 estimated_duration */
clip_count: number; estimated_duration?: number;
/** @deprecated 后端已改名为 estimated_duration,保留兼容 */
target_duration?: number;
clip_count?: number;
/** 使用次数 */ /** 使用次数 */
usage_count?: number; usage_count?: number;
thumbnail_url?: string; thumbnail_url?: string;
preview_url?: string; preview_url?: string;
is_active: boolean; is_active?: boolean;
is_favorite?: boolean; is_favorite?: boolean;
/** 素材规则(片段配置) */ /** 素材规则(片段配置) */
segments?: TemplateSegment[]; segments?: TemplateSegment[];
@@ -33,8 +33,9 @@ const formatSize = (bytes: number) => {
/** 格式化时长 */ /** 格式化时长 */
const formatDuration = (seconds?: number) => { const formatDuration = (seconds?: number) => {
if (!seconds) return "-"; if (!seconds) return "-";
const m = Math.floor(seconds / 60); const totalSec = Math.round(seconds);
const s = seconds % 60; const m = Math.floor(totalSec / 60);
const s = totalSec % 60;
return m > 0 ? `${m}${s}` : `${s}`; return m > 0 ? `${m}${s}` : `${s}`;
}; };
@@ -59,8 +59,9 @@ const formatSize = (bytes: number) => {
/** 格式化时长 */ /** 格式化时长 */
const formatDuration = (seconds?: number) => { const formatDuration = (seconds?: number) => {
if (!seconds) return "-"; if (!seconds) return "-";
const m = Math.floor(seconds / 60); const totalSec = Math.round(seconds);
const s = seconds % 60; const m = Math.floor(totalSec / 60);
const s = totalSec % 60;
return m > 0 ? `${m}${s}` : `${s}`; return m > 0 ? `${m}${s}` : `${s}`;
}; };
+13 -2
View File
@@ -86,8 +86,9 @@ const STATUS_CONFIG: Record<
/** 格式化时长 */ /** 格式化时长 */
const formatDuration = (seconds: number): string => { const formatDuration = (seconds: number): string => {
if (seconds <= 0) return "-"; if (seconds <= 0) return "-";
const m = Math.floor(seconds / 60); const totalSec = Math.round(seconds);
const s = seconds % 60; const m = Math.floor(totalSec / 60);
const s = totalSec % 60;
if (m === 0) return `${s}`; if (m === 0) return `${s}`;
return `${m}${s > 0 ? `${s}` : ""}`; return `${m}${s > 0 ? `${s}` : ""}`;
}; };
@@ -253,6 +254,16 @@ export default function EditPlans() {
<span className="plan-duration">{formatDuration(seconds)}</span> <span className="plan-duration">{formatDuration(seconds)}</span>
), ),
}, },
{
title: "视频数",
dataIndex: "result_count",
key: "result_count",
width: 80,
align: "center",
render: (count: number) => (
<span className="plan-result-count">{count > 0 ? count : "—"}</span>
),
},
{ {
title: "创建时间", title: "创建时间",
dataIndex: "created_at", dataIndex: "created_at",
@@ -3,8 +3,8 @@
* 四行布局:顶栏(42px) → 模式栏(48px) → 三栏主体 → 底栏(40px) * 四行布局:顶栏(42px) → 模式栏(48px) → 三栏主体 → 底栏(40px)
*/ */
import React, { useState, useCallback, useEffect, useRef } from "react"; import React, { useState, useCallback, useEffect, useRef } from "react";
import { useSearchParams, useNavigate } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import { message } from "antd"; import { message, Modal, Progress, Button } from "antd";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import type { import type {
EditingTemplate, EditingTemplate,
@@ -20,11 +20,23 @@ import {
getTemplateCategories, getTemplateCategories,
MODE_LABELS, MODE_LABELS,
} from "@/api/editingPlanner"; } from "@/api/editingPlanner";
import type { EditPlanGeneration, MediaAsset } from "@/api/editPlans"; import type {
EditPlanGeneration,
EditPlanConfig,
GeneratedVideo,
MediaAsset,
TransitionEffect,
} from "@/api/editPlans";
import { import {
getMediaAssets, getMediaAssets,
getEditPlanGenerations, getEditPlanGenerations,
generateCover, generateCover,
getEditPlan,
createEditPlan,
updateEditPlan,
generateEditPlan,
getGenerationStatus,
getGenerationTaskResults,
} from "@/api/editPlans"; } from "@/api/editPlans";
import { useUndoRedo } from "./hooks/useUndoRedo"; import { useUndoRedo } from "./hooks/useUndoRedo";
import type { import type {
@@ -33,6 +45,7 @@ import type {
TransitionConfig, TransitionConfig,
SpeedConfig, SpeedConfig,
TtsConfig, TtsConfig,
TtsMode,
TrimConfig, TrimConfig,
WatermarkConfig, WatermarkConfig,
IntroOutroConfig, IntroOutroConfig,
@@ -107,8 +120,8 @@ const FILTER_CATEGORIES = ["全部", "种草", "知识", "日常", "推荐"];
const EditingPlanner: React.FC = () => { const EditingPlanner: React.FC = () => {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const navigate = useNavigate();
const urlTemplateId = searchParams.get("templateId") || ""; const urlTemplateId = searchParams.get("templateId") || "";
const urlPlanId = searchParams.get("planId") || "";
/* ── 模板列表 ── */ /* ── 模板列表 ── */
const [templates, setTemplates] = useState<EditingTemplate[]>([]); const [templates, setTemplates] = useState<EditingTemplate[]>([]);
@@ -244,6 +257,21 @@ const EditingPlanner: React.FC = () => {
const [genHistory, setGenHistory] = useState<EditPlanGeneration[]>([]); const [genHistory, setGenHistory] = useState<EditPlanGeneration[]>([]);
const [genHistoryLoading, setGenHistoryLoading] = useState(false); const [genHistoryLoading, setGenHistoryLoading] = useState(false);
/* ── 剪辑计划(从列表页编辑进入时) ── */
const [loadedPlanId, setLoadedPlanId] = useState<string | null>(
urlPlanId || null,
);
/* ── 生成进度 ── */
const [generating, setGenerating] = useState(false);
const [genProgress, setGenProgress] = useState(0);
const [genTotalClips, setGenTotalClips] = useState(0);
const [genDoneClips, setGenDoneClips] = useState(0);
const [generated, setGenerated] = useState(false);
const [generatedVideos, setGeneratedVideos] = useState<GeneratedVideo[]>([]);
const [genError, setGenError] = useState<string | null>(null);
const genTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
/* ── 播放 ── */ /* ── 播放 ── */
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0); const [currentTime, setCurrentTime] = useState(0);
@@ -374,6 +402,85 @@ const EditingPlanner: React.FC = () => {
.catch(() => message.error("加载模板详情失败")); .catch(() => message.error("加载模板详情失败"));
}, [loadedTemplateId, resetClips]); }, [loadedTemplateId, resetClips]);
/**
* 加载已有剪辑计划数据到编辑器
* 从列表页"编辑"按钮进入时,URL 带 planId,需要还原计划配置
*/
useEffect(() => {
if (!loadedPlanId) return;
getEditPlan(loadedPlanId)
.then((plan) => {
// 设置关联的模板(触发模板加载 effect)
setLoadedTemplateId(plan.template_id);
// 还原基本信息
setDraftName(plan.name);
// 还原 config 中的编辑器状态
const cfg = plan.config;
if (cfg.title_config) {
setTitleSettings((prev) => ({
...prev,
aiAutoSelect: cfg.title_config!.ai_auto_select,
title: cfg.title_config!.content,
position: cfg.title_config!.position,
font: cfg.title_config!.font_preset,
size: cfg.title_config!.font_size,
color: cfg.title_config!.font_color || "#ffffff",
}));
}
if (cfg.subtitle_config) {
setSubtitleSettings((prev) => ({
...prev,
enabled: cfg.subtitle_config!.enabled,
position: (cfg.subtitle_config!.position ||
"bottom") as SubtitleStyleConfig["position"],
font: cfg.subtitle_config!.font,
fontSize: cfg.subtitle_config!.size,
fontColor: cfg.subtitle_config!.color || "#ffffff",
animation: cfg.subtitle_config!.animation,
}));
}
if (cfg.bgm_config) {
setBgmSettings((prev) => ({
...prev,
enabled: cfg.bgm_config!.enabled,
music_id: cfg.bgm_config!.music_id || "",
}));
}
// 还原片段 — 延迟设置,等模板加载 effect 先执行 resetClips
if (cfg.segments && cfg.segments.length > 0) {
const mapped: ClipData[] = cfg.segments.map((seg, idx) => ({
id: `seg-${idx}`,
template_segment_id: `seg-${idx}`,
type: (seg.material_type === "voiceover"
? "voice"
: "pip") as ClipType,
duration: (seg.duration_min + seg.duration_max) / 2,
startOffset: 0,
script_text: "",
order: seg.segment_order,
transition: seg.transition
? {
type: seg.transition.type as TransitionEffect["type"],
duration: seg.transition.duration,
}
: undefined,
speed: seg.playback_speed
? { rate: seg.playback_speed, pitchCorrection: true }
: undefined,
tts_config: seg.tts_config
? { ...seg.tts_config, mode: seg.tts_config.mode as TtsMode }
: undefined,
trim_config: seg.trim_config || undefined,
}));
setTimeout(() => resetClips(mapped), 100);
}
})
.catch(() => message.error("加载剪辑计划失败"));
}, [loadedPlanId, resetClips]);
/* ──────────── 计算 ──────────── */ /* ──────────── 计算 ──────────── */
const currentTemplate = templates.find((t) => t.id === loadedTemplateId); const currentTemplate = templates.find((t) => t.id === loadedTemplateId);
@@ -671,6 +778,65 @@ const EditingPlanner: React.FC = () => {
} }
}; };
/** 构建剪辑计划 config(编辑器状态 → API config */
const buildPlanConfig = (): EditPlanConfig => ({
title_config: {
ai_auto_select: titleSettings.aiAutoSelect,
content: titleSettings.title,
position: titleSettings.position,
font_preset: titleSettings.font,
font_color: titleSettings.color,
font_size: titleSettings.size,
},
subtitle_config: {
enabled: subtitleSettings.enabled,
position: subtitleSettings.position,
font: subtitleSettings.font,
color: subtitleSettings.fontColor,
size: subtitleSettings.fontSize,
animation: subtitleSettings.animation,
},
bgm_config: {
enabled: bgmSettings.enabled,
music_id: bgmSettings.music_id,
},
estimated_duration: totalDuration,
segments: clips.map((c, i) => ({
segment_order: i,
duration_min: Math.max(1, c.duration - 2),
duration_max: c.duration + 2,
material_type: c.type === "voice" ? "voiceover" : "video",
transition: c.transition
? { type: c.transition.type, duration: c.transition.duration }
: undefined,
playback_speed: c.speed ? c.speed.rate : undefined,
tts_config: c.tts_config
? {
mode: c.tts_config.mode,
text: c.tts_config.text,
voice_id: c.tts_config.voice_id,
speed: c.tts_config.speed,
pitch: c.tts_config.pitch,
volume: c.tts_config.volume,
subtitle_sync: c.tts_config.subtitle_sync,
}
: undefined,
trim_config: c.trim_config
? {
start_time: c.trim_config.start_time,
end_time: c.trim_config.end_time,
}
: undefined,
})),
watermark_config: { ...watermarkSettings },
intro_outro_config: { ...introOutroSettings },
pip_config: { ...pipSettings },
filter_config: { ...filterSettings },
green_screen_config: { ...chromaKeySettings },
sticker_config: { ...stickerSettings },
cover_config: { ...coverSettings },
});
/* 保存 — 无论是否已加载模板,都打开保存弹窗;未加载时创建新模板 */ /* 保存 — 无论是否已加载模板,都打开保存弹窗;未加载时创建新模板 */
const handleOpenSaveModal = () => { const handleOpenSaveModal = () => {
setSaveModalOpen(true); setSaveModalOpen(true);
@@ -763,94 +929,139 @@ const EditingPlanner: React.FC = () => {
}; };
/** /**
* 跳转到一键生成页面 * 剪辑计划生成
* 通过 URL SearchParams 传递 edit_plan_id 和完整 planConfigJSON 序列化) * 1. 有 planId → 更新计划配置 + 触发生成
* 一键生成页面从 params 解析配置,无需重复请求接口 * 2. 无 planId(从模板库直接进入)→ 先创建计划 + 触发生成
* 3. 触发生成后轮询状态,完成后获取视频结果
*/ */
const handleGoToGenerate = () => { const handleGoToGenerate = async () => {
const planConfig = { if (!loadedTemplateId) {
title_config: { message.warning("请先选择一个模板");
ai_auto_select: titleSettings.aiAutoSelect, return;
content: titleSettings.title, }
position: titleSettings.position, if (clips.length === 0) {
font_preset: titleSettings.font, message.warning("请先添加片段");
font_color: titleSettings.color, return;
font_size: titleSettings.size, }
bold: titleSettings.bold,
italic: titleSettings.italic, setGenerating(true);
stroke: titleSettings.stroke, setGenerated(false);
shadow: titleSettings.shadow, setGeneratedVideos([]);
}, setGenError(null);
subtitle_config: { setGenProgress(0);
enabled: subtitleSettings.enabled,
position: subtitleSettings.position, try {
font: subtitleSettings.font, const config = buildPlanConfig();
color: subtitleSettings.fontColor, let planId = loadedPlanId;
size: subtitleSettings.fontSize,
animation: subtitleSettings.animation, if (planId) {
}, // 已有计划 → 更新配置
bgm_config: { await updateEditPlan(planId, {
enabled: bgmSettings.enabled, config,
music_id: bgmSettings.music_id, total_duration: totalDuration,
}, status: "editing",
mode: currentMode, });
total_duration: totalDuration, } else {
segments: clips.map((c, i) => ({ // 无计划 → 创建新计划
order: i, const plan = await createEditPlan({
material_type: c.type === "voice" ? "voiceover" : "video", template_id: loadedTemplateId,
duration: c.duration, name: draftName || "未命名计划",
template_segment_id: c.template_segment_id, config,
script_text: c.script_text, total_duration: totalDuration,
voice_asset_id: c.voice_asset_id, });
voice_file_url: c.voice_file_url, planId = plan.id;
transition: c.transition setLoadedPlanId(planId);
? { type: c.transition.type, duration: c.transition.duration } // 更新 URL 参数(不刷新页面)
: undefined, const params = new URLSearchParams(window.location.search);
playback_speed: c.speed ? c.speed.rate : undefined, params.set("planId", planId);
tts_config: c.tts_config window.history.replaceState(null, "", `?${params.toString()}`);
? { }
mode: c.tts_config.mode,
text: c.tts_config.text, // 触发生成
voice_id: c.tts_config.voice_id, const genRes = await generateEditPlan(planId);
speed: c.tts_config.speed, setGenTotalClips(genRes.clip_count);
pitch: c.tts_config.pitch, message.info("已提交生成,等待处理...");
volume: c.tts_config.volume,
subtitle_sync: c.tts_config.subtitle_sync, // 开始轮询
} startPolling(planId);
: undefined, } catch (err) {
trim_config: c.trim_config console.error("[生成失败]", err);
? { setGenError("生成提交失败,请重试");
start_time: c.trim_config.start_time, setGenerating(false);
end_time: c.trim_config.end_time,
}
: undefined,
})),
watermark_config: { ...watermarkSettings },
intro_outro_config: { ...introOutroSettings },
pip_config: { ...pipSettings },
filter_config: { ...filterSettings },
green_screen_config: { ...chromaKeySettings },
sticker_config: { ...stickerSettings },
cover_config: { ...coverSettings },
};
const params = new URLSearchParams();
if (loadedTemplateId) {
params.set("edit_plan_id", loadedTemplateId);
} }
params.set("plan_config", JSON.stringify(planConfig));
navigate(`/app/generate?${params.toString()}`);
}; };
/** 轮询生成状态,每 2 秒一次 */
const startPolling = (planId: string) => {
const poll = async () => {
try {
const status = await getGenerationStatus(planId);
// 计算进度
const total = status.clips.length || genTotalClips;
const done = status.clips.filter(
(c) => c.status === "completed" || c.status === "failed",
).length;
setGenDoneClips(done);
setGenTotalClips(total);
setGenProgress(total > 0 ? Math.round((done / total) * 100) : 5);
if (status.plan_status === "completed") {
setGenProgress(100);
setGenerating(false);
setGenerated(true);
// 获取视频结果
if (status.generation_task_id) {
try {
const videos = await getGenerationTaskResults(
status.generation_task_id,
);
setGeneratedVideos(videos);
} catch (e) {
console.error("[获取视频结果失败]", e);
}
}
message.success("视频生成完成!");
return; // 停止轮询
}
if (status.plan_status === "failed") {
setGenerating(false);
setGenError("生成失败,请重试");
return; // 停止轮询
}
// 继续轮询
genTimerRef.current = setTimeout(poll, 2000);
} catch (err) {
console.error("[轮询状态失败]", err);
genTimerRef.current = setTimeout(poll, 5000); // 出错后 5 秒重试
}
};
// 首次延迟 2 秒后开始
genTimerRef.current = setTimeout(poll, 2000);
};
/** 清理轮询定时器 */
useEffect(() => {
return () => {
if (genTimerRef.current) clearTimeout(genTimerRef.current);
};
}, []);
/* 查看生成历史 */ /* 查看生成历史 */
const handleViewGenHistory = async () => { const handleViewGenHistory = async () => {
if (!loadedTemplateId) { const targetId = loadedPlanId || loadedTemplateId;
message.warning("请先加载一个模板"); if (!targetId) {
message.warning("请先加载一个模板或计划");
return; return;
} }
setGenHistoryOpen(true); setGenHistoryOpen(true);
setGenHistoryLoading(true); setGenHistoryLoading(true);
try { try {
const items = await getEditPlanGenerations(loadedTemplateId); const items = await getEditPlanGenerations(targetId);
setGenHistory(items); setGenHistory(items);
} catch { } catch {
message.error("加载生成历史失败"); message.error("加载生成历史失败");
@@ -899,8 +1110,9 @@ const EditingPlanner: React.FC = () => {
<button <button
className="ep-btn ep-btn-primary" className="ep-btn ep-btn-primary"
onClick={handleGoToGenerate} onClick={handleGoToGenerate}
disabled={generating}
> >
🎬 使 {loadedPlanId ? "🎬 生成视频" : "🎬 创建计划并生成"}
</button> </button>
</div> </div>
</div> </div>
@@ -1065,6 +1277,111 @@ const EditingPlanner: React.FC = () => {
onClose={() => setGenHistoryOpen(false)} onClose={() => setGenHistoryOpen(false)}
/> />
{/* ═══ 生成进度弹窗 ═══ */}
<Modal
title={genError ? "生成失败" : generated ? "生成完成" : "正在生成视频"}
open={generating || generated || !!genError}
footer={
generated
? [
<Button
key="close"
onClick={() => {
setGenerated(false);
setGenerating(false);
}}
>
</Button>,
generatedVideos.length > 0 && (
<Button
key="download"
type="primary"
onClick={() => {
const v = generatedVideos[0];
const url = v.download_url || v.file_url;
if (url) {
const a = document.createElement("a");
a.href = url;
a.download = v.name || "video.mp4";
a.target = "_blank";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
}}
>
</Button>
),
]
: null
}
closable={!generating}
maskClosable={false}
width={520}
>
{generating && (
<div style={{ padding: "16px 0" }}>
<Progress percent={genProgress} status="active" />
<p style={{ marginTop: 8, color: "var(--text-secondary)" }}>
{genDoneClips}/{genTotalClips}
</p>
<p style={{ color: "var(--text-secondary)", fontSize: 12 }}>
</p>
</div>
)}
{generated && generatedVideos.length > 0 && (
<div style={{ padding: "8px 0" }}>
<video
src={
generatedVideos[0].file_url || generatedVideos[0].download_url
}
controls
preload="metadata"
style={{ width: "100%", maxHeight: 320, borderRadius: 8 }}
/>
<p
style={{
marginTop: 8,
textAlign: "center",
color: "var(--text-secondary)",
}}
>
{generatedVideos[0].name}
</p>
</div>
)}
{generated && !generatedVideos.length && (
<div style={{ padding: "24px 0", textAlign: "center" }}>
<p></p>
<p style={{ color: "var(--text-secondary)", fontSize: 12 }}>
</p>
</div>
)}
{genError && (
<div
style={{
padding: "16px 0",
textAlign: "center",
color: "#ff4d4f",
}}
>
<p>{genError}</p>
<Button
onClick={() => {
setGenError(null);
setGenerating(false);
}}
>
</Button>
</div>
)}
</Modal>
{/* ═══ BGM 选择器 Drawer ═══ */} {/* ═══ BGM 选择器 Drawer ═══ */}
<BgmSelector <BgmSelector
open={bgmDrawerOpen} open={bgmDrawerOpen}
@@ -65,7 +65,7 @@ const GenerationHistoryModal: React.FC<GenerationHistoryModalProps> = ({
return ( return (
<tr key={gen.id} className="ep-gh-table-row"> <tr key={gen.id} className="ep-gh-table-row">
<td className="ep-gh-td ep-gh-td-id"> <td className="ep-gh-td ep-gh-td-id">
{gen.generation_task_id.slice(0, 8)}... {gen.id ? `${gen.id.slice(0, 8)}...` : "—"}
</td> </td>
<td className="ep-gh-td"> <td className="ep-gh-td">
<span className={`ep-gh-status-tag ${statusClass}`}> <span className={`ep-gh-status-tag ${statusClass}`}>
@@ -23,7 +23,6 @@ import {
getTemplate, getTemplate,
toggleFavoriteTemplate, toggleFavoriteTemplate,
copyTemplate, copyTemplate,
generateFromTemplate,
type TemplateItem, type TemplateItem,
type TemplateListParams, type TemplateListParams,
type TemplateSegment, type TemplateSegment,
@@ -91,10 +90,11 @@ const gradientForCategory = (category: string): string => {
}; };
/** 格式化时长 */ /** 格式化时长 */
const formatDuration = (seconds: number): string => { const formatDuration = (seconds: number | undefined | null): string => {
if (seconds <= 0) return "0秒"; if (!seconds || seconds <= 0) return "0秒";
const m = Math.floor(seconds / 60); const totalSec = Math.round(seconds);
const s = seconds % 60; const m = Math.floor(totalSec / 60);
const s = totalSec % 60;
if (m === 0) return `${s}`; if (m === 0) return `${s}`;
return `${m}${s > 0 ? `${s}` : ""}`; return `${m}${s > 0 ? `${s}` : ""}`;
}; };
@@ -237,7 +237,9 @@ const TemplateDetailModal: React.FC<TemplateDetailModalProps> = ({
{ {
key: "duration", key: "duration",
label: "目标时长", label: "目标时长",
children: formatDuration(template.target_duration), children: formatDuration(
template.estimated_duration ?? template.target_duration,
),
}, },
{ {
key: "clips", key: "clips",
@@ -409,7 +411,9 @@ const TemplateCard: React.FC<TemplateCardProps> = ({
<div className="xx-template-thumb-name">{template.name}</div> <div className="xx-template-thumb-name">{template.name}</div>
<div className="xx-template-thumb-meta"> <div className="xx-template-thumb-meta">
<span className="xx-template-thumb-duration"> <span className="xx-template-thumb-duration">
{formatDuration(template.target_duration)} {formatDuration(
template.estimated_duration ?? template.target_duration,
)}
</span> </span>
</div> </div>
<div className="xx-template-preview-hint"></div> <div className="xx-template-preview-hint"></div>
@@ -537,19 +541,6 @@ const TemplateLibrary: React.FC = () => {
}, },
}); });
// ── 从模板生成剪辑计划 mutation ──
const generateMutation = useMutation({
mutationFn: ({ templateId, name }: { templateId: string; name: string }) =>
generateFromTemplate(templateId, { name }),
onSuccess: (data) => {
message.success(`剪辑计划「${data.name}」已创建`);
navigate("/app/edit-plans");
},
onError: () => {
message.error("生成剪辑计划失败,请稍后重试");
},
});
/** 切换收藏 */ /** 切换收藏 */
const toggleFavorite = useCallback( const toggleFavorite = useCallback(
(id: string, e?: React.MouseEvent) => { (id: string, e?: React.MouseEvent) => {
@@ -582,15 +573,12 @@ const TemplateLibrary: React.FC = () => {
[copyMutation], [copyMutation],
); );
/** 使用模板 → 生成剪辑计划 */ /** 使用模板 → 进入剪辑编辑器配置 */
const handleUse = useCallback( const handleUse = useCallback(
(template: TemplateItem) => { (template: TemplateItem) => {
generateMutation.mutate({ navigate(`/app/editing-planner?templateId=${template.id}`);
templateId: template.id,
name: `基于「${template.name}」的剪辑计划`,
});
}, },
[generateMutation, navigate], [navigate],
); );
/** 搜索防抖处理 */ /** 搜索防抖处理 */
+1
View File
@@ -37,6 +37,7 @@ export default defineConfig({
}, },
}, },
build: { build: {
cache: true,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { manualChunks: {
@@ -181,9 +181,9 @@ def _validate_video_path(video_path: str, work_dir: Path) -> None:
resolved_work_dir = work_dir.resolve() resolved_work_dir = work_dir.resolve()
try: try:
resolved_path.relative_to(resolved_work_dir) resolved_path.relative_to(resolved_work_dir)
except ValueError: except ValueError as _e:
if not is_in_allowed_dirs(resolved_path): if not is_in_allowed_dirs(resolved_path):
raise PathSecurityError(f"视频路径不在允许目录内: {video_path[:80]}") raise PathSecurityError(f"视频路径不在允许目录内: {video_path[:80]}") from _e
# URL类型路径不做本地路径校验(由下载阶段的SSRF防护负责) # URL类型路径不做本地路径校验(由下载阶段的SSRF防护负责)
# 但检查扩展名 # 但检查扩展名
else: else:
+1 -1
View File
@@ -356,7 +356,7 @@ def check_duplicate_task(self: Task, generated_video_id: str) -> dict:
except Exception as e: except Exception as e:
logger.error(f"Duplicate check failed for {generated_video_id}: {str(e)}") logger.error(f"Duplicate check failed for {generated_video_id}: {str(e)}")
session.rollback() session.rollback()
raise self.retry(exc=e, countdown=60) raise self.retry(exc=e, countdown=60) from e
finally: finally:
session.close() session.close()
import shutil import shutil
@@ -191,9 +191,9 @@ def _validate_audio_path(audio_path: str, work_dir: Path) -> None:
resolved_work_dir = work_dir.resolve() resolved_work_dir = work_dir.resolve()
try: try:
resolved_path.relative_to(resolved_work_dir) resolved_path.relative_to(resolved_work_dir)
except ValueError: except ValueError as _e:
if not is_in_allowed_dirs(resolved_path): if not is_in_allowed_dirs(resolved_path):
raise PathSecurityError(f"音频路径不在允许目录内: {audio_path[:80]}") raise PathSecurityError(f"音频路径不在允许目录内: {audio_path[:80]}") from _e
# URL类型路径不做本地路径校验(由下载阶段的SSRF防护负责) # URL类型路径不做本地路径校验(由下载阶段的SSRF防护负责)
# 但检查扩展名 # 但检查扩展名
else: else:
@@ -129,8 +129,8 @@ def safe_resolve_path(
if not allow_outside: if not allow_outside:
try: try:
full_path.relative_to(base_dir) full_path.relative_to(base_dir)
except ValueError: except ValueError as _e:
raise PathSecurityError(f"路径遍历检测:路径 '{path_str}' 超出基路径 '{base_dir}' 范围") raise PathSecurityError(f"路径遍历检测:路径 '{path_str}' 超出基路径 '{base_dir}' 范围") from _e
# 扩展名校验 # 扩展名校验
if allowed_extensions is not None: if allowed_extensions is not None:
+1 -1
View File
@@ -403,7 +403,7 @@ class PiPEngine:
input_args: list[str] = [] input_args: list[str] = []
current_label = base_label current_label = base_label
for i, (input_label, layer, path) in enumerate(pip_sources): for i, (_input_label, layer, path) in enumerate(pip_sources):
# 添加输入 # 添加输入
input_args.extend(["-i", str(path)]) input_args.extend(["-i", str(path)])
@@ -359,7 +359,7 @@ class StickerEngine:
image_stickers: list[ImageStickerConfig] = [] image_stickers: list[ImageStickerConfig] = []
image_paths: list[str] = [] image_paths: list[str] = []
for i, s in enumerate(stickers): for _, s in enumerate(stickers):
try: try:
sticker_type = s.get("type", "image") sticker_type = s.get("type", "image")
z = int(s.get("z_index", 10)) z = int(s.get("z_index", 10))
@@ -682,6 +682,6 @@ def _validate_subtitle_path(subtitle_path: str, work_dir: Path) -> None:
resolved_work_dir = work_dir.resolve() resolved_work_dir = work_dir.resolve()
try: try:
resolved_path.relative_to(resolved_work_dir) resolved_path.relative_to(resolved_work_dir)
except ValueError: except ValueError as _e:
if not is_in_allowed_dirs(resolved_path): if not is_in_allowed_dirs(resolved_path):
raise PathSecurityError(f"字幕路径不在允许目录内: {subtitle_path[:80]}") raise PathSecurityError(f"字幕路径不在允许目录内: {subtitle_path[:80]}") from _e
@@ -1363,23 +1363,37 @@ class UnifiedRenderService:
# 单 clip 层,直接使用预处理标签 # 单 clip 层,直接使用预处理标签
layer_output_labels[layer.role] = layer_labels[0] layer_output_labels[layer.role] = layer_labels[0]
else: else:
# 多 clip 层,用 TransitionEngine 构建转场链
out_label = f"{layer.role}_merged" out_label = f"{layer.role}_merged"
# 计算该层使用的转场时长(取首个非零值,否则用默认) # 判断是否全部为硬切:是则用 concat filter,否则用 xfade 转场链
layer_dur = 0.0 all_cut = all(
for d in layer_transition_durations: t is None or t == "" or str(t).lower() == "cut"
if d > 0: for t in layer_transitions[1:] # 第一个 clip 的转场忽略
layer_dur = d
break
xfade_filter, _ = self._transition_engine.build_xfade_chain(
clip_durations=layer_durations,
clip_video_labels=layer_labels,
transitions=layer_transitions,
transition_duration=layer_dur if layer_dur > 0 else None,
output_label=out_label,
) )
if xfade_filter: if all_cut:
filter_parts.append(xfade_filter) # 全硬切:用 concat filter,性能远优于 xfade
concat_inputs = "".join(f"[{label}]" for label in layer_labels)
filter_parts.append(f"{concat_inputs}concat=n={len(layer_labels)}:v=1:a=0[{out_label}]")
logger.info(
"[unified-render] layer=%s clips=%d using concat (all hard-cut)",
layer.role,
len(layer_labels),
)
else:
# 有转场效果:用 TransitionEngine 构建 xfade 链
layer_dur = 0.0
for d in layer_transition_durations:
if d > 0:
layer_dur = d
break
xfade_filter, _ = self._transition_engine.build_xfade_chain(
clip_durations=layer_durations,
clip_video_labels=layer_labels,
transitions=layer_transitions,
transition_duration=layer_dur if layer_dur > 0 else None,
output_label=out_label,
)
if xfade_filter:
filter_parts.append(xfade_filter)
layer_output_labels[layer.role] = out_label layer_output_labels[layer.role] = out_label
# Step 3: 合成各层 # Step 3: 合成各层
+13 -1
View File
@@ -63,6 +63,18 @@ def compose_video(self, job_id: str, **kwargs):
resolver = get_render_engine_resolver() resolver = get_render_engine_resolver()
user_id = job.created_by_user_id or None user_id = job.created_by_user_id or None
engine = resolver.get_engine(user_id=user_id) engine = resolver.get_engine(user_id=user_id)
# 灰度期间打印详细 flag 配置,便于排查
config = resolver.get_config_snapshot()
logger.info(
"compose_video 引擎选择: job_id=%s engine=%s user_id=%s enabled=%s percentage=%s whitelist=%d default=%s",
job_id,
engine,
user_id,
config.get("enabled"),
config.get("percentage"),
len(config.get("whitelist", [])),
config.get("default_engine"),
)
if engine == "unified": if engine == "unified":
return _compose_with_unified_engine(self, job_service, job, plan_id, db) return _compose_with_unified_engine(self, job_service, job, plan_id, db)
@@ -78,7 +90,7 @@ def compose_video(self, job_id: str, **kwargs):
job_service.fail_job(job_id, str(exc)[:500]) job_service.fail_job(job_id, str(exc)[:500])
except Exception: except Exception:
logger.exception("更新 Job 失败状态时出错") logger.exception("更新 Job 失败状态时出错")
raise self.retry(exc=exc, countdown=60) raise self.retry(exc=exc, countdown=60) from exc
finally: finally:
db.close() db.close()
@@ -77,9 +77,21 @@ def _resolve_render_engine(user_id: str) -> str:
from video_processing.render_engine_resolver import get_render_engine_resolver from video_processing.render_engine_resolver import get_render_engine_resolver
resolver = get_render_engine_resolver() resolver = get_render_engine_resolver()
return resolver.get_engine(user_id=user_id) engine = resolver.get_engine(user_id=user_id)
# 灰度期间打印详细 flag 配置,便于排查
config = resolver.get_config_snapshot()
logger.info(
"edit_plan 引擎选择: user_id=%s engine=%s enabled=%s percentage=%s whitelist=%d default=%s",
user_id,
engine,
config.get("enabled"),
config.get("percentage"),
len(config.get("whitelist", [])),
config.get("default_engine"),
)
return engine
except Exception as exc: except Exception as exc:
logger.warning("获取渲染引擎配置失败,fallback 到 legacy: %s", exc) logger.warning("获取渲染引擎配置失败,fallback 到 legacy: %s", exc, exc_info=True)
return "legacy" return "legacy"
@@ -95,6 +107,14 @@ def _mark_plan_failed(plan_repo, plan_id, gen_task_repo, generation_task_id, err
gen_task.status = "failed" gen_task.status = "failed"
gen_task.error_message = error_msg gen_task.error_message = error_msg
gen_task.completed_at = datetime.now(timezone.utc) gen_task.completed_at = datetime.now(timezone.utc)
try:
gen_task.append_log(
stage="render_failed",
message=error_msg[:500],
level="ERROR",
)
except Exception:
pass
gen_task_repo.update(gen_task) gen_task_repo.update(gen_task)
@@ -148,9 +168,13 @@ def _finalize_render_success(
clip.mark_rendered() clip.mark_rendered()
clip_repo.update(clip) clip_repo.update(clip)
# 更新 EditPlan 状态为 completed # 更新 EditPlan 状态为 completed + 回写实际渲染时长 + 结果数
plan.config["rendered_url"] = output_url or "" plan.config["rendered_url"] = output_url or ""
plan.config["rendered_storage_key"] = storage_key plan.config["rendered_storage_key"] = storage_key
if hasattr(plan, "total_duration") and duration > 0:
plan.total_duration = duration
if hasattr(plan, "result_count"):
plan.result_count = 1
plan.mark_completed() plan.mark_completed()
plan_repo.update(plan) plan_repo.update(plan)
@@ -160,7 +184,15 @@ def _finalize_render_success(
if gen_task: if gen_task:
gen_task.status = "completed" gen_task.status = "completed"
gen_task.progress = 100.0 gen_task.progress = 100.0
gen_task.result_count = len(rendered_clip_ids) # 剪辑计划是多片段合成 1 个成片,result_count = 1
gen_task.result_count = 1
gen_task.append_log(
stage="render_complete",
message=f"渲染完成,输出时长 {duration:.1f}s",
level="INFO",
engine=engine,
clip_count=len(rendered_clip_ids),
)
gen_task.completed_at = datetime.now(timezone.utc) gen_task.completed_at = datetime.now(timezone.utc)
gen_task_repo.update(gen_task) gen_task_repo.update(gen_task)
@@ -365,6 +397,13 @@ def render_edit_plan(self, plan_id: str) -> dict:
if gen_task: if gen_task:
gen_task.status = "running" gen_task.status = "running"
gen_task.started_at = datetime.now(timezone.utc) gen_task.started_at = datetime.now(timezone.utc)
gen_task.append_log(
stage="render_start",
message=f"开始渲染,引擎 {engine},片段数 {len(clips)}",
level="INFO",
engine=engine,
clip_count=len(clips),
)
gen_task_repo.update(gen_task) gen_task_repo.update(gen_task)
# 3. 下载素材并构建 asset_path_map # 3. 下载素材并构建 asset_path_map
@@ -429,9 +468,28 @@ def render_edit_plan(self, plan_id: str) -> dict:
gen_task.status = "failed" gen_task.status = "failed"
gen_task.error_message = "所有片段素材下载失败" gen_task.error_message = "所有片段素材下载失败"
gen_task.completed_at = datetime.now(timezone.utc) gen_task.completed_at = datetime.now(timezone.utc)
gen_task.append_log(
stage="download_failed",
message="所有片段素材下载失败",
level="ERROR",
)
gen_task_repo.update(gen_task) gen_task_repo.update(gen_task)
return {"status": "error", "message": "所有片段素材下载失败"} return {"status": "error", "message": "所有片段素材下载失败"}
# 素材下载完成,记录日志
if generation_task_id:
gen_task = gen_task_repo.get(generation_task_id)
if gen_task:
gen_task.append_log(
stage="download_done",
message=f"素材下载完成,成功 {len(asset_path_map)} 个,失败 {len(failed_clip_ids)}",
level="INFO",
success_count=len(asset_path_map),
failed_count=len(failed_clip_ids),
)
gen_task.progress = 30.0
gen_task_repo.update(gen_task)
# 4. 根据引擎选择渲染方式 # 4. 根据引擎选择渲染方式
if engine == "unified": if engine == "unified":
result = _render_with_unified( result = _render_with_unified(
@@ -483,6 +541,15 @@ def render_edit_plan(self, plan_id: str) -> dict:
gen_task.status = "failed" gen_task.status = "failed"
gen_task.error_message = f"渲染异常: {type(exc).__name__}: {exc}" gen_task.error_message = f"渲染异常: {type(exc).__name__}: {exc}"
gen_task.completed_at = datetime.now(timezone.utc) gen_task.completed_at = datetime.now(timezone.utc)
try:
gen_task.append_log(
stage="render_failed",
message=f"渲染异常: {type(exc).__name__}: {str(exc)[:500]}",
level="ERROR",
exception_type=type(exc).__name__,
)
except Exception:
pass
gen_task_repo.update(gen_task) gen_task_repo.update(gen_task)
logger.info( logger.info(
"GenerationTask 已标记为 failed: task_id=%s plan_id=%s", "GenerationTask 已标记为 failed: task_id=%s plan_id=%s",
@@ -493,6 +560,6 @@ def render_edit_plan(self, plan_id: str) -> dict:
logger.warning( logger.warning(
"更新 GenerationTask 失败状态时异常: task_id=%s error=%s", generation_task_id, e, exc_info=True "更新 GenerationTask 失败状态时异常: task_id=%s error=%s", generation_task_id, e, exc_info=True
) )
raise self.retry(exc=exc, countdown=60) raise self.retry(exc=exc, countdown=60) from exc
return {"status": "error", "message": "数据库连接失败"} return {"status": "error", "message": "数据库连接失败"}
+16 -3
View File
@@ -787,10 +787,23 @@ def _resolve_render_engine(user_id: str) -> str:
from video_processing.render_engine_resolver import get_render_engine_resolver from video_processing.render_engine_resolver import get_render_engine_resolver
resolver = get_render_engine_resolver() resolver = get_render_engine_resolver()
return resolver.get_engine(user_id=user_id) engine = resolver.get_engine(user_id=user_id)
# 灰度期间打印详细 flag 配置,便于排查
config = resolver.get_config_snapshot()
logger.info(
"[渲染引擎] flag 解析: user_id=%s engine=%s enabled=%s percentage=%s whitelist=%d default=%s",
user_id,
engine,
config.get("enabled"),
config.get("percentage"),
len(config.get("whitelist", [])),
config.get("default_engine"),
)
return engine
except Exception as exc: except Exception as exc:
logger.warning("获取渲染引擎配置失败,fallback 到 unified: %s", exc) # 异常时 fallback 到 legacy(保守策略,与 edit_plan_generation 一致)
return ENGINE_UNIFIED logger.warning("获取渲染引擎配置失败,fallback 到 legacy: %s", exc, exc_info=True)
return ENGINE_LEGACY
# ── 旧引擎渲染(FFmpeg filter_complex) ──────────────────────────────────────── # ── 旧引擎渲染(FFmpeg filter_complex) ────────────────────────────────────────
@@ -63,7 +63,7 @@ def process_tts_synthesis(self: Task, job_id: str) -> dict:
if session is not None: if session is not None:
session.rollback() session.rollback()
# 超时重试,指数退避 # 超时重试,指数退避
raise self.retry(exc=e, countdown=30) raise self.retry(exc=e, countdown=30) from e
except CosyVoiceError as e: except CosyVoiceError as e:
logger.error(f"TTS synthesis failed for {job_id}: {e}") logger.error(f"TTS synthesis failed for {job_id}: {e}")
@@ -139,7 +139,7 @@ def process_tts_segment_synthesis(self: Task, job_id: str) -> dict:
logger.warning(f"TTS segment synthesis timeout for {job_id}: {e}") logger.warning(f"TTS segment synthesis timeout for {job_id}: {e}")
if session is not None: if session is not None:
session.rollback() session.rollback()
raise self.retry(exc=e, countdown=60) raise self.retry(exc=e, countdown=60) from e
except CosyVoiceError as e: except CosyVoiceError as e:
logger.error(f"TTS segment synthesis failed for {job_id}: {e}") logger.error(f"TTS segment synthesis failed for {job_id}: {e}")
+1 -1
View File
@@ -73,7 +73,7 @@ def process_voice_clone(self: Task, profile_id: str) -> dict:
if session is not None: if session is not None:
session.rollback() session.rollback()
# 超时属于临时性故障,延迟 30 秒后重试 # 超时属于临时性故障,延迟 30 秒后重试
raise self.retry(exc=e, countdown=30) raise self.retry(exc=e, countdown=30) from e
except CosyVoiceError as e: except CosyVoiceError as e:
logger.error(f"Voice clone failed for {profile_id}: {e}") logger.error(f"Voice clone failed for {profile_id}: {e}")
@@ -105,7 +105,7 @@ def extract_voice_task(self: Task, asset_id: str) -> dict:
except Exception as e: except Exception as e:
logger.error(f"Voice extraction failed for {asset_id}: {str(e)}") logger.error(f"Voice extraction failed for {asset_id}: {str(e)}")
session.rollback() session.rollback()
raise self.retry(exc=e, countdown=60) raise self.retry(exc=e, countdown=60) from e
finally: finally:
session.close() session.close()
import shutil import shutil
@@ -141,7 +141,7 @@ def extract_background_task(self: Task, asset_id: str) -> dict:
except Exception as e: except Exception as e:
logger.error(f"Background extraction failed for {asset_id}: {str(e)}") logger.error(f"Background extraction failed for {asset_id}: {str(e)}")
session.rollback() session.rollback()
raise self.retry(exc=e, countdown=60) raise self.retry(exc=e, countdown=60) from e
finally: finally:
session.close() session.close()
import shutil import shutil
+8
View File
@@ -990,6 +990,14 @@
"type": "FLOAT", "type": "FLOAT",
"unique": false "unique": false
}, },
{
"index": false,
"name": "result_count",
"nullable": false,
"primary_key": false,
"type": "INTEGER",
"unique": false
},
{ {
"index": false, "index": false,
"name": "config", "name": "config",
+1 -1
View File
@@ -113,7 +113,7 @@ RUN chmod +x /usr/local/bin/entrypoint-worker.sh
# 设置 Python 路径 # 设置 Python 路径
ENV PATH="/opt/venv/bin:$PATH" ENV PATH="/opt/venv/bin:$PATH"
ENV PYTHONPATH=/app ENV PYTHONPATH=/app:/app/packages
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV APP_VERSION=$APP_VERSION ENV APP_VERSION=$APP_VERSION
+3
View File
@@ -100,6 +100,7 @@ class SQLAlchemyEditPlanRepository:
name=plan.name, name=plan.name,
status=plan.status, status=plan.status,
total_duration=plan.total_duration, total_duration=plan.total_duration,
result_count=plan.result_count,
source_edit_plan_id=plan.source_edit_plan_id or None, source_edit_plan_id=plan.source_edit_plan_id or None,
project_id=plan.project_id or "", project_id=plan.project_id or "",
created_by_user_id=plan.created_by_user_id or "", created_by_user_id=plan.created_by_user_id or "",
@@ -119,6 +120,7 @@ class SQLAlchemyEditPlanRepository:
model.name = plan.name model.name = plan.name
model.status = plan.status model.status = plan.status
model.total_duration = plan.total_duration model.total_duration = plan.total_duration
model.result_count = plan.result_count
model.source_edit_plan_id = plan.source_edit_plan_id or None model.source_edit_plan_id = plan.source_edit_plan_id or None
model.project_id = plan.project_id or "" model.project_id = plan.project_id or ""
model.created_by_user_id = plan.created_by_user_id or "" model.created_by_user_id = plan.created_by_user_id or ""
@@ -152,6 +154,7 @@ class SQLAlchemyEditPlanRepository:
name=model.name, name=model.name,
status=EditPlanStatus(model.status) if model.status else EditPlanStatus.DRAFT, status=EditPlanStatus(model.status) if model.status else EditPlanStatus.DRAFT,
total_duration=model.total_duration or 0.0, total_duration=model.total_duration or 0.0,
result_count=int(model.result_count or 0),
source_edit_plan_id=model.source_edit_plan_id or "", source_edit_plan_id=model.source_edit_plan_id or "",
project_id=model.project_id or "", project_id=model.project_id or "",
created_by_user_id=model.created_by_user_id or "", created_by_user_id=model.created_by_user_id or "",
+3 -1
View File
@@ -1,9 +1,10 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any
from sqlalchemy import JSON, Boolean, Column, DateTime, Float, Integer, String, Text, UniqueConstraint from sqlalchemy import JSON, Boolean, Column, DateTime, Float, Integer, String, Text, UniqueConstraint
from sqlalchemy.orm import declarative_base from sqlalchemy.orm import declarative_base
Base = declarative_base() Base: Any = declarative_base()
class UserModel(Base): class UserModel(Base):
@@ -148,6 +149,7 @@ class EditPlanModel(Base):
name = Column(String(200), nullable=False) name = Column(String(200), nullable=False)
status = Column(String(20), nullable=False, default="draft", index=True) status = Column(String(20), nullable=False, default="draft", index=True)
total_duration = Column(Float, nullable=False, default=0.0) total_duration = Column(Float, nullable=False, default=0.0)
result_count = Column(Integer, nullable=False, default=0)
config = Column(JSON, nullable=False, default=dict) config = Column(JSON, nullable=False, default=dict)
source_edit_plan_id = Column(String(36), nullable=True, index=True) source_edit_plan_id = Column(String(36), nullable=True, index=True)
project_id = Column(String(36), nullable=False, default="", index=True) project_id = Column(String(36), nullable=False, default="", index=True)
+3 -3
View File
@@ -147,10 +147,10 @@ class JWTService:
algorithms=[self.config.ALGORITHM], algorithms=[self.config.ALGORITHM],
) )
return payload return payload
except ExpiredSignatureError: except ExpiredSignatureError as _e:
raise ExpiredSignatureError("Token has expired") raise ExpiredSignatureError("Token has expired") from _e
except InvalidTokenError as e: except InvalidTokenError as e:
raise InvalidTokenError(f"Invalid token: {str(e)}") raise InvalidTokenError(f"Invalid token: {str(e)}") from e
def verify_access_token(self, token: str) -> Dict[str, Any]: def verify_access_token(self, token: str) -> Dict[str, Any]:
""" """
+2 -2
View File
@@ -656,8 +656,8 @@ class CosyVoiceService:
code = body.get("code", "") code = body.get("code", "")
message = body.get("message", "") message = body.get("message", "")
raise CosyVoiceError(f"CosyVoice API 参数错误: HTTP 400, " f"code={code}, message={message}") raise CosyVoiceError(f"CosyVoice API 参数错误: HTTP 400, " f"code={code}, message={message}")
except ValueError: except ValueError as _e:
raise CosyVoiceError(f"CosyVoice API 调用失败: HTTP 400, body={body_text}") raise CosyVoiceError(f"CosyVoice API 调用失败: HTTP 400, body={body_text}") from _e
elif response.status_code >= 500: elif response.status_code >= 500:
# 服务端错误,可重试 # 服务端错误,可重试
last_error = CosyVoiceError(f"CosyVoice API 服务端错误: HTTP {response.status_code}") last_error = CosyVoiceError(f"CosyVoice API 服务端错误: HTTP {response.status_code}")
+4 -4
View File
@@ -78,16 +78,16 @@ class AudioMerger:
run_ffmpeg(cmd, timeout=120) run_ffmpeg(cmd, timeout=120)
except CalledProcessError as e: except CalledProcessError as e:
logger.error(f"FFmpeg 合并失败: stderr={e.stderr}") logger.error(f"FFmpeg 合并失败: stderr={e.stderr}")
raise AudioMergeError(f"FFmpeg 合并失败: {str(e)[:500]}") raise AudioMergeError(f"FFmpeg 合并失败: {str(e)[:500]}") from e
with open(output_path, "rb") as f: with open(output_path, "rb") as f:
return f.read() return f.read()
except TimeoutExpired: except TimeoutExpired as _e:
raise AudioMergeError("FFmpeg 合并超时(120 秒)") raise AudioMergeError("FFmpeg 合并超时(120 秒)") from _e
except AudioMergeError: except AudioMergeError:
raise raise
except Exception as e: except Exception as e:
raise AudioMergeError(f"音频合并失败: {e}") raise AudioMergeError(f"音频合并失败: {e}") from e
finally: finally:
shutil.rmtree(temp_dir, ignore_errors=True) shutil.rmtree(temp_dir, ignore_errors=True)
Regular → Executable
+3
View File
@@ -42,6 +42,7 @@ class EditPlan:
name: str name: str
status: EditPlanStatus = EditPlanStatus.DRAFT status: EditPlanStatus = EditPlanStatus.DRAFT
total_duration: float = 0.0 total_duration: float = 0.0
result_count: int = 0
source_edit_plan_id: str = "" source_edit_plan_id: str = ""
project_id: str = "" project_id: str = ""
created_by_user_id: str = "" created_by_user_id: str = ""
@@ -57,6 +58,7 @@ class EditPlan:
*, *,
config: dict[str, Any] | None = None, config: dict[str, Any] | None = None,
total_duration: float = 0.0, total_duration: float = 0.0,
result_count: int = 0,
source_edit_plan_id: str = "", source_edit_plan_id: str = "",
project_id: str = "", project_id: str = "",
created_by_user_id: str = "", created_by_user_id: str = "",
@@ -73,6 +75,7 @@ class EditPlan:
name=clean_name, name=clean_name,
status=EditPlanStatus.DRAFT, status=EditPlanStatus.DRAFT,
total_duration=total_duration, total_duration=total_duration,
result_count=result_count,
source_edit_plan_id=source_edit_plan_id.strip(), source_edit_plan_id=source_edit_plan_id.strip(),
project_id=project_id.strip(), project_id=project_id.strip(),
created_by_user_id=created_by_user_id.strip(), created_by_user_id=created_by_user_id.strip(),
+2 -2
View File
@@ -170,8 +170,8 @@ class GenerationTask:
if isinstance(new_status, str): if isinstance(new_status, str):
try: try:
new_status = GenerationTaskStatus(new_status) new_status = GenerationTaskStatus(new_status)
except ValueError: except ValueError as _e:
raise ValueError(f"无效状态: {new_status}") raise ValueError(f"无效状态: {new_status}") from _e
allowed = _VALID_TRANSITIONS.get(self.status, set()) allowed = _VALID_TRANSITIONS.get(self.status, set())
if new_status not in allowed: if new_status not in allowed:
+4 -4
View File
@@ -147,8 +147,8 @@ class Job:
if isinstance(job_type, str): if isinstance(job_type, str):
try: try:
job_type = JobType(job_type) job_type = JobType(job_type)
except ValueError: except ValueError as _e:
raise ValueError(f"不支持的任务类型: {job_type}") raise ValueError(f"不支持的任务类型: {job_type}") from _e
return cls( return cls(
id=uuid4().hex, id=uuid4().hex,
@@ -182,8 +182,8 @@ class Job:
if isinstance(new_status, str): if isinstance(new_status, str):
try: try:
new_status = JobStatus(new_status) new_status = JobStatus(new_status)
except ValueError: except ValueError as _e:
raise ValueError(f"无效状态: {new_status}") raise ValueError(f"无效状态: {new_status}") from _e
allowed = _VALID_TRANSITIONS.get(self.status, set()) allowed = _VALID_TRANSITIONS.get(self.status, set())
if new_status not in allowed: if new_status not in allowed:
+2 -2
View File
@@ -193,8 +193,8 @@ class TTSJob:
if isinstance(new_status, str): if isinstance(new_status, str):
try: try:
new_status = TTSJobStatus(new_status) new_status = TTSJobStatus(new_status)
except ValueError: except ValueError as _e:
raise ValueError(f"无效状态: {new_status}") raise ValueError(f"无效状态: {new_status}") from _e
allowed = _VALID_TRANSITIONS.get(self.status, set()) allowed = _VALID_TRANSITIONS.get(self.status, set())
if new_status not in allowed: if new_status not in allowed:
+2 -2
View File
@@ -177,8 +177,8 @@ class VoiceCloneProfile:
if isinstance(new_status, str): if isinstance(new_status, str):
try: try:
new_status = VoiceCloneStatus(new_status) new_status = VoiceCloneStatus(new_status)
except ValueError: except ValueError as _e:
raise ValueError(f"无效状态: {new_status}") raise ValueError(f"无效状态: {new_status}") from _e
allowed = _VALID_TRANSITIONS.get(self.status, set()) allowed = _VALID_TRANSITIONS.get(self.status, set())
if new_status not in allowed: if new_status not in allowed:
+2 -2
View File
@@ -114,7 +114,7 @@ class SharedStorageService:
self.bucket.put_object(storage_key, file_or_path, headers={"Content-Type": content_type}) self.bucket.put_object(storage_key, file_or_path, headers={"Content-Type": content_type})
return f"{self.public_url}/{storage_key}" return f"{self.public_url}/{storage_key}"
except Exception as e: except Exception as e:
raise Exception(f"Failed to upload file to OSS: {e}") raise Exception(f"Failed to upload file to OSS: {e}") from e
def get_url(self, storage_key: str) -> str: def get_url(self, storage_key: str) -> str:
"""Get public URL for a file.""" """Get public URL for a file."""
@@ -129,7 +129,7 @@ class SharedStorageService:
os.makedirs(os.path.dirname(local_path), exist_ok=True) os.makedirs(os.path.dirname(local_path), exist_ok=True)
self.bucket.get_object_to_file(storage_key, local_path) self.bucket.get_object_to_file(storage_key, local_path)
except Exception as e: except Exception as e:
raise Exception(f"Failed to download file from OSS: {e}") raise Exception(f"Failed to download file from OSS: {e}") from e
def get_download_url(self, storage_key_or_url: str, expires_seconds: int = 3600) -> str: def get_download_url(self, storage_key_or_url: str, expires_seconds: int = 3600) -> str:
"""Get signed download URL.""" """Get signed download URL."""
+16 -12
View File
@@ -66,18 +66,19 @@ exclude = [
] ]
[tool.ruff.lint] [tool.ruff.lint]
# 当前阶段:摸底模式,规则集与原flake8对齐 # 正式替换 flake8规则集与原 flake8 完全对齐
# 后续迭代计划: # 后续迭代计划:
# Phase 1: 修完 bugbear 后正式替换 flake8 # Phase 2: 加入 B (flake8-bugbear),修完后升级为阻断级
# Phase 2: 启用 UP(pyupgrade) + SIM(simplify) # Phase 3: 启用 UP(pyupgrade) + SIM(simplify)
# Phase 3: 启用 RET(return) + ARG(unused-args) # Phase 4: 启用 RET(return) + ARG(unused-args)
select = [ select = [
"E", # pycodestyle errors(同flake8 "E", # pycodestyle errors(同 flake8
"F", # pyflakes(同flake8 "F", # pyflakes(同 flake8
"W", # pycodestyle warnings(同flake8 "W", # pycodestyle warnings(同 flake8
"B", # flake8-bugbear新增,摸底用 "B", # flake8-bugbearP0-5 Step 2 已完成修复
] ]
# 与原 setup.cfg flake8 配置对齐,确保不新增阻断 # 与原 setup.cfg + .flake8 的 flake8 配置完全对齐
# 注意:W503 在 ruff≥0.14 中已被移除(行为变默认),故不列入
ignore = [ ignore = [
"E203", "E203",
"E501", # line-too-longblack管) "E501", # line-too-longblack管)
@@ -86,16 +87,19 @@ ignore = [
"E722", # bare-except "E722", # bare-except
"W291", "W291",
"W293", "W293",
"B008", # function-call-in-default-argumentFastAPI 依赖注入模式,大量使用)
"F401", # unused-import "F401", # unused-import
"F403", "F403",
"F405", "F405",
"F841", # unused-variable "F841", # unused-variable
"B008", # do-not-perform-callback-from-argfastapi依赖注入)
] ]
[tool.ruff.lint.per-file-ignores] [tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401", "F403", "F405"] "__init__.py" = ["F401", "F403", "F405"]
"tests/*" = ["E402", "F401", "F841"] "tests/*" = ["E402", "F401", "F821", "F841"]
"packages/ports/*" = ["E301"] "packages/ports/*" = ["E301"] # E704 在 ruff≥0.14 已移除
"apps/api/app/api/routes/auth.py" = ["ALL"]
"apps/api/app/api/routes/workspaces.py" = ["ALL"]
"apps/api/app/middleware/auth.py" = ["ALL"]
"apps/*/migrations/*" = ["ALL"] "apps/*/migrations/*" = ["ALL"]
"alembic/*" = ["ALL"] "alembic/*" = ["ALL"]
Regular → Executable
+1 -1
View File
@@ -3,7 +3,7 @@
# 代码质量 # 代码质量
black==26.5.1 black==26.5.1
isort==8.0.1 isort==8.0.1
flake8==7.3.0 ruff==0.14.0
bandit==1.9.4 bandit==1.9.4
# 测试 # 测试
+12
View File
@@ -60,6 +60,9 @@ fi
# 默认只读不写,防止 feature 分支污染主缓存 # 默认只读不写,防止 feature 分支污染主缓存
# 只有 develop/main 分支才写回缓存 # 只有 develop/main 分支才写回缓存
BRANCH_NAME="${GITHUB_REF_NAME:-${CI_COMMIT_BRANCH:-unknown}}" BRANCH_NAME="${GITHUB_REF_NAME:-${CI_COMMIT_BRANCH:-unknown}}"
# 清理本地旧镜像
docker rmi -f "$API_IMAGE" "$API_LATEST" 2>/dev/null || true
if [ "$USE_CACHE" -eq 1 ]; then if [ "$USE_CACHE" -eq 1 ]; then
docker buildx build \ docker buildx build \
--build-arg APP_VERSION="$VERSION" \ --build-arg APP_VERSION="$VERSION" \
@@ -89,6 +92,9 @@ build_with_cache() {
echo " cache: read-only from ${CACHE_REGISTRY}/${IMG_NAME}-cache:${CACHE_TAG_PRIMARY}" echo " cache: read-only from ${CACHE_REGISTRY}/${IMG_NAME}-cache:${CACHE_TAG_PRIMARY}"
fi fi
# 清理本地旧镜像,避免 buildx --load 报 already exists 错误
docker rmi -f "$IMG_NAME:$VERSION" 2>/dev/null || true
if [ "$USE_CACHE" -eq 1 ]; then if [ "$USE_CACHE" -eq 1 ]; then
if [ -n "$CACHE_TO" ]; then if [ -n "$CACHE_TO" ]; then
docker buildx build \ docker buildx build \
@@ -119,6 +125,9 @@ build_with_cache "api" "infra/docker/api.Dockerfile" \
docker tag "$API_IMAGE" "$API_LATEST" docker tag "$API_IMAGE" "$API_LATEST"
echo "=== Building Worker image ===" echo "=== Building Worker image ==="
# 清理本地旧镜像
docker rmi -f "$WORKER_IMAGE" "$WORKER_LATEST" 2>/dev/null || true
if [ "$USE_CACHE" -eq 1 ]; then if [ "$USE_CACHE" -eq 1 ]; then
docker buildx build \ docker buildx build \
--build-arg APP_VERSION="$VERSION" \ --build-arg APP_VERSION="$VERSION" \
@@ -148,6 +157,9 @@ docker run --rm \
test -f apps/web/dist/index.html test -f apps/web/dist/index.html
# 清理本地旧镜像
docker rmi -f "$WEB_IMAGE" 2>/dev/null || true
if [ "$USE_CACHE" -eq 1 ]; then if [ "$USE_CACHE" -eq 1 ]; then
docker buildx build \ docker buildx build \
--cache-from "type=registry,ref=${CACHE_REGISTRY}/web-cache:${CACHE_TAG_PRIMARY},ignore-error=true" \ --cache-from "type=registry,ref=${CACHE_REGISTRY}/web-cache:${CACHE_TAG_PRIMARY},ignore-error=true" \
+32 -22
View File
@@ -30,10 +30,13 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import json
import os import os
import re import re
import subprocess import subprocess
import sys import sys
import urllib.error
import urllib.request
from pathlib import Path from pathlib import Path
from typing import List, Tuple from typing import List, Tuple
@@ -99,30 +102,37 @@ def extract_upgrade_content(content: str) -> str:
def get_new_migrations_via_diff(diff_target: str) -> List[Path]: def get_new_migrations_via_diff(diff_target: str) -> List[Path]:
""" """
通过 git diff 对比目标分支/commit找出 alembic/versions/ 下新增的迁移文件 通过 Gitea API 对比目标分支找出 alembic/versions/ 下新增的迁移文件
只包含新增文件A状态不包含修改或删除的文件 不依赖本地 git避免 CI 环境下 git 操作不稳定的问题
""" """
api_url = os.environ.get("GITHUB_API_URL", "")
repo = os.environ.get("GITHUB_REPOSITORY", "")
token = os.environ.get("GITHUB_TOKEN", "")
branch = diff_target.replace("origin/", "")
if not api_url or not repo or not token:
print("⚠️ CI 环境变量不完整,降级为检查所有迁移文件")
return sorted(ALEMBIC_VERSIONS_DIR.glob("*.py"))
try: try:
result = subprocess.run( url = f"{api_url}/repos/{repo}/contents/alembic/versions?ref={branch}"
[ req = urllib.request.Request(url, headers={"Authorization": f"token {token}"})
"git", with urllib.request.urlopen(req, timeout=15) as resp:
"diff", data = json.loads(resp.read().decode())
"--name-only",
"--diff-filter=A", remote_files = {item["name"] for item in data if item["name"].endswith(".py")}
diff_target, local_files = {f.name for f in ALEMBIC_VERSIONS_DIR.glob("*.py")}
"HEAD", new_file_names = sorted(local_files - remote_files)
"--",
"alembic/versions/", if new_file_names:
], result = [ALEMBIC_VERSIONS_DIR / f for f in new_file_names]
cwd=str(REPO_ROOT), print(f" API 对比 {branch} 分支,发现 {len(result)} 个新增迁移)")
capture_output=True, return result
text=True, else:
check=True, print(f" API 对比 {branch} 分支,无新增迁移)")
) return []
files = [line.strip() for line in result.stdout.strip().split("\n") if line.strip()] except Exception as e:
return [REPO_ROOT / f for f in files] print(f"⚠️ API 获取迁移列表失败:{e}")
except subprocess.CalledProcessError as e:
print(f"⚠️ git diff 失败({diff_target}):{e.stderr.strip()}")
print(" 降级为检查所有迁移文件") print(" 降级为检查所有迁移文件")
return sorted(ALEMBIC_VERSIONS_DIR.glob("*.py")) return sorted(ALEMBIC_VERSIONS_DIR.glob("*.py"))
+91
View File
@@ -0,0 +1,91 @@
#!/bin/bash
# 通用Docker镜像构建+推送脚本(local cache为主 + registry cache兜底)
# M-2优化:解决registry缓存导入慢(247s)和推送不稳定问题
# 用法: docker_build_push.sh <Dockerfile> <image_tag> <cache_ref> [build_arg...]
set -eu
DOCKERFILE="$1"
IMAGE_TAG="$2"
CACHE_REF="$3"
shift 3
BUILD_ARGS=""
for arg in "$@"; do
BUILD_ARGS="$BUILD_ARGS --build-arg $arg"
done
if ! docker buildx inspect ci-builder > /dev/null 2>&1; then
docker buildx create --use --name ci-builder --driver docker-container
echo "Created ci-builder"
else
docker buildx use ci-builder
echo "Using existing ci-builder"
fi
docker buildx inspect --bootstrap
# 从cache_ref中提取缓存名称(如 api-cache:develop -> api-cache-develop
CACHE_NAME=$(echo "$CACHE_REF" | tr '/' '_' | tr ':' '-')
LOCAL_CACHE_DIR="/tmp/buildx-cache/${CACHE_NAME}"
mkdir -p "$LOCAL_CACHE_DIR"
# 缓存源:local优先,registry兜底
CACHE_FROM_LOCAL="type=local,src=${LOCAL_CACHE_DIR}"
CACHE_FROM_REGISTRY="type=registry,ref=${CACHE_REF},ignore-error=true"
# 本地缓存目标(必选,mode=max最大化命中率)
CACHE_TO_LOCAL="type=local,dest=${LOCAL_CACHE_DIR},mode=max"
echo "=== Step 1: Build & push image (local cache read-write + registry read) ==="
echo "Local cache: ${LOCAL_CACHE_DIR}"
echo "Registry cache: ${CACHE_REF}"
echo ""
docker buildx build \
$BUILD_ARGS \
--cache-from "${CACHE_FROM_LOCAL}" \
--cache-from "${CACHE_FROM_REGISTRY}" \
--cache-to "${CACHE_TO_LOCAL}" \
-f "${DOCKERFILE}" \
-t "${IMAGE_TAG}" \
--push \
.
echo ""
echo "Image pushed: ${IMAGE_TAG}"
echo "Local cache updated"
echo ""
echo "=== Step 2: Sync registry cache (best effort, retries 3x) ==="
CACHE_TO_REGISTRY="type=registry,ref=${CACHE_REF},mode=max,compression=zstd"
MAX_RETRIES=3
SUCCESS=0
for attempt in $(seq 1 $MAX_RETRIES); do
echo "Registry cache sync attempt $attempt/$MAX_RETRIES"
if docker buildx build \
$BUILD_ARGS \
--cache-from "${CACHE_FROM_LOCAL}" \
--cache-to "${CACHE_TO_REGISTRY}" \
-f "${DOCKERFILE}" \
-t "${IMAGE_TAG}" \
--push \
.; then
echo "Registry cache synced (attempt $attempt)"
SUCCESS=1
break
else
echo "Registry cache sync failed (attempt $attempt)"
if [ $attempt -lt $MAX_RETRIES ]; then
WAIT=$((attempt * 5))
echo "Retrying in ${WAIT}s..."
sleep $WAIT
fi
fi
done
if [ $SUCCESS -eq 0 ]; then
echo "WARNING: Registry cache sync failed after $MAX_RETRIES attempts (non-fatal, local cache still works)"
fi
echo ""
echo "Build completed: ${IMAGE_TAG}"
+4 -4
View File
@@ -7,7 +7,7 @@
# 环境变量: # 环境变量:
# IMAGE_TAG - 镜像版本 tag(如 commit SHA 或分支名) # IMAGE_TAG - 镜像版本 tag(如 commit SHA 或分支名)
# REGISTRY_TOKEN - Registry 访问令牌 # REGISTRY_TOKEN - Registry 访问令牌
# REGISTRY - Registry 地址(默认 git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas # REGISTRY - Registry 地址(默认 xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com/xiaoxiakeji
# REGISTRY_USER - Registry 用户名(默认 xiaoxia # REGISTRY_USER - Registry 用户名(默认 xiaoxia
# ENV_FILE - 环境变量文件路径 # ENV_FILE - 环境变量文件路径
# GENERATED_DIR - 生成文件目录 # GENERATED_DIR - 生成文件目录
@@ -16,9 +16,9 @@
set -eu set -eu
IMAGE_TAG="${IMAGE_TAG:-}" IMAGE_TAG="${IMAGE_TAG:-}"
REGISTRY="${REGISTRY:-git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas}" REGISTRY="${REGISTRY:-xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com/xiaoxiakeji}"
REGISTRY_USER="${REGISTRY_USER:-xiaoxia}" REGISTRY_USER="${ACR_USERNAME:-${REGISTRY_USER:-nick0415343655}}"
REGISTRY_TOKEN="${REGISTRY_TOKEN:-}" REGISTRY_TOKEN="${ACR_PASSWORD:-${REGISTRY_TOKEN:-}}"
ENV_FILE="${ENV_FILE:-/var/lib/xiaoxia-saas-staging/.env}" ENV_FILE="${ENV_FILE:-/var/lib/xiaoxia-saas-staging/.env}"
GENERATED_DIR="${GENERATED_DIR:-/var/lib/xiaoxia-saas-staging/generated}" GENERATED_DIR="${GENERATED_DIR:-/var/lib/xiaoxia-saas-staging/generated}"
+2 -2
View File
@@ -137,7 +137,7 @@ class PerfAssert:
result = PerfResult(name=name or threshold_level, threshold_ms=threshold_ms) result = PerfResult(name=name or threshold_level, threshold_ms=threshold_ms)
last_response = None last_response = None
for i in range(num_samples): for _ in range(num_samples):
start = time.perf_counter() start = time.perf_counter()
last_response = func() last_response = func()
elapsed = (time.perf_counter() - start) * 1000 elapsed = (time.perf_counter() - start) * 1000
@@ -261,7 +261,7 @@ def run_perf_test(
result = PerfResult(name=name, threshold_ms=threshold_ms) result = PerfResult(name=name, threshold_ms=threshold_ms)
last_response = None last_response = None
for i in range(samples): for _ in range(samples):
start = time.perf_counter() start = time.perf_counter()
last_response = func() last_response = func()
elapsed = (time.perf_counter() - start) * 1000 elapsed = (time.perf_counter() - start) * 1000
+1 -1
View File
@@ -530,7 +530,7 @@ class TestLargeDataRequests:
def test_rapid_sequential_requests(self, auth_headers): def test_rapid_sequential_requests(self, auth_headers):
"""快速连续请求不应触发限流导致 500。""" """快速连续请求不应触发限流导致 500。"""
statuses = [] statuses = []
for i in range(20): for _ in range(20):
resp = client.get("/api/v1/projects", headers=auth_headers) resp = client.get("/api/v1/projects", headers=auth_headers)
statuses.append(resp.status_code) statuses.append(resp.status_code)
+10 -1
View File
@@ -665,7 +665,16 @@ class TestResponseSchema:
resp = client.get(f"/api/v1/edit-plans/{plan.id}/generation-status") resp = client.get(f"/api/v1/edit-plans/{plan.id}/generation-status")
assert resp.status_code == 200 assert resp.status_code == 200
data = resp.json() data = resp.json()
expected_keys = {"plan_id", "plan_status", "generation_task_id", "clips"} expected_keys = {
"plan_id",
"plan_status",
"generation_task_id",
"generation_task_status",
"progress",
"video_url",
"error_message",
"clips",
}
assert set(data.keys()) == expected_keys assert set(data.keys()) == expected_keys
+6 -6
View File
@@ -44,7 +44,7 @@ class TestEditTemplate:
def test_create_empty_name_raises(self): def test_create_empty_name_raises(self):
try: try:
EditTemplate.create(" ") EditTemplate.create(" ")
assert False, "应该抛出 ValueError" raise AssertionError("应该抛出 ValueError")
except ValueError as e: except ValueError as e:
assert "模板名称不能为空" in str(e) assert "模板名称不能为空" in str(e)
@@ -74,14 +74,14 @@ class TestEditPlan:
def test_create_empty_name_raises(self): def test_create_empty_name_raises(self):
try: try:
EditPlan.create("tpl-1", " ") EditPlan.create("tpl-1", " ")
assert False, "应该抛出 ValueError" raise AssertionError("应该抛出 ValueError")
except ValueError as e: except ValueError as e:
assert "计划名称不能为空" in str(e) assert "计划名称不能为空" in str(e)
def test_create_empty_template_id_raises(self): def test_create_empty_template_id_raises(self):
try: try:
EditPlan.create(" ", "test") EditPlan.create(" ", "test")
assert False, "应该抛出 ValueError" raise AssertionError("应该抛出 ValueError")
except ValueError as e: except ValueError as e:
assert "template_id 不能为空" in str(e) assert "template_id 不能为空" in str(e)
@@ -112,7 +112,7 @@ class TestEditPlan:
p = EditPlan.create("tpl-1", "test") p = EditPlan.create("tpl-1", "test")
try: try:
p.start_rendering() # draft → rendering 不合法 p.start_rendering() # draft → rendering 不合法
assert False, "应该抛出 ValueError" raise AssertionError("应该抛出 ValueError")
except ValueError as e: except ValueError as e:
logger.warning(f"Operation failed in tests/unit/test_phase8_edit_models.py: {e}", exc_info=True) logger.warning(f"Operation failed in tests/unit/test_phase8_edit_models.py: {e}", exc_info=True)
@@ -120,7 +120,7 @@ class TestEditPlan:
p = EditPlan.create("tpl-1", "test") p = EditPlan.create("tpl-1", "test")
try: try:
p.mark_completed() # draft → completed 不合法 p.mark_completed() # draft → completed 不合法
assert False, "应该抛出 ValueError" raise AssertionError("应该抛出 ValueError")
except ValueError as e: except ValueError as e:
logger.warning(f"Operation failed in tests/unit/test_phase8_edit_models.py: {e}", exc_info=True) logger.warning(f"Operation failed in tests/unit/test_phase8_edit_models.py: {e}", exc_info=True)
@@ -128,7 +128,7 @@ class TestEditPlan:
p = EditPlan.create("tpl-1", "test") p = EditPlan.create("tpl-1", "test")
try: try:
p.reset_to_draft() # draft → draft 不合法 p.reset_to_draft() # draft → draft 不合法
assert False, "应该抛出 ValueError" raise AssertionError("应该抛出 ValueError")
except ValueError as e: except ValueError as e:
logger.warning(f"Operation failed in tests/unit/test_phase8_edit_models.py: {e}", exc_info=True) logger.warning(f"Operation failed in tests/unit/test_phase8_edit_models.py: {e}", exc_info=True)
+1 -1
View File
@@ -253,7 +253,7 @@ class TestConcatSecurity:
# 创建超过上限的段数 # 创建超过上限的段数
segments = [] segments = []
for i in range(MAX_CONCAT_SEGMENTS + 5): for _ in range(MAX_CONCAT_SEGMENTS + 5):
segments.append(ConcatSegment(video_path=str(sample_video))) segments.append(ConcatSegment(video_path=str(sample_video)))
config = ConcatConfig(segments=segments) config = ConcatConfig(segments=segments)
+1 -1
View File
@@ -367,7 +367,7 @@ class TestVerifyUrlRedirectValidation:
except urllib.error.HTTPError as e: except urllib.error.HTTPError as e:
if 300 <= e.code < 400 and e.headers.get("Location"): if 300 <= e.code < 400 and e.headers.get("Location"):
if redirect_count >= max_redirects: if redirect_count >= max_redirects:
raise Exception(f"重定向次数超过上限 ({max_redirects})") raise Exception(f"重定向次数超过上限 ({max_redirects})") from e
location = e.headers["Location"] location = e.headers["Location"]
current = urljoin(safe_url, location) current = urljoin(safe_url, location)
redirect_count += 1 redirect_count += 1
+2 -2
View File
@@ -115,7 +115,7 @@ class TestAudioMerger:
# 创建临时文件 # 创建临时文件
paths = [] paths = []
for i in range(3): for _ in range(3):
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f: with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
f.write(b"audio") f.write(b"audio")
paths.append(f.name) paths.append(f.name)
@@ -151,7 +151,7 @@ class TestAudioMerger:
mock_run_ffmpeg.side_effect = CalledProcessError(returncode=1, cmd=["ffmpeg"], stderr="error details") mock_run_ffmpeg.side_effect = CalledProcessError(returncode=1, cmd=["ffmpeg"], stderr="error details")
paths = [] paths = []
for i in range(2): for _ in range(2):
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f: with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
f.write(b"audio") f.write(b"audio")
paths.append(f.name) paths.append(f.name)
+34 -2
View File
@@ -45,6 +45,7 @@ class FakeClip:
start_time: float = 0.0 start_time: float = 0.0
duration: float = 0.0 duration: float = 0.0
transition_effect: str = "cut" transition_effect: str = "cut"
transition_duration: float = 0.0
status: str = "ready" status: str = "ready"
config: dict[str, Any] = field(default_factory=dict) config: dict[str, Any] = field(default_factory=dict)
@@ -65,6 +66,7 @@ def _make_clip(
asset_id: str = "", asset_id: str = "",
duration: float = 0.0, duration: float = 0.0,
transition_effect: str = "cut", transition_effect: str = "cut",
transition_duration: float = 0.0,
config: dict[str, Any] | None = None, config: dict[str, Any] | None = None,
) -> FakeClip: ) -> FakeClip:
return FakeClip( return FakeClip(
@@ -74,6 +76,7 @@ def _make_clip(
asset_id=asset_id or f"asset_{clip_id}.mp4", asset_id=asset_id or f"asset_{clip_id}.mp4",
duration=duration, duration=duration,
transition_effect=transition_effect, transition_effect=transition_effect,
transition_duration=transition_duration,
config=config or {}, config=config or {},
) )
@@ -332,7 +335,7 @@ class TestBuildFilterComplex:
assert "[final_video]" in fc assert "[final_video]" in fc
def test_single_layer_multi_clips(self): def test_single_layer_multi_clips(self):
"""多个 main clips → xfade 串联。""" """多个 main clips(默认硬切)→ concat 串联。"""
clips = [ clips = [
_make_clip("c1", "main", order=0, duration=3.0), _make_clip("c1", "main", order=0, duration=3.0),
_make_clip("c2", "main", order=1, duration=3.0), _make_clip("c2", "main", order=1, duration=3.0),
@@ -343,6 +346,35 @@ class TestBuildFilterComplex:
} }
svc = _make_service(clips, asset_paths) svc = _make_service(clips, asset_paths)
with _patch_path_exists(), patch("video_processing.unified_render_service.probe_duration", return_value=5.0):
resolved = svc._resolve_clips()
layers = svc._group_clips_into_layers(resolved)
fc, input_args = svc._build_filter_complex(layers)
assert input_args.count("-i") == 2
# 全硬切场景走 concat filter(性能远优于 xfade
assert "concat=n=2:v=1:a=0" in fc
assert "[final_video]" in fc
def test_single_layer_multi_clips_with_transition(self):
"""多个 main clips 带转场效果 → xfade 串联。"""
clips = [
_make_clip("c1", "main", order=0, duration=3.0),
_make_clip(
"c2",
"main",
order=1,
duration=3.0,
transition_effect="fade",
transition_duration=0.5,
),
]
asset_paths = {
"asset_c1.mp4": Path("/tmp/asset_c1.mp4"),
"asset_c2.mp4": Path("/tmp/asset_c2.mp4"),
}
svc = _make_service(clips, asset_paths)
with _patch_path_exists(), patch("video_processing.unified_render_service.probe_duration", return_value=5.0): with _patch_path_exists(), patch("video_processing.unified_render_service.probe_duration", return_value=5.0):
resolved = svc._resolve_clips() resolved = svc._resolve_clips()
layers = svc._group_clips_into_layers(resolved) layers = svc._group_clips_into_layers(resolved)
@@ -380,7 +412,7 @@ class TestBuildFilterComplex:
""" """
clips = [ clips = [
_make_clip("c1", "main", order=0, duration=3.0), _make_clip("c1", "main", order=0, duration=3.0),
_make_clip("c2", "main", order=1, duration=5.0), _make_clip("c2", "main", order=1, duration=5.0, transition_effect="fade", transition_duration=0.5),
] ]
asset_paths = { asset_paths = {
"asset_c1.mp4": Path("/tmp/asset_c1.mp4"), "asset_c1.mp4": Path("/tmp/asset_c1.mp4"),