Compare commits

...

107 Commits

Author SHA1 Message Date
CI Bot 11976e9a87 test(unit): 第75波 - video_share + editing_mode + template_version (+54)
AI Code Review / AI Code Review (pull_request) Failing after 0s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 39s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m4s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 35s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 41s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m49s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m50s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m54s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m20s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 2m22s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m57s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m39s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1h14m32s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Has been cancelled
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 15s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 1264h9m33s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1264h9m38s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1264h9m42s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1264h9m44s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1264h9m46s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1264h9m50s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1264h9m50s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1264h9m52s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1264h9m58s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1264h42m30s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1264h42m12s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 1264h42m24s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1264h42m16s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1264h42m32s
2026-07-25 21:01:36 +08:00
CI Bot 87485137c9 test(P3-1): 第56波 worker层更多引擎纯逻辑单测(+108)
- test_subtitle_render_engine.py: 53个(颜色转换/时间格式化/文字换行/ASS转义/样式)
- test_sticker_engine.py: 27个(配置类/位置解析/常量/便捷函数)
- test_tts_engine.py: 12个(数据类/入口判断/初始化)
- test_render_audio_utils.py: 16个(clip_effective_duration/RenderContext)

覆盖worker层4个模块的纯逻辑部分
2026-07-25 21:01:27 +08:00
xiaoxia 2972f19954 test(ci): 修复10个ruff错误 + Unit Tests dedup收集失败 (#839 #881)
CI/CD Pipeline / Frontend Lint (push) Successful in 58s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m38s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m46s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m42s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m18s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m23s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 5m27s
CI/CD Pipeline / Unit Tests (push) Failing after 5m54s
CI/CD Pipeline / Integration Tests (push) Successful in 2m26s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m39s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 14s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m3s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m14s
CI/CD Pipeline / Canary Release to Production (push) Failing after 1269h30m20s
CI/CD Pipeline / Deploy Production (push) Failing after 1269h45m45s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1269h45m50s
CI/CD Pipeline / Build Production API Image (push) Failing after 1269h45m54s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1269h47m30s
CI/CD Pipeline / PR Build API Image (push) Failing after 1269h47m32s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1269h47m34s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1270h18m27s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1270h20m7s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1270h18m18s
2026-07-25 17:38:17 +08:00
xiaoxia 32868e54ad refactor(assets): Phase 1 - extract types, constants and utils (#892)
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m44s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m0s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 57s
CI/CD Pipeline / Unit Tests (push) Failing after 3m28s
CI/CD Pipeline / Integration Tests (push) Successful in 2m13s
CI/CD Pipeline / Frontend Lint (push) Successful in 38s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m0s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m44s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m56s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m20s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m33s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 7s
CI/CD Pipeline / Canary Release to Production (push) Failing after 1269h47m34s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1269h58m52s
CI/CD Pipeline / Deploy Production (push) Failing after 1270h2m23s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1270h6m44s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1270h9m7s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1270h9m9s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1270h6m46s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1270h10m8s
CI/CD Pipeline / Build Production API Image (push) Failing after 1270h39m23s
CI/CD Pipeline / PR Build API Image (push) Failing after 1270h41m46s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-25 17:18:27 +08:00
xiaoxia d50edcb2e6 refactor(voices): Phase 3 - extract 5 business hooks (#877)
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m1s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 58s
CI/CD Pipeline / Frontend Lint (push) Successful in 42s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m0s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m11s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m34s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m35s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m9s
CI/CD Pipeline / Integration Tests (push) Successful in 1m46s
CI/CD Pipeline / Unit Tests (push) Failing after 3m22s
CI/CD Pipeline / Canary Release to Production (push) Failing after 1271h1m39s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 1271h21m28s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1271h21m29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1271h22m43s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1272h15m56s
CI/CD Pipeline / Deploy Production (push) Failing after 1272h15m58s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1272h16m16s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1272h16m18s
CI/CD Pipeline / Build Production API Image (push) Failing after 1272h16m20s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1272h18m8s
CI/CD Pipeline / PR Build API Image (push) Failing after 1272h18m10s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1272h19m14s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 1271h54m3s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1272h50m44s
2026-07-25 15:09:05 +08:00
xiaoxia a9aface0b8 feat(ci): 金丝雀发布流水线(#450)
CI/CD Pipeline / Frontend Lint (push) Successful in 48s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m46s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m47s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m8s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m20s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m29s
CI/CD Pipeline / Unit Tests (push) Failing after 4m59s
CI/CD Pipeline / Build Staging API Image (push) Failing after 6m35s
CI/CD Pipeline / Build Staging Worker Image (push) Failing after 6m7s
CI/CD Pipeline / Integration Tests (push) Successful in 2m20s
CI/CD Pipeline / Canary Release to Production (push) Failing after 1272h37m52s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 1272h37m57s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1272h37m59s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1272h38m2s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1272h38m3s
CI/CD Pipeline / Deploy Production (push) Failing after 1272h38m53s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1272h42m47s
CI/CD Pipeline / Build Production API Image (push) Failing after 1272h42m51s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1272h44m41s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1272h45m1s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1272h44m41s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 1273h10m32s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1273h15m23s
CI/CD Pipeline / PR Build API Image (push) Failing after 1273h17m17s
金丝雀发布流水线:5%→20%→50%→100%分阶段灰度,自动健康检查+异常回滚,main分支push自动触发。
2026-07-25 14:43:37 +08:00
xiaoxia dc62884c5f test(unit): 第70波 - 去重算法 + 字幕渲染 + TTS引擎纯逻辑 (+73) (#868)
CI/CD Pipeline / Frontend Lint (push) Successful in 57s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m30s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m31s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m38s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m59s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m46s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m49s
CI/CD Pipeline / Unit Tests (push) Failing after 4m29s
CI/CD Pipeline / Integration Tests (push) Successful in 2m4s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m7s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m16s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 15s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m13s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 5m38s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1272h59m43s
CI/CD Pipeline / Deploy Production (push) Failing after 1273h1m49s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1273h8m27s
CI/CD Pipeline / Build Production API Image (push) Failing after 1273h8m31s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1273h10m12s
CI/CD Pipeline / PR Build API Image (push) Failing after 1273h10m14s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1273h10m16s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1273h41m3s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1273h42m48s
2026-07-25 14:18:22 +08:00
xiaoxia 9b06d814bd refactor(voices): Phase 2 - extract 7 UI components (#875)
CI/CD Pipeline / Frontend Lint (push) Successful in 37s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m1s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 58s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m33s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m48s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m31s
CI/CD Pipeline / Integration Tests (push) Successful in 2m43s
CI/CD Pipeline / Unit Tests (push) Failing after 5m37s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m35s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m12s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 29s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m45s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m17s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1273h26m53s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1273h28m12s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1273h28m14s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1273h30m22s
CI/CD Pipeline / Build Production API Image (push) Failing after 1273h28m16s
CI/CD Pipeline / PR Build API Image (push) Failing after 1273h30m24s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1273h31m17s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1274h2m54s
CI/CD Pipeline / Deploy Production (push) Failing after 1273h59m43s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-07-25 13:57:19 +08:00
xiaoxia 248eb3266d feat(ci): 添加Renovate依赖自动更新(#453) (#876)
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 58s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 57s
CI/CD Pipeline / Frontend Lint (push) Successful in 30s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m43s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m54s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m16s
CI/CD Pipeline / Unit Tests (push) Failing after 5m47s
CI/CD Pipeline / Integration Tests (push) Successful in 1m57s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m6s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m10s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 14s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m53s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m55s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1274h5m11s
CI/CD Pipeline / Deploy Production (push) Failing after 1274h5m39s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1274h9m49s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1274h10m20s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1274h9m51s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1274h10m22s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1274h11m21s
CI/CD Pipeline / PR Build API Image (push) Failing after 1274h42m55s
CI/CD Pipeline / Build Production API Image (push) Failing after 1274h42m26s
2026-07-25 13:16:49 +08:00
xiaoxia c575a4b835 test(unit): 第56波 worker层更多引擎纯逻辑单测(+108)(#848)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m21s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m55s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m52s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m27s
CI/CD Pipeline / Unit Tests (push) Failing after 6m39s
CI/CD Pipeline / Integration Tests (push) Successful in 2m41s
CI/CD Pipeline / Frontend Lint (push) Successful in 44s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m0s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15m23s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m49s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m11s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 5s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m11s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m28s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1274h18m59s
CI/CD Pipeline / Deploy Production (push) Failing after 1274h19m39s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1274h26m28s
CI/CD Pipeline / Build Production API Image (push) Failing after 1274h26m32s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1274h26m49s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1274h28m34s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1274h26m51s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1274h59m3s
CI/CD Pipeline / PR Build API Image (push) Failing after 1274h59m26s
2026-07-25 12:56:26 +08:00
CI Bot 22a34aebf1 style: auto-format with black + isort + prettier
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 24s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m32s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m48s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m48s
CI/CD Pipeline / Unit Tests (push) Failing after 6m5s
CI/CD Pipeline / Integration Tests (push) Successful in 2m13s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m48s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m9s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m50s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m15s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m56s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m56s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1274h43m0s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1274h44m15s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1274h44m15s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1274h46m2s
CI/CD Pipeline / Build Production API Image (push) Failing after 1274h44m15s
CI/CD Pipeline / PR Build API Image (push) Failing after 1274h46m2s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1274h46m4s
CI/CD Pipeline / Deploy Production (push) Failing after 1275h15m36s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1275h18m33s
2026-07-25 04:32:40 +00:00
xiaoxia 40776cd4fa refactor(voices): Phase 1 - extract types, constants and utils (#873)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m36s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 4m40s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m22s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m21s
CI/CD Pipeline / Unit Tests (push) Failing after 6m50s
CI/CD Pipeline / Integration Tests (push) Successful in 2m30s
CI/CD Pipeline / Frontend Lint (push) Successful in 42s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m43s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m59s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m13s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m22s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 32s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 6m9s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 6m45s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1274h54m16s
CI/CD Pipeline / Deploy Production (push) Failing after 1274h57m46s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1274h59m11s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1275h0m4s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1275h0m6s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1274h59m12s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1275h0m36s
CI/CD Pipeline / Build Production API Image (push) Failing after 1275h31m47s
CI/CD Pipeline / PR Build API Image (push) Failing after 1275h32m40s
2026-07-25 12:25:14 +08:00
xiaoxia e9bc340612 feat(ci): ACR镜像自动清理增强版(#524)
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m39s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m11s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 58s
CI/CD Pipeline / Unit Tests (push) Failing after 5m46s
CI/CD Pipeline / Integration Tests (push) Successful in 3m19s
CI/CD Pipeline / Frontend Lint (push) Successful in 41s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m9s
CI/CD Pipeline / Build Staging API Image (push) Failing after 14m39s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m44s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 6m0s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 1274h59m8s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 1274h59m9s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1274h59m10s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1275h0m38s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1275h2m30s
CI/CD Pipeline / Deploy Production (push) Failing after 1275h5m54s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1275h11m30s
CI/CD Pipeline / Build Production API Image (push) Failing after 1275h11m34s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1275h15m34s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1275h16m50s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1275h15m35s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1275h44m6s
CI/CD Pipeline / PR Build API Image (push) Failing after 1275h48m11s
ACR镜像自动清理增强版:
- 3种执行模式:PR-SHA清理/cron全量/手动预览
- 生产镜像保护:main/v*版本tag/固定tag永不删除
- develop镜像保留最近20个commit hash tag
- PR镜像关闭后自动清理(Gitea PR状态检查 + 7天降级保留)
- 安全兜底:staging运行中镜像白名单
- workflow触发:cron每天03:00 + PR关闭事件 + 手动触发
2026-07-25 12:07:17 +08:00
CI Bot 71faf2ba44 style: auto-format with black + isort + prettier
CI/CD Pipeline / Frontend Lint (push) Successful in 44s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m10s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m11s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m48s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m33s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m52s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m28s
CI/CD Pipeline / Integration Tests (push) Successful in 2m0s
CI/CD Pipeline / Unit Tests (push) Failing after 6m22s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m27s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m15s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 32s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m53s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m2s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1275h36m23s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1275h40m49s
CI/CD Pipeline / Build Production API Image (push) Failing after 1275h40m51s
CI/CD Pipeline / Deploy Production (push) Failing after 1275h39m14s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1275h41m58s
CI/CD Pipeline / PR Build API Image (push) Failing after 1275h42m0s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1275h42m2s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1276h13m20s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1276h14m29s
2026-07-25 03:33:12 +00:00
xiaoxia 26db4ac44d test: 第73波 url_security + pagination + text_splitter 单测补充 (+68)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 11:32:38 +08:00
xiaoxia aa0b0de90f test(unit): 第72波 - TTSJob状态机 + GeneratedVideo + VerificationCode (+76)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 11:32:35 +08:00
xiaoxia 3204bf2d1f test(unit): 第71波 - 领域实体扩展测试 Project+Asset+IngestJob+User+AssetLibrary (+57)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m29s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m44s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m11s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m13s
CI/CD Pipeline / Unit Tests (push) Failing after 5m59s
CI/CD Pipeline / Integration Tests (push) Successful in 2m31s
CI/CD Pipeline / Frontend Lint (push) Successful in 39s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m55s
CI/CD Pipeline / Build Staging API Image (push) Successful in 14m3s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m9s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m50s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m35s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m58s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m27s
CI/CD Pipeline / Deploy Production (push) Failing after 1275h53m47s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1275h53m45s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1275h55m8s
CI/CD Pipeline / Build Production API Image (push) Failing after 1275h55m12s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1275h56m9s
CI/CD Pipeline / PR Build API Image (push) Failing after 1275h56m13s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1275h57m12s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1276h27m43s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1276h28m44s
2026-07-25 11:31:28 +08:00
xiaoxia ee2cc0e7a1 refactor(voice-materials): phase 3 extract hooks (#866)
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m11s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m12s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m14s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m36s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m56s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m37s
CI/CD Pipeline / Unit Tests (push) Failing after 5m50s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 6m55s
CI/CD Pipeline / Integration Tests (push) Successful in 2m7s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15m26s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m9s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 22s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m12s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m33s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1277h9m46s
CI/CD Pipeline / Deploy Production (push) Failing after 1277h15m45s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1277h22m14s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1277h22m43s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1277h22m16s
CI/CD Pipeline / PR Build API Image (push) Failing after 1277h22m44s
CI/CD Pipeline / Build Production API Image (push) Failing after 1277h22m18s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1277h23m56s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1277h55m14s
2026-07-25 10:04:02 +08:00
xiaoxia be0b4f4dac fix: 状态枚举添加_missing_兼容历史脏数据,修复Staging模板生成接口500 #809 (#835)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 10:04:00 +08:00
xiaoxia 1e16e81344 chore(ci): 升级migration验证,新增4项检查 (#451)
CI/CD Pipeline / Production Browser E2E (push) Failing after 1277h35m35s
CI/CD Pipeline / Deploy Production (push) Failing after 1277h37m19s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1277h42m2s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1277h44m23s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1277h44m25s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1277h42m4s
CI/CD Pipeline / PR Build API Image (push) Failing after 1277h44m27s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1277h45m15s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m2s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m19s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m39s
CI/CD Pipeline / Build Production API Image (push) Failing after 1278h14m38s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m9s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 53s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 53s
CI/CD Pipeline / Unit Tests (push) Failing after 6m4s
CI/CD Pipeline / Integration Tests (push) Successful in 2m0s
CI/CD Pipeline / Frontend Lint (push) Successful in 35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 44s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m39s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m5s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m34s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 36s
#451 数据库迁移检查升级

新增5项完整检查:
1. 命名规范检查(文件名格式/编号连续/revision一致性)
2. 链完整性检查(revision/down_revision链)
3. upgrade head(原有逻辑)
4. downgrade -1 + re-upgrade(双向一致性)
5. alembic check(未生成migration的model变更检测,警告模式)
2026-07-25 09:43:26 +08:00
CI Bot 7a72cfd709 style: auto-format with black + isort + prettier
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m9s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m19s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m10s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m7s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m7s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m17s
CI/CD Pipeline / Frontend Lint (push) Successful in 3m37s
CI/CD Pipeline / Integration Tests (push) Successful in 2m18s
CI/CD Pipeline / Unit Tests (push) Failing after 4m59s
CI/CD Pipeline / Build Staging API Image (push) Successful in 8m39s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m25s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 42s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 27m2s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 30m15s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1278h57m2s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1278h58m2s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1278h58m4s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1279h0m13s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1279h0m14s
CI/CD Pipeline / PR Build API Image (push) Failing after 1279h0m14s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1279h0m15s
CI/CD Pipeline / Build Production API Image (push) Failing after 1279h30m38s
CI/CD Pipeline / Deploy Production (push) Failing after 1279h29m36s
2026-07-25 00:16:10 +00:00
xiaoxia b82b303d6a refactor: VoiceMaterialLibrary Phase 2 - 抽离4个子组件 (#858)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 08:13:23 +08:00
xiaoxia baa3bf6300 test(unit): 第69波 - 统一渲染+音频+适配器纯逻辑测试 (+53) (#865)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 08:13:19 +08:00
xiaoxia d7788013de test(unit): 第68波 - ffmpeg纯函数 + 模板编辑器工具 + OSS助手纯逻辑 (+83) (#864)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 08:13:16 +08:00
xiaoxia ff3038a291 test(unit): 第67波 - trim引擎 + concat引擎 + subtitle_render引擎配置解析 (+112) (#863)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 08:13:12 +08:00
xiaoxia 10f9e67c8e test(unit): 第66波 - multi_track + bgm_mixer + sticker 配置 (+52) (#862)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 08:13:08 +08:00
xiaoxia c192398db6 test(unit): 第65波 - intro_outro + transition + pip 引擎配置 (+82) (#861)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 08:13:03 +08:00
xiaoxia 17cf970ae6 test(unit): 第64波 - speed + chroma_key + color_grade 引擎配置 (+96) (#860)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 08:13:00 +08:00
xiaoxia 5b21763aea test(unit): 第63波 - watermark + noise_reduction + thumbnail 纯逻辑 (+89) (#859)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-25 08:12:57 +08:00
xiaoxia c90b4819c0 test(unit): 第62波 - module_registry + asr_service_factory + sms_service (+58) (#856)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m40s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m21s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m42s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m42s
CI/CD Pipeline / Unit Tests (push) Failing after 5m59s
CI/CD Pipeline / Integration Tests (push) Successful in 3m26s
CI/CD Pipeline / Frontend Lint (push) Successful in 57s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m43s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15m29s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m4s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m59s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 55s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 35m30s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 39m55s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1279h11m55s
CI/CD Pipeline / Deploy Production (push) Failing after 1279h12m25s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1279h14m4s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1279h14m6s
CI/CD Pipeline / Build Production API Image (push) Failing after 1279h14m6s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1279h15m46s
CI/CD Pipeline / PR Build API Image (push) Failing after 1279h15m48s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1279h15m48s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1279h48m18s
2026-07-25 08:12:53 +08:00
CI Bot 464f6ea155 style: auto-format with black + isort + prettier
CI/CD Pipeline / Frontend Lint (push) Successful in 41s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m2s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m5s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m25s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m7s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m16s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m4s
CI/CD Pipeline / Build Staging API Image (push) Successful in 14m15s
CI/CD Pipeline / Unit Tests (push) Failing after 4m43s
CI/CD Pipeline / Integration Tests (push) Successful in 2m37s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m13s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 27s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m29s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m31s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1286h40m0s
CI/CD Pipeline / Deploy Production (push) Failing after 1286h55m17s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1287h5m54s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1287h6m9s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1287h5m56s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1287h6m11s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1287h7m3s
CI/CD Pipeline / Build Production API Image (push) Failing after 1287h38m28s
CI/CD Pipeline / PR Build API Image (push) Failing after 1287h38m43s
2026-07-24 16:00:04 +00:00
xiaoxia d2bdb462c6 test(P3-1): 第61波 mock ASR/TTS + TTS工厂单测(+52) (#854)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 23:57:35 +08:00
xiaoxia 745c851758 refactor: VoiceMaterialLibrary Phase 1 - 抽离类型/常量/工具函数 (#846)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m29s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m42s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m41s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m39s
CI/CD Pipeline / Unit Tests (push) Failing after 6m57s
CI/CD Pipeline / Integration Tests (push) Successful in 3m6s
CI/CD Pipeline / Frontend Lint (push) Successful in 46s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m50s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m46s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m3s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m51s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m35s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 37s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m8s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1287h29m34s
CI/CD Pipeline / Deploy Production (push) Failing after 1287h29m36s
CI/CD Pipeline / Build Production API Image (push) Failing after 1287h29m40s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1287h29m38s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1287h31m14s
CI/CD Pipeline / PR Build API Image (push) Failing after 1287h31m18s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1287h31m20s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1288h2m8s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1288h3m46s
2026-07-24 23:57:23 +08:00
CI Bot 18bd0de3fa style: auto-format with black + isort + prettier
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m42s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m44s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m42s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m26s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m13s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m22s
CI/CD Pipeline / Frontend Lint (push) Successful in 4m29s
CI/CD Pipeline / Integration Tests (push) Successful in 2m16s
CI/CD Pipeline / Unit Tests (push) Failing after 5m46s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m28s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m18s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 30s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m13s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m58s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1289h2m32s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1289h5m6s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1289h5m7s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1289h6m49s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1289h6m50s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1289h6m52s
CI/CD Pipeline / Deploy Production (push) Failing after 1289h35m22s
CI/CD Pipeline / Build Production API Image (push) Failing after 1289h37m37s
CI/CD Pipeline / PR Build API Image (push) Failing after 1289h39m20s
2026-07-24 14:07:12 +00:00
xiaoxia 04bcd2e6f9 test(P3-1): 第60波 reverse引擎+in-memory素材+task_enqueue单测(+85) (#852)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 22:06:07 +08:00
xiaoxia f7151ea2be test(P3-1): 第59波 Schema验证+FeatureFlags单测(+72) (#851)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m36s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m44s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m12s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 56s
CI/CD Pipeline / Unit Tests (push) Failing after 6m55s
CI/CD Pipeline / Integration Tests (push) Successful in 3m8s
CI/CD Pipeline / Frontend Lint (push) Successful in 41s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m14s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15m2s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m10s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m54s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 54s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m55s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m41s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1289h16m24s
CI/CD Pipeline / Deploy Production (push) Failing after 1289h19m44s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1289h20m39s
CI/CD Pipeline / Build Production API Image (push) Failing after 1289h20m43s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1289h21m56s
CI/CD Pipeline / PR Build API Image (push) Failing after 1289h22m0s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1289h23m16s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1289h53m10s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1289h54m27s
2026-07-24 22:04:50 +08:00
xiaoxia cea83329c8 test(P3-1): 第58波 in-memory仓储层单测(+57)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 22:03:50 +08:00
CI Bot 2df7bc9dc8 style: auto-format with black + isort + prettier
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m41s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m45s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m54s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m1s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m34s
CI/CD Pipeline / Build Staging API Image (push) Successful in 5m49s
CI/CD Pipeline / Frontend Lint (push) Successful in 6m0s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m27s
CI/CD Pipeline / Integration Tests (push) Successful in 2m20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m11s
CI/CD Pipeline / Unit Tests (push) Failing after 5m52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 30s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m48s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m10s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1289h38m58s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1289h42m13s
CI/CD Pipeline / Deploy Production (push) Failing after 1289h40m1s
CI/CD Pipeline / Build Production API Image (push) Failing after 1289h42m15s
CI/CD Pipeline / PR Build API Image (push) Failing after 1289h43m56s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1289h43m55s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1289h43m57s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1290h14m40s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1290h16m23s
2026-07-24 13:35:05 +00:00
xiaoxia f6a1798124 test(P3-1): 第54波 domain 单测 - 剩余12个小模块(+62)domain层扫完 (#844)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m39s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m38s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m28s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m27s
CI/CD Pipeline / Unit Tests (push) Failing after 6m21s
CI/CD Pipeline / Integration Tests (push) Successful in 3m3s
CI/CD Pipeline / Frontend Lint (push) Successful in 44s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m20s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m40s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m26s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m17s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 32s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m42s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m24s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1289h53m56s
CI/CD Pipeline / Deploy Production (push) Failing after 1289h53m58s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1289h54m2s
CI/CD Pipeline / Build Production API Image (push) Failing after 1289h54m6s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1289h55m41s
CI/CD Pipeline / PR Build API Image (push) Failing after 1289h55m45s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1289h56m16s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1290h26m33s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1290h28m12s
2026-07-24 21:30:59 +08:00
xiaoxia 3ca32f3532 test(P3-1): 第53波 domain 单测 - tts_job/voice_clone_profile/video_share(+133) (#843)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 21:30:57 +08:00
xiaoxia 03b42e1561 test(P3-1): 第52波 domain 单测 - config_schemas/filter_presets/transition_presets(+129) (#842)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 21:30:54 +08:00
xiaoxia 972ff89990 test(P3-1): 第51波 domain 单测 - quota/job/entities(+183) (#840)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 21:30:52 +08:00
xiaoxia 37d111dcd5 test(P3-1): 第57波 worker层小工具+配置单测(+60) (#849)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 21:29:40 +08:00
xiaoxia 0e86062f17 test(P3-1): 第55波 worker层引擎配置验证单测(+120) (#847)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 21:29:37 +08:00
xiaoxia 6e70a7ad29 chore(ci): 端口变量命名统一 (#799) (#845)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 21:29:35 +08:00
xiaoxia c9c7da0c74 docs: 新增统一端口分配清单文档 (#798) (#841)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 21:29:32 +08:00
xiaoxia cf544881fd refactor: GeneratePage Phase 3 - Step内部子组件拆分 (#816)
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m9s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m11s
CI/CD Pipeline / Frontend Lint (push) Successful in 2m26s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m45s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m55s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m5s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m14s
CI/CD Pipeline / Integration Tests (push) Successful in 3m8s
CI/CD Pipeline / Unit Tests (push) Successful in 7m24s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15m59s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m22s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 35s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m21s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m8s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1291h7m30s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1291h7m57s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1291h7m59s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1291h10m11s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1291h10m12s
CI/CD Pipeline / PR Build API Image (push) Failing after 1291h10m12s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1291h10m13s
CI/CD Pipeline / Deploy Production (push) Failing after 1291h40m1s
CI/CD Pipeline / Build Production API Image (push) Failing after 1291h40m30s
2026-07-24 20:18:32 +08:00
xiaoxia 42f92017de fix(ci): Preview Deploy npm ci参数错误修复 (#834)
CI/CD Pipeline / Frontend Lint (push) Successful in 44s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m7s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m10s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m49s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m43s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 42s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m36s
CI/CD Pipeline / Integration Tests (push) Successful in 3m51s
CI/CD Pipeline / Unit Tests (push) Successful in 8m45s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m44s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m9s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 28s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m23s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m39s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1291h50m26s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1291h55m23s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1291h55m25s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1291h57m15s
CI/CD Pipeline / Build Production API Image (push) Failing after 1291h55m27s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1291h56m25s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1291h56m27s
CI/CD Pipeline / Deploy Production (push) Failing after 1292h24m35s
CI/CD Pipeline / PR Build API Image (push) Failing after 1292h28m58s
2026-07-24 19:19:52 +08:00
xiaoxia 58fe71c483 PR #832: Worker基础镜像三级缓存策略优化 + 测试环境变量污染修复
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m48s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m29s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 55s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 48s
CI/CD Pipeline / Unit Tests (push) Successful in 6m16s
CI/CD Pipeline / Integration Tests (push) Successful in 2m56s
CI/CD Pipeline / Frontend Lint (push) Successful in 35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 46s
CI/CD Pipeline / Build Staging API Image (push) Successful in 5m34s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m2s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 14m0s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 23s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m50s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m10s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1292h5m15s
CI/CD Pipeline / Build Production API Image (push) Failing after 1292h11m7s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1292h11m20s
CI/CD Pipeline / PR Build API Image (push) Failing after 1292h11m22s
CI/CD Pipeline / Deploy Production (push) Failing after 1292h5m46s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1292h13m26s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1292h11m3s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1292h43m34s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1292h43m49s
perf(ci): Worker基础镜像三级缓存策略优化 + 单元测试环境变量污染修复

- 三级缓存策略:L1本地daemon / L2 Gitea Registry / L3本地构建+推送
- 修复pre-build镜像tag与Dockerfile FROM地址不一致的bug
- worker.Dockerfile层顺序优化,变化少的文件放前面
- 修复test_api_settings.py autouse fixture环境变量污染
  (JWT_SECRET_KEY/DATABASE_URL被pop后影响后续测试模块)
2026-07-24 19:04:19 +08:00
xiaoxia 3ae15eb4fc refactor(#778): 统一Storage接口,定义StoragePort+SharedStorageService作为唯一入口 (#794)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 19:00:13 +08:00
CI Bot 68e55df83b style: auto-format with black + isort + prettier
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 10:59:46 +00:00
xiaoxia 78afa40458 test: P3-1 第50波单元测试(generation_task/edit_plan/edit_template/edit_plan_clip,+86) (#838)
CI/CD Pipeline / Production Browser E2E (push) Failing after 1292h14m30s
CI/CD Pipeline / Deploy Production (push) Failing after 1292h21m58s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1292h28m31s
CI/CD Pipeline / Build Production API Image (push) Failing after 1292h28m35s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1292h29m16s
CI/CD Pipeline / PR Build API Image (push) Failing after 1292h29m20s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1292h30m21s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m19s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 58s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 55s
CI/CD Pipeline / Unit Tests (push) Failing after 6m24s
CI/CD Pipeline / Integration Tests (push) Successful in 3m48s
CI/CD Pipeline / Frontend Lint (push) Successful in 36s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m46s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15m45s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m59s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 6m0s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m11s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m16s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m53s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1293h1m1s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1293h1m46s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 34s
2026-07-24 18:58:21 +08:00
xiaoxia e38f13eb96 test: P3-1 第49波单元测试(generated_video/duplication/tts_config/classification,+88) (#837)
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1292h30m27s
2026-07-24 18:58:17 +08:00
xiaoxia 42335eff42 test: P3-1 第48波单元测试(tag/verification_code/subtitle/voice_presets,+59) (#836)
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1292h30m30s
2026-07-24 18:58:14 +08:00
xiaoxia 9a51cad137 refactor: GeneratePage Phase 2 — 7个Step组件化 + 自定义Hook(2682→485行) (#803)
CI/CD Pipeline / Frontend Lint (push) Successful in 48s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m24s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m25s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m27s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m8s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m16s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m20s
CI/CD Pipeline / Integration Tests (push) Successful in 3m53s
CI/CD Pipeline / Unit Tests (push) Failing after 7m53s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m13s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 26s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m3s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m15s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1294h4m36s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1294h5m22s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1294h5m22s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1294h7m45s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1294h7m47s
CI/CD Pipeline / PR Build API Image (push) Failing after 1294h7m47s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1294h7m49s
CI/CD Pipeline / Deploy Production (push) Failing after 1294h37m5s
CI/CD Pipeline / Build Production API Image (push) Failing after 1294h37m51s
2026-07-24 17:20:59 +08:00
CI Bot 3921a657e8 style: auto-format with black + isort + prettier
CI/CD Pipeline / Frontend Lint (push) Successful in 52s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m56s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m7s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m7s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 54s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 6m27s
CI/CD Pipeline / Unit Tests (push) Failing after 8m24s
CI/CD Pipeline / Build Staging API Image (push) Successful in 16m1s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Integration Tests (push) Successful in 3m28s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 21s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m26s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m34s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1294h13m51s
CI/CD Pipeline / Deploy Production (push) Failing after 1294h18m16s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1294h30m2s
CI/CD Pipeline / Build Production API Image (push) Failing after 1294h30m6s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1294h30m57s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1294h30m59s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1294h30m58s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1295h2m31s
CI/CD Pipeline / PR Build API Image (push) Failing after 1295h3m25s
2026-07-24 08:47:07 +00:00
xiaoxia 0bfb57813f test: P3-1 第44波单元测试(email_service/session_store) (#826)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:45:47 +08:00
xiaoxia 318f968c0e test: P3-1 第43波单元测试(ffmpeg_utils/ai_client/config_base) (#825)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:45:44 +08:00
xiaoxia 08cb1cfc23 test: P3-1 第41波单元测试(login_use_case) (#823)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:45:41 +08:00
xiaoxia 4b8bde5535 test: P3-1 第39波单元测试(audio_merger/jwt_service/verification_code/register_user) (#820)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:45:38 +08:00
xiaoxia afb2427089 test: P3-1 第38波单元测试(ingest/classification/generation/password_reset) (#819)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:45:34 +08:00
xiaoxia 2c0f0c24f9 test: P3-1 第37波单元测试(text_splitter/pagination/password_hasher/bind_contact) (#818)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:45:31 +08:00
xiaoxia 05c11e848b test: P3-1 第36波单元测试(assets/jwt/password/video_share) (#817)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:45:28 +08:00
xiaoxia 70eea45f7f test: P3-1 第47波单元测试(tts_workflow补充,+17) (#830)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:44:41 +08:00
xiaoxia 5ab090a0ce test: P3-1 第46波单元测试(api_settings/worker_settings + tts_streaming补充) (#829)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:44:40 +08:00
xiaoxia e9c5f2b78f test: P3-1 第45波单元测试(schema_guard/sms_service/job_use_cases) (#827)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:44:39 +08:00
xiaoxia c01ee5052f test: P3-1 第42波单元测试(tts_job/voice_clone use_cases + feature_flags) (#824)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:44:37 +08:00
xiaoxia 7e505a04c7 test: P3-1 第40波单元测试(wechat_oauth + wechat_sync) (#821)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 16:44:35 +08:00
xiaoxia 862fe038aa test: P3-1 第34波单元测试(title_library/recipe) (#812)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m50s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m35s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m8s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m52s
CI/CD Pipeline / Unit Tests (push) Successful in 6m56s
CI/CD Pipeline / Integration Tests (push) Successful in 3m44s
CI/CD Pipeline / Frontend Lint (push) Successful in 3m21s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 53s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m10s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m14s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m37s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m18s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 33s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m20s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1294h40m27s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1294h42m1s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1294h42m3s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1294h44m11s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1294h44m12s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1294h44m13s
CI/CD Pipeline / Deploy Production (push) Failing after 1295h12m58s
CI/CD Pipeline / Build Production API Image (push) Failing after 1295h14m32s
CI/CD Pipeline / PR Build API Image (push) Failing after 1295h16m40s
2026-07-24 16:44:32 +08:00
xiaoxia bae7113629 fix(ci): staging E2E/API tests shell由sh改为bash
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m8s
CI/CD Pipeline / Frontend Lint (push) Successful in 29s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m8s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m15s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 4m58s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m42s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m33s
CI/CD Pipeline / Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Unit Tests (push) Successful in 7m31s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m0s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m12s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 33s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m35s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m45s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1296h32m16s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1296h32m49s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1296h32m51s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1296h34m32s
CI/CD Pipeline / Build Production API Image (push) Failing after 1296h32m53s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1296h34m34s
CI/CD Pipeline / PR Build API Image (push) Failing after 1296h34m36s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1296h36m20s
CI/CD Pipeline / Deploy Production (push) Failing after 1297h4m50s
2026-07-24 14:52:20 +08:00
xiaoxia 627bd5b6a2 style: 修复scripts目录ruff F841/B007/F401/F541问题(9个文件)
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m10s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 55s
CI/CD Pipeline / Frontend Lint (push) Successful in 27s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m48s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 4m44s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m46s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 41s
CI/CD Pipeline / Integration Tests (push) Successful in 2m17s
CI/CD Pipeline / Unit Tests (push) Successful in 5m4s
CI/CD Pipeline / Build Staging API Image (push) Successful in 14m18s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m16s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 14s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 14s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 41s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1298h18m57s
CI/CD Pipeline / Deploy Production (push) Failing after 1298h20m38s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1298h26m57s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1298h26m59s
CI/CD Pipeline / Build Production API Image (push) Failing after 1298h27m1s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1298h35m54s
CI/CD Pipeline / PR Build API Image (push) Failing after 1298h35m58s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1298h37m31s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1299h8m23s
修复scripts目录下ruff检测到的18个问题:F841未使用变量6处、F401未使用import 10处、B007未使用循环变量1处、F541 f-string缺少占位符1处。
2026-07-24 12:49:07 +08:00
xiaoxia 5215e868f7 style: 清理ruff F401未使用import(5个文件)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
清理5个文件中ruff F401未使用import,含adjustments.py、pr_auto_scan.py、ci_trigger_monitor.py等。
2026-07-24 12:40:10 +08:00
xiaoxia 94afec2b9c chore(ci): 端口与PG配置常量集中管理,清理硬编码
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m8s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m49s
CI/CD Pipeline / Frontend Lint (push) Successful in 28s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m8s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m55s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 6m13s
CI/CD Pipeline / Build Staging API Image (push) Successful in 14m43s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m1s
CI/CD Pipeline / Integration Tests (push) Successful in 2m16s
CI/CD Pipeline / Unit Tests (push) Successful in 5m22s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m18s
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Failing after 1298h53m45s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1298h58m31s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1298h58m33s
CI/CD Pipeline / Build Production API Image (push) Failing after 1298h58m35s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1299h21m14s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1299h23m7s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1299h21m15s
CI/CD Pipeline / PR Build API Image (push) Failing after 1299h53m43s
清理CI脚本中硬编码的端口/用户/密码/DB名,统一抽到scripts/ci/ci_env.sh常量文件管理;ci-pipeline.yml中DATABASE_URL硬编码改为workflow级env变量引用。
2026-07-24 11:43:05 +08:00
xiaoxia 907cc63f96 fix(ci): staging-e2e/api-tests容器名改用GITHUB_SHA命名,彻底解决DooD模式命名冲突 (#808)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 11:26:06 +08:00
xiaoxia 2ee110f19c test: P3-1 第33波单元测试(projects/generated_videos/duplication) (#810)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 11:25:49 +08:00
xiaoxia da53b6d175 refactor: GeneratePage Phase 1
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
refactor: GeneratePage Phase 1 — 抽离常量/类型/UI组件(3036→2682行)
2026-07-24 11:01:41 +08:00
CI Bot 01e057c939 style: auto-format with black + isort + prettier
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 / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Failing after 1300h27m31s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1300h41m33s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1300h41m51s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1300h41m35s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1300h41m53s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1300h42m58s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m25s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 58s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 52s
CI/CD Pipeline / Unit Tests (push) Successful in 4m19s
CI/CD Pipeline / Integration Tests (push) Successful in 3m11s
CI/CD Pipeline / Frontend Lint (push) Successful in 35s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 49s
CI/CD Pipeline / Build Production API Image (push) Failing after 1301h14m3s
CI/CD Pipeline / PR Build API Image (push) Failing after 1301h14m21s
CI/CD Pipeline / Build Staging API Image (push) Failing after 14m16s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m52s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 6m41s
2026-07-24 02:30:15 +00:00
xiaoxia db85a174b9 test: P3-1 第32波单元测试(bgm_utils/exceptions/asset_libraries) (#807)
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m39s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m11s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m4s
CI/CD Pipeline / Unit Tests (push) Successful in 6m4s
CI/CD Pipeline / Integration Tests (push) Successful in 2m20s
CI/CD Pipeline / Frontend Lint (push) Successful in 36s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m48s
CI/CD Pipeline / Build Staging API Image (push) Failing after 9m40s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m16s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m52s
CI/CD Pipeline / ACR Image Cleanup (push) Failing after 1300h32m37s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1300h32m41s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Failing after 1300h43m0s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1300h54m39s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1300h55m36s
CI/CD Pipeline / Build Production API Image (push) Failing after 1300h55m40s
CI/CD Pipeline / Deploy Production (push) Failing after 1300h54m41s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1300h58m55s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1300h58m57s
CI/CD Pipeline / PR Build API Image (push) Failing after 1300h58m59s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1301h0m12s
CI/CD Pipeline / Staging API Integration Tests (push) Failing after 1301h5m5s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1301h28m4s
2026-07-24 10:24:53 +08:00
xiaoxia 9652e9e892 refactor: 拆分templates_editor.py巨无霸为16个模块 (#806)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 10:24:50 +08:00
CI Bot 7680247a25 style: auto-format with black + isort + prettier
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m45s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m6s
CI/CD Pipeline / Frontend Lint (push) Successful in 2m57s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m5s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m38s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 4m25s
CI/CD Pipeline / Integration Tests (push) Successful in 2m23s
CI/CD Pipeline / Unit Tests (push) Successful in 5m20s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 8m13s
CI/CD Pipeline / Build Staging API Image (push) Successful in 17m2s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m10s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 39s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 54s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m7s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1302h51m42s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1302h53m24s
CI/CD Pipeline / Deploy Production (push) Failing after 1302h51m44s
CI/CD Pipeline / Build Production API Image (push) Failing after 1302h53m27s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1302h55m9s
CI/CD Pipeline / PR Build API Image (push) Failing after 1302h55m10s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1302h55m11s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1303h25m51s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1303h27m36s
2026-07-24 00:20:47 +00:00
xiaoxia b055200d0b refactor: 替换废弃的datetime.utcnow()为datetime.now(timezone.utc) (#805)
CI/CD Pipeline / Production Browser E2E (push) Failing after 1303h9m47s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1303h10m43s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1303h10m45s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1303h11m43s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1303h11m44s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1303h11m45s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m48s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m53s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m56s
CI/CD Pipeline / Unit Tests (push) Successful in 6m14s
CI/CD Pipeline / Integration Tests (push) Successful in 2m42s
CI/CD Pipeline / Frontend Lint (push) Successful in 57s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m0s
CI/CD Pipeline / Deploy Production (push) Failing after 1303h42m15s
CI/CD Pipeline / Build Production API Image (push) Failing after 1303h43m13s
CI/CD Pipeline / Build Staging API Image (push) Successful in 16m28s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m23s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 8m33s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m44s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 37m24s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 38m43s
CI/CD Pipeline / PR Build API Image (push) Failing after 1303h44m10s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m15s
2026-07-24 08:03:17 +08:00
xiaoxia 4208b5d940 fix(#781): 替换print为logging,统一日志输出规范 (#804)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 08:03:14 +08:00
xiaoxia c74f1a82d9 refactor(#781): 统一应用层异常定义,消除重复异常类 (#802)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-24 08:03:11 +08:00
xiaoxia 5679daca41 refactor(#779): 消除重复枚举定义,统一到classification模块 (#801)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m55s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 4m50s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m54s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m58s
CI/CD Pipeline / Unit Tests (push) Successful in 6m48s
CI/CD Pipeline / Integration Tests (push) Successful in 2m31s
CI/CD Pipeline / Frontend Lint (push) Successful in 49s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m36s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m52s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m28s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m50s
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Failing after 1303h19m58s
CI/CD Pipeline / Deploy Production (push) Failing after 1303h22m56s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1303h23m42s
CI/CD Pipeline / Build Production API Image (push) Failing after 1303h23m45s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1303h25m40s
CI/CD Pipeline / PR Build API Image (push) Failing after 1303h25m40s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1303h25m42s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1303h56m7s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1303h58m6s
2026-07-24 08:03:08 +08:00
xiaoxia 9958eb6c51 fix(ci): staging-e2e/api-tests容器启动前强制清理,避免DooD模式下命名冲突 (#795)
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 50s
CI/CD Pipeline / Frontend Lint (push) Successful in 29s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 47s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 3m19s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m56s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m53s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15m4s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 38s
CI/CD Pipeline / Unit Tests (push) Successful in 4m26s
CI/CD Pipeline / Integration Tests (push) Successful in 2m7s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m7s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 33s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m18s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1309h42m4s
CI/CD Pipeline / Deploy Production (push) Failing after 1309h57m2s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1310h17m4s
CI/CD Pipeline / Build Production API Image (push) Failing after 1310h17m8s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1310h17m51s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1310h19m53s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1310h17m53s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1310h49m31s
CI/CD Pipeline / PR Build API Image (push) Failing after 1310h50m20s
2026-07-24 00:44:38 +08:00
xiaoxia 20909e2058 fix: 修复ai_tasks.py中3个ruff F401未使用import (#792)
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 44s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 4m59s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m4s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 48s
CI/CD Pipeline / Frontend Lint (push) Successful in 28s
CI/CD Pipeline / Build Staging API Image (push) Successful in 14m43s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m33s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m48s
CI/CD Pipeline / Unit Tests (push) Successful in 4m46s
CI/CD Pipeline / Integration Tests (push) Successful in 2m18s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m22s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 18s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 32s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m9s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1310h10m30s
CI/CD Pipeline / Deploy Production (push) Failing after 1310h22m31s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1310h47m2s
CI/CD Pipeline / Build Production API Image (push) Failing after 1310h47m6s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1310h47m46s
CI/CD Pipeline / PR Build API Image (push) Failing after 1310h47m50s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1310h47m48s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1310h49m10s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1311h19m29s
2026-07-24 00:20:28 +08:00
CI Bot f21d981585 style: auto-format with black + isort + prettier
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m34s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 47s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m5s
CI/CD Pipeline / Unit Tests (push) Successful in 4m57s
CI/CD Pipeline / Integration Tests (push) Successful in 2m13s
CI/CD Pipeline / Frontend Lint (push) Successful in 28s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 54s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m48s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m10s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m19s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m26s
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Failing after 1310h52m27s
CI/CD Pipeline / Deploy Production (push) Failing after 1311h6m46s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1311h15m57s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1311h15m59s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1311h16m31s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1311h16m33s
CI/CD Pipeline / PR Build API Image (push) Failing after 1311h16m35s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1311h18m13s
CI/CD Pipeline / Build Production API Image (push) Failing after 1311h48m25s
2026-07-23 15:57:09 +00:00
xiaoxia ed21ee11d2 refactor(#776): 统一配置入口,建立packages/config消除重复定义 (#791)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-23 23:55:01 +08:00
xiaoxia c6941a738c docs(#780): 补齐.env.example缺失的30+配置项 (#790)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-23 23:54:59 +08:00
xiaoxia d92909321c refactor(#777): 修复Domain层依赖Adapters的DDD违规(4处) (#789)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m12s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m28s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m24s
CI/CD Pipeline / Unit Tests (push) Successful in 6m25s
CI/CD Pipeline / Integration Tests (push) Successful in 5m4s
CI/CD Pipeline / Frontend Lint (push) Successful in 45s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m23s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m44s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m17s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m36s
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Failing after 1311h30m48s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1311h31m38s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1311h31m40s
CI/CD Pipeline / Build Production API Image (push) Failing after 1311h31m42s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1311h33m45s
CI/CD Pipeline / PR Build API Image (push) Failing after 1311h33m49s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1311h33m56s
CI/CD Pipeline / Deploy Production (push) Failing after 1312h3m14s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1312h6m11s
2026-07-23 23:54:56 +08:00
CI Bot a2bddf726e style: auto-format with black + isort + prettier
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m35s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m47s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m44s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m50s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m23s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m59s
CI/CD Pipeline / Frontend Lint (push) Successful in 4m44s
CI/CD Pipeline / Unit Tests (push) Successful in 6m16s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m44s
CI/CD Pipeline / Integration Tests (push) Successful in 2m50s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 53s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 7s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 26s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m23s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1311h54m1s
CI/CD Pipeline / Deploy Production (push) Failing after 1312h1m2s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1312h11m48s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1312h11m50s
CI/CD Pipeline / PR Build API Image (push) Failing after 1312h13m28s
CI/CD Pipeline / Build Production API Image (push) Failing after 1312h11m52s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1312h13m29s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1312h13m26s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1312h45m50s
2026-07-23 15:05:23 +00:00
xiaoxia c052e857ff style: black格式化3个BGM相关测试文件(修复develop push CI Code Quality)(#787)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m1s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m15s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m34s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m30s
CI/CD Pipeline / Unit Tests (push) Successful in 5m57s
CI/CD Pipeline / Integration Tests (push) Successful in 3m16s
CI/CD Pipeline / Frontend Lint (push) Successful in 40s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 50s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m4s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m29s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m25s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 56s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 5m39s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m37s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1312h24m1s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1312h24m7s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1312h24m8s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1312h25m37s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1312h25m39s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1312h25m43s
CI/CD Pipeline / Deploy Production (push) Failing after 1312h56m28s
CI/CD Pipeline / Build Production API Image (push) Failing after 1312h56m33s
CI/CD Pipeline / PR Build API Image (push) Failing after 1312h58m4s
2026-07-23 23:01:01 +08:00
xiaoxia 3862996045 refactor(#783): 统一API文件命名风格为kebab-case (#788)
CI/CD Pipeline / Production Browser E2E (push) Failing after 1312h43m40s
CI/CD Pipeline / Deploy Production (push) Failing after 1312h43m42s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1312h48m16s
CI/CD Pipeline / Build Production API Image (push) Failing after 1312h48m20s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1312h49m4s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1312h51m34s
CI/CD Pipeline / PR Build API Image (push) Failing after 1312h49m8s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m4s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m5s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m56s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 58s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 2m0s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m5s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m12s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 21s
CI/CD Pipeline / Unit Tests (push) Successful in 6m21s
CI/CD Pipeline / Integration Tests (push) Successful in 2m38s
CI/CD Pipeline / Frontend Lint (push) Successful in 46s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m39s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1313h21m29s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m40s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1313h20m41s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 35s
2026-07-23 22:34:56 +08:00
xiaoxia 69845eedc6 chore(#782): 清理废弃文件 init-tables.sql / routers.py / api_docs.html (#785)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-23 22:34:54 +08:00
xiaoxia 389d1e4401 refactor(#775): 禁止API直接调用Worker任务函数,必须走Celery队列或shared层 (#786)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-23 22:34:51 +08:00
xiaoxia a009cd382a fix(ci): 修复integration-tests needs依赖 + push事件auto-fix支持
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
fix(ci): 修复integration-tests needs依赖 + push事件auto-fix支持
2026-07-23 22:33:23 +08:00
xiaoxia 956d90c5c2 refactor(#774): 统一配置管理,消除重复定义和默认值不一致
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 57s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m7s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 56s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 50s
CI/CD Pipeline / Unit Tests (push) Successful in 5m52s
CI/CD Pipeline / Integration Tests (push) Successful in 2m55s
CI/CD Pipeline / Frontend Lint (push) Successful in 27s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m31s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m4s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m45s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m41s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 25s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m11s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 6m53s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1313h0m28s
CI/CD Pipeline / Deploy Production (push) Failing after 1313h0m30s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1313h0m36s
CI/CD Pipeline / Build Production API Image (push) Failing after 1313h0m40s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1313h4m30s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1313h6m39s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1313h4m32s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1313h33m1s
CI/CD Pipeline / PR Build API Image (push) Failing after 1313h36m56s
2026-07-23 22:17:50 +08:00
xiaoxia 678d3503a7 fix(test): 修复JWT篡改签名测试flaky - 改签名中间字符而非最后一位 (#770)
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m5s
CI/CD Pipeline / Frontend Lint (push) Successful in 30s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 57s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m3s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m7s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m23s
CI/CD Pipeline / Integration Tests (push) Successful in 4m1s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m51s
CI/CD Pipeline / Unit Tests (push) Successful in 6m43s
CI/CD Pipeline / Build Staging API Image (push) Successful in 15m42s
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 / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Failing after 1313h13m39s
CI/CD Pipeline / Deploy Production (push) Failing after 1313h21m27s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1313h31m19s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1313h31m21s
CI/CD Pipeline / Build Production API Image (push) Failing after 1313h31m23s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1313h35m3s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1313h35m5s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1313h36m45s
CI/CD Pipeline / PR Build API Image (push) Failing after 1314h7m29s
fix(test): 修复JWT篡改签名测试flaky - 改签名中间字符而非最后一位
2026-07-23 21:52:01 +08:00
xiaoxia 90dd5c6d80 test(p3-1): wave31 素材库+素材UseCase单测20个 (#772)
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m43s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m51s
CI/CD Pipeline / Frontend Lint (push) Successful in 1m53s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m59s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m6s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m1s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m50s
CI/CD Pipeline / Integration Tests (push) Successful in 4m53s
CI/CD Pipeline / Unit Tests (push) Successful in 8m32s
CI/CD Pipeline / Build Staging API Image (push) Successful in 11m55s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 47s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 10s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 30s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m2s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1314h26m42s
CI/CD Pipeline / Deploy Production (push) Failing after 1314h26m44s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1314h28m55s
CI/CD Pipeline / Build Production API Image (push) Failing after 1314h28m57s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1314h30m43s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1314h30m42s
CI/CD Pipeline / PR Build API Image (push) Failing after 1314h30m44s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1314h30m45s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1315h1m16s
2026-07-23 20:46:04 +08:00
xiaoxia e81c7444b0 test(p3-1): wave30 Project领域模型+UseCase单测39个 (#771)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-23 20:45:46 +08:00
xiaoxia 081e58e5a0 fix: 修复2个ruff F401未使用import错误(BGM PR引入) (#773)
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m14s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m31s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m26s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m20s
CI/CD Pipeline / Unit Tests (push) Successful in 7m17s
CI/CD Pipeline / Integration Tests (push) Successful in 4m26s
CI/CD Pipeline / Frontend Lint (push) Successful in 37s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 43s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m38s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m37s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m37s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m6s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m9s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 6m37s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1314h40m8s
CI/CD Pipeline / Deploy Production (push) Failing after 1314h40m10s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1314h40m32s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1314h40m34s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1314h43m27s
CI/CD Pipeline / Build Production API Image (push) Failing after 1314h40m36s
CI/CD Pipeline / PR Build API Image (push) Failing after 1314h43m31s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1314h44m20s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1315h15m51s
fix: 修复2个ruff F401未使用import错误(BGM PR引入)
2026-07-23 20:44:36 +08:00
xiaoxia 486f1a092e style: black格式化3个AI相关文件(修复develop Validate Code Quality失败) (#768)
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m23s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m15s
CI/CD Pipeline / Frontend Lint (push) Successful in 40s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m12s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m4s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m8s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 44s
CI/CD Pipeline / Integration Tests (push) Successful in 3m33s
CI/CD Pipeline / Unit Tests (push) Successful in 7m9s
CI/CD Pipeline / Build Staging API Image (push) Successful in 13m15s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 52s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 25s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m59s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m50s
CI/CD Pipeline / Production Browser E2E (push) Failing after 1315h0m45s
CI/CD Pipeline / Deploy Production (push) Failing after 1315h0m47s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1315h1m0s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1315h0m58s
CI/CD Pipeline / Build Production API Image (push) Failing after 1315h1m2s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1315h8m33s
CI/CD Pipeline / PR Build API Image (push) Failing after 1315h8m37s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1315h10m5s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1315h40m57s
2026-07-23 20:10:42 +08:00
xiaoxia 021001073b fix(ci): 修复Staging E2E/API Tests DooD挂载路径问题 - 改用docker cp (#769)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-23 20:04:20 +08:00
xiaoxia 7d92f82900 Merge pull request 'feat(#642): 一键生成支持自定义BGM' (#763) from feat/custom-bgm-generation into develop
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-23 19:45:08 +08:00
xiaoxia 3e621bee2c fix(ci): P0系统性修复 - concurrency/UnitTests/门禁同步(4项全改) (#765)
CI/CD Pipeline / Check if frontend-only change (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (push) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (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 / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (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
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
2026-07-23 19:38:03 +08:00
xiaoxia 5629cf63ec fix(web): 修复微信回调页token时序bug - 先存token再取用户信息 (#764)
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 / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Failing after 1315h47m45s
CI/CD Pipeline / Build Production API Image (push) Failing after 1315h47m49s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 1315h52m20s
CI/CD Pipeline / PR Build API Image (push) Failing after 1315h52m24s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 1315h53m9s
CI/CD Pipeline / Validate - Code Quality (push) Failing after 1m23s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m22s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m17s
CI/CD Pipeline / Unit Tests (push) Successful in 7m50s
CI/CD Pipeline / Integration Tests (push) Successful in 3m43s
CI/CD Pipeline / Frontend Lint (push) Successful in 34s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 53s
CI/CD Pipeline / PR Build Web Image (push) Failing after 1316h24m44s
CI/CD Pipeline / Build Staging API Image (push) Successful in 6m55s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m7s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 3m4s
CI/CD Pipeline / Build Production Web Image (push) Failing after 1316h20m9s
2026-07-23 19:35:46 +08:00
CI Bot a9896e1507 feat(#642): 一键生成支持自定义BGM
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 21s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 37s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 25s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m21s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m23s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 1m55s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 23s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 59s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 53s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 4m34s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m40s
AI Code Review / AI Code Review (pull_request) Successful in 4m4s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 5m49s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 10m3s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 19s
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1317h0m15s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1317h0m19s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1317h0m21s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1317h0m27s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1317h0m23s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1317h0m29s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1317h0m31s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1317h1m57s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1317h1m57s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1317h1m59s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1317h32m46s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1317h32m38s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 1317h32m54s
- 领域模型 GenerationTask 新增 bgm_config 字段
- ORM/Repository/UseCase/API 全链路透传 bgm_config
- Worker 端新增 BGM 配置合并逻辑(用户配置 > 模板配置)
- enabled 字段特殊处理:用户显式传才覆盖模板状态
- 合并逻辑抽至 packages/domain/bgm_utils.py 纯函数
- 14个BGM合并单测 + 2个领域单测,全量4246通过
2026-07-23 18:25:06 +08:00
374 changed files with 49704 additions and 18638 deletions
+180 -42
View File
@@ -1,60 +1,198 @@
# 小虾 SaaS 环境变量配置
# ============================================================
# 小虾 SaaS 环境变量完整配置
# ============================================================
# 本文件列出所有可配置的环境变量及默认值。
# 复制为 .env 后按需修改;生产环境务必覆盖所有密钥类配置。
#
# 配置读取规则(pydantic-settings,大小写不敏感):
# 1. 系统环境变量(最高优先级)
# 2. .env.{APP_ENV} 文件(如 .env.staging)
# 3. .env 文件
# 4. 代码中的默认值(最低优先级)
# ============================================================
# ==================== 应用配置 ====================
APP_NAME=小虾 SaaS
APP_BASE_URL=http://localhost:3000
# ==================== 应用基本配置 ====================
# 应用名称
APP_NAME=xiaoxia-saas
# 应用版本号(展示用,代码中已内置默认)
APP_VERSION=0.1.61
# 环境标识:development / staging / production
# 决定读取 .env.{APP_ENV} 还是 .env,也影响部分配置的严格校验
APP_ENV=development
# ==================== 数据库配置 ====================
DATABASE_URL=postgresql://xiaoxia_user:your_password@localhost:5432/xiaoxia_saas
# 开发环境:使用内存数据库(不需要 PostgreSQL)
USE_IN_MEMORY_DB=true
# 生产环境:使用 PostgreSQL
# USE_IN_MEMORY_DB=false
# ==================== Redis 配置 ====================
REDIS_URL=redis://localhost:6379/0
# ==================== JWT 配置 ====================
JWT_SECRET_KEY=your-super-secret-key-change-this-in-production-min-32-chars
JWT_ALGORITHM=HS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30
JWT_REFRESH_TOKEN_EXPIRE_DAYS=30
# ==================== 邮件配置 ====================
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASSWORD=your-app-specific-password
SMTP_FROM_EMAIL=noreply@xiaoxia-saas.com
SMTP_FROM_NAME=小虾 SaaS
# ==================== 环境配置 ====================
ENVIRONMENT=development
# 是否开启 Debug 模式(开发环境 true,生产环境 false)
DEBUG=true
# ==================== CORS 配置 ====================
# 逗号分隔的域名列表(Settings 读取 CORS_ORIGINS_RAW)
CORS_ORIGINS_RAW=http://localhost:3000,http://localhost:5173
# 应用基础 URL,用于生成认证邮件、回调链接等
APP_BASE_URL=http://localhost:3000
# API 服务监听地址(容器内绑定,外部暴露由 Docker/Nginx 控制)
API_HOST=0.0.0.0
# API 服务监听端口
API_PORT=8000
# 是否自动创建数据库表结构(开发环境可开启,生产环境用 alembic migration)
AUTO_CREATE_SCHEMA=false
# ==================== 数据库配置 ====================
# 数据库连接串(格式:postgresql+psycopg://user:password@host:port/dbname)
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/xiaoxia_saas
# 连接池大小(常驻连接数)
DATABASE_POOL_SIZE=20
# 连接池最大溢出连接数(pool_size + max_overflow = 最大并发连接数)
DATABASE_MAX_OVERFLOW=10
# 获取连接超时时间(秒)
DATABASE_POOL_TIMEOUT=30
# 连接回收时间(秒),防止数据库端主动断开导致的死连接
DATABASE_POOL_RECYCLE=3600
# 是否使用内存数据库(SQLite,仅开发/测试可用;生产务必 false)
USE_IN_MEMORY_DB=false
# ==================== Redis 配置 ====================
# Redis 连接 URL(格式:redis://[:password@]host:port/db)
REDIS_URL=redis://localhost:6379/0
# 是否使用 Redis 存储 Session(多实例部署时必须开启;开发可用内存存储)
ENABLE_REDIS_SESSIONS=false
# ==================== Celery 任务队列 ====================
# Celery Broker(任务分发),默认用 Redis db0
CELERY_BROKER_URL=redis://localhost:6379/0
# Celery Result Backend(任务结果存储),默认用 Redis db1
CELERY_RESULT_BACKEND=redis://localhost:6379/1
# ==================== Worker 配置 ====================
# Worker 进程名称
WORKER_NAME=xiaoxia-saas-worker
# Worker 并发数(同时执行的任务数)
WORKER_CONCURRENCY=4
# 每个子进程最多处理多少任务后重启(防止内存泄漏)
WORKER_MAX_TASKS_PER_CHILD=1000
# ==================== JWT 认证配置 ====================
# JWT 签名密钥 — 生产环境必须设置为强随机字符串(至少32字符)
# 内置不安全值会被拒绝:secret / changeme / password / your-secret-key 等
JWT_SECRET_KEY=your-super-secret-key-change-this-in-production-min-32-chars
# JWT 签名算法
JWT_ALGORITHM=HS256
# Access Token 过期时间(分钟)
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30
# Refresh Token 过期时间(天)
JWT_REFRESH_TOKEN_EXPIRE_DAYS=30
# ==================== 邮件配置 ====================
# 是否启用邮件投递(关闭时邮件内容打印到日志,开发调试用)
ENABLE_EMAIL_DELIVERY=false
# SMTP 服务器地址
SMTP_HOST=smtp.gmail.com
# SMTP 端口
SMTP_PORT=587
# SMTP 用户名
SMTP_USER=your-email@gmail.com
# SMTP 密码 / 应用专用密码
SMTP_PASSWORD=your-app-specific-password
# 发件人邮箱
SMTP_FROM_EMAIL=noreply@xiaoxia-saas.com
# 发件人显示名称
SMTP_FROM_NAME=小虾 SaaS
# 是否启用 TLS
SMTP_USE_TLS=true
# ==================== 阿里云 OSS 配置 ====================
# OSS 区域 endpoint
OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com
# OSS Access Key ID — 非开发环境必须设置
OSS_ACCESS_KEY_ID=your-access-key-id
# OSS Access Key Secret — 非开发环境必须设置
OSS_ACCESS_KEY_SECRET=your-access-key-secret
# OSS Bucket 名称
OSS_BUCKET_NAME=xiaoxia-autocut
# ==================== CosyVoice 语音合成配置 ====================
# 注意:base_url 只需写到 /api/v1,具体路径由代码拼接
# 模型: cosyvoice-v3-flash (推荐,支持系统音色,性价比高)
# cosyvoice-v3-plus (高质量,系统音色少)
# cosyvoice-v3.5-flash / cosyvoice-v3.5-plus (仅支持克隆/设计音色,无系统音色)
# 音色: v3系列系统音色带 _v3 后缀,如 longxiaochun_v3, longxiaoxia_v3, longanyang (无后缀)
# 注意:COSYVOICE_* 变量由 packages/shared/config.py 的 SharedSettings 读取
# 直传最大文件大小(MB)
OSS_DIRECT_UPLOAD_MAX_MB=2000
# 直传签名有效期(秒)
OSS_DIRECT_UPLOAD_EXPIRE_SECONDS=900
# ==================== CORS 配置 ====================
# 允许跨域的前端域名列表,逗号分隔
CORS_ORIGINS_RAW=http://localhost:3000,http://localhost:5173,http://localhost:8000
# ==================== 渲染引擎配置 ====================
# 渲染引擎选择:
# legacy — 旧 VideoComposeService(稳定,功能完整)
# unified — 新 UnifiedRenderService(新架构,部分场景仍在验证)
RENDER_ENGINE=legacy
# ==================== CosyVoice 语音合成 ====================
# 阿里云百灵语音合成服务
# 模型选择:
# cosyvoice-v3-flash — 推荐,系统音色多,性价比高
# cosyvoice-v3-plus — 高质量,系统音色少
# cosyvoice-v3.5-flash / cosyvoice-v3.5-plus — 仅支持克隆/设计音色,无系统音色
# 音色:v3 系列系统音色带 _v3 后缀,如 longxiaochun_v3 / longxiaoxia_v3 / longanyang
COSYVOICE_API_KEY=your-cosyvoice-api-key
COSYVOICE_BASE_URL=https://dashscope.aliyuncs.com/api/v1
COSYVOICE_MODEL=cosyvoice-v3-flash
COSYVOICE_VOICE=longxiaochun_v3
COSYVOICE_SAMPLE_RATE=22050
COSYVOICE_FORMAT=mp3
# 音色克隆模型名(固定为 voice-enrollment,通常不需修改)
COSYVOICE_CLONE_MODEL=voice-enrollment
# ==================== 豆包大模型(火山引擎方舟) ====================
# 用于 AI 文案生成、智能剪辑等需要大模型能力的场景
DOUBAO_API_KEY=your-doubao-api-key
DOUBAO_MODEL=doubao-seed-1-6-250615
DOUBAO_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
DOUBAO_TIMEOUT=30
DOUBAO_MAX_RETRIES=2
+162
View File
@@ -0,0 +1,162 @@
name: ACR Cleanup
on:
schedule:
- cron: '0 19 * * *' # UTC 19:00 = 北京时间凌晨3:00
workflow_dispatch:
inputs:
pr_sha:
description: "PR commit SHA(仅清理指定PR镜像,留空则全量清理)"
required: false
default: ""
dry_run:
description: "预览模式(dry-run),不实际删除"
required: false
default: "true"
pull_request_target:
types: [closed]
branches: [develop, main]
concurrency:
group: acr-cleanup-${{ gitea.ref }}
cancel-in-progress: false
jobs:
cleanup:
name: ACR Image Cleanup
runs-on: ci-l2
timeout-minutes: 20
permissions:
contents: read
env:
ACR_REGISTRY: xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com
ACR_NAMESPACE: xiaoxiakeji
ACR_SERVICE: registry.aliyuncs.com:cn-hangzhou:china:cri-fvec8o9q4mmxrkaa
GITEA_URL: https://git.xiaoxiajianji.com
GITEA_REPO: xiaoxia/xiaoxia-saas
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
# ====== Cron模式:获取staging运行中镜像作为白名单 ======
- name: Get staging running images (whitelist)
id: protected_images
if: gitea.event_name != 'pull_request_target' && !gitea.event.inputs.pr_sha
env:
STAGING_SSH_KEY: ${{ secrets.PREVIEW_SSH_KEY }}
run: |
set +e
echo "获取staging服务器运行中镜像作为白名单..."
mkdir -p ~/.ssh
echo "$STAGING_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
staging_host="${STAGING_SSH_HOST:-47.98.113.167}"
staging_port="${STAGING_SSH_PORT:-22222}"
ssh-keyscan -p "$staging_port" -H "$staging_host" >> ~/.ssh/known_hosts 2>/dev/null
# 获取所有运行容器的镜像,提取tag部分
IMAGES=$(ssh -p "$staging_port" -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no \
"root@$staging_host" "docker ps --format '{{.Image}}' 2>/dev/null" 2>/dev/null | grep -v "^$" | sort -u)
PROTECTED_TAGS=""
if [ -n "$IMAGES" ]; then
while IFS= read -r img; do
# 从完整镜像名中提取tag(最后一个冒号后)
tag=$(echo "$img" | rev | cut -d: -f1 | rev)
if [ -n "$tag" ] && [ "$tag" != "latest" ] && [ ${#tag} -gt 5 ]; then
if [ -z "$PROTECTED_TAGS" ]; then
PROTECTED_TAGS="$tag"
else
PROTECTED_TAGS="$PROTECTED_TAGS,$tag"
fi
fi
done <<< "$IMAGES"
fi
echo "staging运行中镜像tag: ${PROTECTED_TAGS:-(无)}"
echo "protected_tags=$PROTECTED_TAGS" >> $GITEA_OUTPUT
# ====== Docker登录 ======
- name: Docker login to ACR
env:
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
run: |
printf '%s' "$ACR_PASSWORD" | docker login "$ACR_REGISTRY" -u "$ACR_USERNAME" --password-stdin
# ====== 模式1:PR关闭时清理 ======
- name: Cleanup PR images (PR closed)
if: gitea.event_name == 'pull_request_target'
env:
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
PR_SHA: ${{ gitea.event.pull_request.head.sha }}
PR_NUMBER: ${{ gitea.event.pull_request.number }}
run: |
echo "============================================"
echo " PR #$PR_NUMBER 已关闭,清理对应镜像"
echo " Head SHA: ${PR_SHA::12}"
echo "============================================"
echo ""
python3 scripts/ci/acr_cleanup.py \
--pr-sha "$PR_SHA" \
--execute
# ====== 模式2:Cron全量清理 ======
- name: Full cleanup (cron / manual)
if: gitea.event_name != 'pull_request_target' && !gitea.event.inputs.pr_sha
env:
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
PROTECTED_TAGS: ${{ steps.protected_images.outputs.protected_tags }}
DRY_RUN_INPUT: ${{ gitea.event.inputs.dry_run }}
run: |
echo "============================================"
echo " ACR 全量清理(${{ gitea.event_name }})"
echo "============================================"
echo ""
# 决定是否dry-run
DRY_RUN_FLAG=""
if [ "$DRY_RUN_INPUT" = "true" ]; then
DRY_RUN_FLAG="--dry-run"
echo "模式: 预览模式 (dry-run)"
else
echo "模式: 执行模式"
fi
echo ""
python3 scripts/ci/acr_cleanup.py \
--keep 20 \
--protected-tags "$PROTECTED_TAGS" \
$DRY_RUN_FLAG
# ====== 模式3:手动指定PR SHA清理 ======
- name: Cleanup specific PR image (manual)
if: gitea.event_name == 'workflow_dispatch' && gitea.event.inputs.pr_sha
env:
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
PR_SHA: ${{ gitea.event.inputs.pr_sha }}
DRY_RUN_INPUT: ${{ gitea.event.inputs.dry_run }}
run: |
echo "手动清理PR镜像: ${PR_SHA::12}"
echo ""
DRY_RUN_FLAG=""
if [ "$DRY_RUN_INPUT" = "true" ]; then
DRY_RUN_FLAG="--dry-run"
fi
python3 scripts/ci/acr_cleanup.py \
--pr-sha "$PR_SHA" \
$DRY_RUN_FLAG
+216 -73
View File
@@ -22,7 +22,19 @@ permissions:
contents: read
concurrency:
group: ci-pipeline-${{ gitea.event_name }}-${{ gitea.ref }}
cancel-in-progress: true
# PR事件取消进行中的旧run,push事件不取消(确保完整CI跑完)
cancel-in-progress: ${{ gitea.event_name == 'pull_request' }}
env:
CI_PG_HOST: host.docker.internal
CI_LOCAL_PG_PORT: "5432"
CI_PG_USER: postgres
CI_PG_PASSWORD: postgres
CI_PG_DB: xiaoxia_saas
CI_SHARED_PG_PORT: "5433"
CI_SHARED_PG_USER: postgres
CI_SHARED_PG_PASSWORD: ci_pg_2026!
CI_DEFAULT_DB: xiaoxia_saas
jobs:
check-frontend-only:
name: Check if frontend-only change
@@ -250,7 +262,7 @@ jobs:
permissions:
contents: read
env:
DATABASE_URL: postgresql+psycopg://postgres:postgres@host.docker.internal:5432/xiaoxia_saas
DATABASE_URL: postgresql+psycopg://${{ env.CI_PG_USER }}:${{ env.CI_PG_PASSWORD }}@${{ env.CI_PG_HOST }}:${{ env.CI_LOCAL_PG_PORT }}/${{ env.CI_PG_DB }}
USE_IN_MEMORY_DB: 'false'
CI_USE_SHARED_PG: 'true'
steps:
@@ -334,6 +346,7 @@ jobs:
OSS_ACCESS_KEY_SECRET: placeholder
OSS_BUCKET_NAME: xiaoxia-autocut
OSS_ENDPOINT: oss-cn-hangzhou.aliyuncs.com
JWT_SECRET_KEY: test-jwt-secret-for-ci-only-2026
steps:
- name: Checkout code
shell: sh
@@ -394,15 +407,18 @@ jobs:
if: always() && needs.check-frontend-only.outputs.skip_backend != 'true'
needs:
- check-frontend-only
- validate
- validate-code-quality
- validate-type-check
- validate-migration
env:
DATABASE_URL: postgresql+psycopg://postgres:postgres@host.docker.internal:5432/xiaoxia_saas
DATABASE_URL: postgresql+psycopg://${{ env.CI_PG_USER }}:${{ env.CI_PG_PASSWORD }}@${{ env.CI_PG_HOST }}:${{ env.CI_LOCAL_PG_PORT }}/${{ env.CI_PG_DB }}
USE_IN_MEMORY_DB: 'false'
CI_USE_SHARED_PG: 'true'
OSS_ACCESS_KEY_ID: placeholder
OSS_ACCESS_KEY_SECRET: placeholder
OSS_BUCKET_NAME: xiaoxia-autocut
OSS_ENDPOINT: oss-cn-hangzhou.aliyuncs.com
JWT_SECRET_KEY: test-jwt-secret-for-ci-only-2026
steps:
- name: Checkout code
shell: sh
@@ -624,65 +640,83 @@ jobs:
echo "Docker login failed ($i/3), retrying in 5s..."
sleep 5
done
- name: Pre-build worker base images (fallback if not exist)
- name: Pre-build worker base images (3-level cache)
if: matrix.service == 'worker'
id: prebuild
shell: sh
shell: bash
run: |
set -eu
REGISTRY="git.xiaoxiajianji.com/xiaoxia-saas"
BASE_BUILDER="${REGISTRY}/worker-base-builder:latest"
BASE_RUNTIME="${REGISTRY}/worker-base-runtime:latest"
# 尝试拉取基础镜像
echo "检查基础镜像..."
if docker pull "$BASE_BUILDER" 2>/dev/null && docker pull "$BASE_RUNTIME" 2>/dev/null; then
echo "基础镜像已存在,使用远程镜像"
echo "fallback=false" >> $GITHUB_OUTPUT
else
echo "基础镜像不存在,本地构建(fallback模式)..."
# 构建builder基础镜像
echo "构建 worker-base-builder..."
# 用buildx docker-container驱动构建(兼容DooD模式:普通docker build看不到容器内文件)
BUILDER_NAME="ci-pr-builder-${GITHUB_RUN_ID:-local}"
if ! docker buildx inspect "$BUILDER_NAME" > /dev/null 2>&1; then
docker buildx create --use --name "$BUILDER_NAME" --driver docker-container
else
docker buildx use "$BUILDER_NAME"
fi
docker buildx inspect --bootstrap > /dev/null 2>&1
# 构建builder基础镜像(带重试,buildx容器偶发不稳定)
echo "构建 worker-base-builder..."
for attempt in 1 2 3; do
if docker buildx build --load -f infra/docker/worker-base-builder.Dockerfile -t "$BASE_BUILDER" .; then
echo "worker-base-builder 构建成功"
break
fi
echo "worker-base-builder 构建失败,重试 $attempt/3..."
docker buildx rm "$BUILDER_NAME" 2>/dev/null || true
docker buildx create --use --name "$BUILDER_NAME" --driver docker-container
sleep 3
done
# 构建runtime基础镜像
echo "构建 worker-base-runtime..."
for attempt in 1 2 3; do
if docker buildx build --load -f infra/docker/worker-base-runtime.Dockerfile -t "$BASE_RUNTIME" .; then
echo "worker-base-runtime 构建成功"
break
fi
echo "worker-base-runtime 构建失败,重试 $attempt/3..."
docker buildx rm "$BUILDER_NAME" 2>/dev/null || true
docker buildx create --use --name "$BUILDER_NAME" --driver docker-container
sleep 3
done
echo "fallback=true" >> $GITHUB_OUTPUT
echo "基础镜像本地构建完成"
fi
GITEA_REGISTRY="git.xiaoxiajianji.com/xiaoxia-saas"
ACR_REGISTRY="xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com/xiaoxiakeji"
GITEA_BUILDER="${GITEA_REGISTRY}/worker-base-builder:latest"
GITEA_RUNTIME="${GITEA_REGISTRY}/worker-base-runtime:latest"
ACR_BUILDER="${ACR_REGISTRY}/worker-base-builder:latest"
ACR_RUNTIME="${ACR_REGISTRY}/worker-base-runtime:latest"
# L1: 本地daemon缓存(DooD模式8runner共享宿主机daemon)
echo "=== L1 本地缓存 ==="
if docker image inspect "$ACR_BUILDER" > /dev/null 2>&1 \
&& docker image inspect "$ACR_RUNTIME" > /dev/null 2>&1; then
echo "本地缓存命中"
echo "has_local_base=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "本地无缓存"
# L2: Gitea registry缓存(内网快)
echo "=== L2 Registry拉取 ==="
if docker pull "$GITEA_BUILDER" 2>/dev/null && docker pull "$GITEA_RUNTIME" 2>/dev/null; then
echo "Registry拉取成功,重tag供Dockerfile使用"
docker tag "$GITEA_BUILDER" "$ACR_BUILDER"
docker tag "$GITEA_RUNTIME" "$ACR_RUNTIME"
echo "has_local_base=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "Registry无缓存,需本地构建"
# L3: 本地构建
echo "=== L3 本地构建 ==="
BUILDER_NAME="ci-pr-builder-${GITHUB_RUN_ID:-local}"
if ! docker buildx inspect "$BUILDER_NAME" > /dev/null 2>&1; then
docker buildx create --use --name "$BUILDER_NAME" --driver docker-container
else
docker buildx use "$BUILDER_NAME"
fi
docker buildx inspect --bootstrap > /dev/null 2>&1
echo "构建 worker-base-builder..."
for attempt in 1 2 3; do
if docker buildx build --load -f infra/docker/worker-base-builder.Dockerfile -t "$ACR_BUILDER" .; then
echo "worker-base-builder 构建成功"
break
fi
echo "worker-base-builder 失败,重试 $attempt/3..."
docker buildx rm "$BUILDER_NAME" 2>/dev/null || true
docker buildx create --use --name "$BUILDER_NAME" --driver docker-container
sleep 3
done
echo "构建 worker-base-runtime..."
for attempt in 1 2 3; do
if docker buildx build --load -f infra/docker/worker-base-runtime.Dockerfile -t "$ACR_RUNTIME" .; then
echo "worker-base-runtime 构建成功"
break
fi
echo "worker-base-runtime 失败,重试 $attempt/3..."
docker buildx rm "$BUILDER_NAME" 2>/dev/null || true
docker buildx create --use --name "$BUILDER_NAME" --driver docker-container
sleep 3
done
# 推送到Gitea registry供后续复用
echo "=== 推送缓存到Registry ==="
docker tag "$ACR_BUILDER" "$GITEA_BUILDER"
docker tag "$ACR_RUNTIME" "$GITEA_RUNTIME"
docker push "$GITEA_BUILDER" 2>/dev/null || echo "push builder失败(不影响)"
docker push "$GITEA_RUNTIME" 2>/dev/null || echo "push runtime失败(不影响)"
echo "has_local_base=true" >> $GITHUB_OUTPUT
echo "基础镜像构建完成"
- name: Build PR image (verify only, no push)
shell: sh
run: |
@@ -696,15 +730,15 @@ jobs:
EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS NGINX_CONF=infra/docker/nginx-staging.conf"
fi
# Worker fallback模式:基础镜像本地已构建,用普通docker build绕过buildx
if [ "${{ matrix.service }}" = "worker" ] && [ "${{ steps.prebuild.outputs.fallback }}" = "true" ]; then
echo "Fallback模式:用普通docker build(基础镜像本地已构建)"
# Worker有本地base镜像时:用BuildKit直接构建(快,无需起buildx容器)
if [ "${{ matrix.service }}" = "worker" ] && [ "${{ steps.prebuild.outputs.has_local_base }}" = "true" ]; then
echo "本地base镜像已就绪,BuildKit快速构建"
BUILD_ARG_STR=""
for arg in $EXTRA_BUILD_ARGS; do
BUILD_ARG_STR="$BUILD_ARG_STR --build-arg $arg"
done
docker build -f ${{ matrix.dockerfile }} -t "${IMAGE_TAG}" $BUILD_ARG_STR .
echo "Fallback PR Build successful"
DOCKER_BUILDKIT=1 docker build -f ${{ matrix.dockerfile }} -t "${IMAGE_TAG}" $BUILD_ARG_STR .
echo "快速构建成功"
exit 0
fi
@@ -1080,18 +1114,27 @@ jobs:
shell: sh
run: bash scripts/ci/step_timer_start.sh
- name: Run Playwright E2E on staging
shell: sh
shell: bash
run: |
set -eu
docker run --rm --ipc=host \
# DooD模式下不能用-v挂载(宿主机路径与CI容器路径不一致)
# 改用 docker create + docker cp 方式把代码拷进容器
CONTAINER_NAME="staging-e2e-${GITHUB_SHA::8}"
docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
docker create --name "$CONTAINER_NAME" --ipc=host \
-e E2E_BASE_URL=https://staging.xiaoxiajianji.com \
-e E2E_API_BASE=https://staging-api.xiaoxiajianji.com/api/v1 \
-e E2E_BROWSER_CHANNEL=chromium \
-e PLAYWRIGHT_HEADLESS=1 \
-v "$PWD:/workspace" \
-w /workspace/apps/web \
git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \
sh -lc "npm ci && npx playwright test --reporter=line --project=chromium e2e/auth.spec.ts e2e/auth-guard.spec.ts e2e/core-upload.spec.ts e2e/core-generation.spec.ts"
docker cp apps "$CONTAINER_NAME:/workspace/"
docker cp package-lock.json "$CONTAINER_NAME:/workspace/" 2>/dev/null || true
docker start -a "$CONTAINER_NAME"
EXIT_CODE=$(docker wait "$CONTAINER_NAME")
docker rm "$CONTAINER_NAME" 2>/dev/null || true
exit $EXIT_CODE
- name: Job duration summary
if: always()
@@ -1136,16 +1179,25 @@ jobs:
shell: sh
run: bash scripts/ci/step_timer_start.sh
- name: Run API integration tests on staging
shell: sh
shell: bash
run: |
set -eu
docker run --rm \
# DooD模式下不能用-v挂载(宿主机路径与CI容器路径不一致)
# 改用 docker create + docker cp 方式把代码拷进容器
CONTAINER_NAME="staging-api-tests-${GITHUB_SHA::8}"
docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
docker create --name "$CONTAINER_NAME" \
-e E2E_BASE_URL=https://staging.xiaoxiajianji.com \
-e E2E_API_BASE=https://staging-api.xiaoxiajianji.com/api/v1 \
-v "$PWD:/workspace" \
-w /workspace/apps/web \
git.xiaoxiajianji.com/xiaoxia/base/playwright:v1.45.0-jammy \
sh -lc 'npm ci && npx playwright test --reporter=line e2e/test_auth.spec.ts e2e/test_asset.spec.ts e2e/test_project.spec.ts'
docker cp apps "$CONTAINER_NAME:/workspace/"
docker cp package-lock.json "$CONTAINER_NAME:/workspace/" 2>/dev/null || true
docker start -a "$CONTAINER_NAME"
EXIT_CODE=$(docker wait "$CONTAINER_NAME")
docker rm "$CONTAINER_NAME" 2>/dev/null || true
exit $EXIT_CODE
- name: Job duration summary
if: always()
@@ -1178,7 +1230,7 @@ jobs:
runs-on: runtime-builder
timeout-minutes: ${{ matrix.timeout }}
needs:
if: startsWith(github.ref, 'refs/tags/v')
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'push' && github.ref_name == 'main')
strategy:
fail-fast: false
matrix:
@@ -1255,10 +1307,16 @@ jobs:
run: |
set -eu
REGISTRY="xiaoxia-registry.cn-hangzhou.cr.aliyuncs.com/xiaoxiakeji"
IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:${GITHUB_REF_NAME}"
# 根据ref类型设置镜像标签:tag用版本号,分支用分支名+sha
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
TAG_NAME="${GITHUB_REF_NAME}"
else
TAG_NAME="${GITHUB_REF_NAME}-${GITHUB_SHA::8}"
fi
IMAGE_TAG="${REGISTRY}/${{ matrix.image_name }}:${TAG_NAME}"
CACHE_REF="${REGISTRY}/${{ matrix.cache_name }}:main"
EXTRA_BUILD_ARGS="APP_VERSION=\"${GITHUB_REF_NAME}\""
EXTRA_BUILD_ARGS="APP_VERSION=\"${TAG_NAME}\""
if [ "${{ matrix.service }}" = "web" ]; then
EXTRA_BUILD_ARGS="$EXTRA_BUILD_ARGS NGINX_CONF=infra/docker/nginx-production.conf"
fi
@@ -1556,4 +1614,89 @@ jobs:
[ ${{ job.status }} = "success" ] || STATUS="error"
START_TIME=""
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
canary-release:
name: Canary Release to Production
runs-on: runtime-builder
timeout-minutes: 120
concurrency:
group: canary-release-production
cancel-in-progress: false
if: github.event_name == 'push' && github.ref_name == 'main'
needs:
- build-production
- staging-api-tests
steps:
- name: Checkout code
shell: sh
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
curl -sH "Authorization: token $GITHUB_TOKEN" "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/raw/scripts/ci/step_checkout.sh?ref=${GITHUB_SHA}" | bash
- name: Record job start time
shell: sh
run: bash scripts/ci/step_timer_start.sh
- name: Notify canary release start
continue-on-error: true
shell: sh
env:
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: |
set +e
NOTIFY_MODE=start JOB_NAME="Canary Release" python3 scripts/ci_notify.py
- name: Install SSH client
shell: sh
run: |
set -eu
apt-get update -qq && apt-get install -y -qq openssh-client curl >/dev/null 2>&1
echo "openssh-client installed"
- name: Run canary release
shell: bash
env:
PRODUCTION_SSH_HOST: ${{ secrets.PRODUCTION_SSH_HOST }}
PRODUCTION_SSH_USER: ${{ secrets.PRODUCTION_SSH_USER }}
PRODUCTION_SSH_PORT: ${{ secrets.PRODUCTION_SSH_PORT }}
PRODUCTION_SSH_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: |
set -eu
IMAGE_TAG="main-${GITHUB_SHA::8}"
export IMAGE_TAG
echo "Canary release version: $IMAGE_TAG"
bash scripts/ci/canary_release.sh
- name: Job duration summary
if: always()
shell: sh
run: bash scripts/ci/step_timer_end.sh
- name: Notify on success
continue-on-error: true
if: success()
shell: sh
env:
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: |
set +e
NOTIFY_MODE=success JOB_NAME="Canary Release" python3 scripts/ci_notify.py
- name: Notify on failure
continue-on-error: true
if: failure()
shell: sh
env:
CI_NOTIFY_WEBHOOK: ${{ secrets.CI_NOTIFY_WEBHOOK }}
run: |
set +e
NOTIFY_MODE=failure JOB_NAME="Canary Release" python3 scripts/ci_notify.py
- name: Report CI trace
if: always()
shell: sh
env:
AGENTLOOP_LICENSE_KEY: ${{ secrets.AGENTLOOP_LICENSE_KEY }}
run: |
STATUS="ok"
[ ${{ job.status }} = "success" ] || STATUS="error"
START_TIME=""
[ -f /tmp/ci_job_start_time ] && START_TIME=$(cat /tmp/ci_job_start_time)
python3 scripts/ci/ci_trace_report.py --service xiaoxia-saas-ci --status $STATUS --start-time "$START_TIME" || true
+2 -2
View File
@@ -115,11 +115,11 @@ jobs:
fi
if [ "$CACHE_VALID" = "false" ]; then
echo "Cache miss or invalid: running npm ci..."
if ! npm ci --include=dev; then
if ! npm ci; then
echo "npm ci failed, cleaning node_modules and retrying..."
rm -rf node_modules
mkdir -p node_modules
npm ci --include=dev
npm ci
fi
echo "$PACKAGE_LOCK_HASH" > "$CACHE_HASH_FILE"
echo "Dependencies installed, cache updated"
Regular → Executable
+7 -4
View File
@@ -1,4 +1,3 @@
import os
from logging.config import fileConfig
from sqlalchemy import engine_from_config, pool
@@ -8,13 +7,17 @@ from alembic import context
# Import your models' Base here
from packages.adapters.sqlalchemy_impl.models import Base
# 使用统一配置入口获取 database_url,而非直接读环境变量
from packages.config import get_shared_settings
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
database_url = os.getenv("DATABASE_URL")
if database_url:
config.set_main_option("sqlalchemy.url", database_url)
# 从统一配置系统获取 database_url,确保与应用使用同一配置源
settings = get_shared_settings()
if settings.database_url:
config.set_main_option("sqlalchemy.url", settings.database_url)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,3 +1,4 @@
from app.api.routes.ai import router as ai_router
from app.api.routes.asset_diagnosis import router as asset_diagnosis_router
from app.api.routes.asset_libraries import router as asset_libraries_router
from app.api.routes.assets import router as assets_router
@@ -9,7 +10,6 @@ from app.api.routes.feature_flags import router as feature_flags_router
from app.api.routes.generation_tasks import router as generation_tasks_router
from app.api.routes.health import router as health_check_router
from app.api.routes.ingest_jobs import router as ingest_jobs_router
from app.api.routes.ai import router as ai_router
from app.api.routes.internal_render import router as internal_render_router
from app.api.routes.projects import router as projects_router
from app.api.routes.share import router as share_router
+1 -1
View File
@@ -5,7 +5,7 @@
from __future__ import annotations
from typing import List, Literal, Optional
from typing import List, Literal
from app.services.ai_service import TITLE_STYLES, generate_smart_titles, semantic_match_assets
from fastapi import APIRouter
+2 -1
View File
@@ -30,7 +30,6 @@ from app.schemas.generation_task import (
GenerationTaskResponse,
ListGenerationTasksResponse,
)
from app.services.smart_asset_selector import SmartAssetSelector
from fastapi import APIRouter, Depends, HTTPException
from packages.application import (
@@ -61,6 +60,7 @@ def _to_generation_task_response(task) -> GenerationTaskResponse:
batch_id=getattr(task, "batch_id", ""),
video_title=getattr(task, "video_title", ""),
resolution=getattr(task, "resolution", ""),
bgm_config=getattr(task, "bgm_config", {}) or {},
logs=getattr(task, "logs", "[]"),
status=task.status,
progress=task.progress,
@@ -289,6 +289,7 @@ def create_generation_task(
batch_id=batch_id,
video_title=request.video_title,
resolution=request.resolution,
bgm_config=request.bgm_config,
auto_retry_enabled=request.auto_retry_enabled,
auto_retry_max=request.auto_retry_max,
)
+3 -3
View File
@@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timezone
import psycopg2
import redis
@@ -13,7 +13,7 @@ router = APIRouter(tags=["Health"])
async def health_check():
return {
"status": "healthy",
"timestamp": datetime.utcnow().isoformat(),
"timestamp": datetime.now(timezone.utc).isoformat(),
"version": settings.APP_VERSION,
}
@@ -33,7 +33,7 @@ async def startup_check():
all_ready = all(check["status"] == "healthy" for check in checks.values())
response = {
"status": "started" if all_ready else "starting",
"timestamp": datetime.utcnow().isoformat(),
"timestamp": datetime.now(timezone.utc).isoformat(),
"checks": checks,
}
if not all_ready:
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
"""模板编辑器 API 路由包.
将原来 2560 行的 templates_editor.py 巨无霸拆分为 12 个模块:
- schemas.py: 所有 Pydantic model
- dependencies.py: 依赖注入
- _utils.py: 工具函数
- _fallback.py: 自动兜底逻辑
- draft.py: 草稿管理(详情/更新/发布/版本/回滚)
- clips.py: 片段管理(CRUD/分割/合并/重排/批量删除/从素材创建)
- adjustments.py: 片段调整(速度/音量/裁剪/批量调速)
- bgm.py: BGM 管理
- effects.py: 转场 + 滤镜
- export.py: 导出配置
- cover.py: 封面管理 + AI 生成封面
- subtitles.py: 字幕管理
- ai_features.py: AI 推荐
- generation.py: 生成(触发/进度/记录)
- timeline.py: 时间线
挂载路径: /api/v1/templates/{template_id}/editor/
"""
from __future__ import annotations
# 向后兼容:测试和其他模块可能直接从 templates_editor 导入这些符号
from app.auth import get_current_user # noqa: F401
from app.dependencies import get_db_session # noqa: F401
from fastapi import APIRouter
from .adjustments import router as adjustments_router
from .ai_features import router as ai_features_router
from .bgm import router as bgm_router
from .clips import router as clips_router
from .cover import router as cover_router
from .dependencies import get_draft_plan_id, get_editor_services # noqa: F401
from .draft import router as draft_router
from .effects import router as effects_router
from .export import router as export_router
from .generation import router as generation_router
from .subtitles import router as subtitles_router
from .timeline import router as timeline_router
# 主 router,所有子路由都合并到这里
router = APIRouter(tags=["Template Editor"])
# 合并所有子模块的路由(不用 include_router 是因为子路由有空路径 "")
_sub_routers = [
draft_router,
clips_router,
adjustments_router,
bgm_router,
effects_router,
export_router,
cover_router,
subtitles_router,
ai_features_router,
generation_router,
timeline_router,
]
for sub in _sub_routers:
for route in sub.routes:
router.routes.append(route)
__all__ = ["router"]
+163
View File
@@ -0,0 +1,163 @@
"""模板编辑器自动兜底逻辑.
generate_editor_draft 触发生成前的自动修复流程:
1. draft → editing 状态迁移
2. 无片段时从模板复制片段配置
3. 为无素材片段分配指定素材
4. 项目有素材库时自动选素材
"""
from __future__ import annotations
import logging
import random
from typing import Any
from app.services.edit_plan_service import EditPlanService
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.template_clip_config_repository import (
SQLAlchemyTemplateClipConfigRepository,
)
from packages.adapters.sqlalchemy_impl.template_repository import (
SQLAlchemyTemplateRepository,
)
from packages.domain.edit_plan import EditPlanStatus
logger = logging.getLogger(__name__)
def _auto_fallback_draft_to_editing(
svc: EditPlanService, plan_id: str, plan_check
) -> None:
"""自动兜底 1: draft → editing"""
if plan_check.status == EditPlanStatus.DRAFT:
logger.info("模板编辑器自动兜底: plan=%s draft→editing", plan_id)
svc.transition_status(plan_id, EditPlanStatus.EDITING)
def _auto_fallback_copy_template_clips(
svc: EditPlanService, plan_id: str, plan_check, db: Session
) -> None:
"""自动兜底 2: 无片段 + 有 template_id → 从模板复制片段配置"""
existing_clips = svc.count_clips(plan_id)
if existing_clips == 0 and plan_check.template_id:
logger.info(
"模板编辑器自动兜底: plan=%s 无片段,从模板 %s 复制片段配置",
plan_id,
plan_check.template_id,
)
clip_config_repo = SQLAlchemyTemplateClipConfigRepository(db)
configs = clip_config_repo.list_by_template(plan_check.template_id)
if configs:
for cfg in configs:
svc.create_clip(
plan_id=plan_id,
clip_type=cfg.clip_type.value
if hasattr(cfg.clip_type, "value")
else cfg.clip_type,
order=cfg.order,
template_clip_config_id=cfg.id,
duration=cfg.default_duration,
transition_effect=cfg.transition_effect.value
if hasattr(cfg.transition_effect, "value")
else cfg.transition_effect,
)
logger.info(
"模板编辑器自动兜底: plan=%s 从 template_clip_configs 复制了 %d 个片段",
plan_id,
len(configs),
)
else:
tpl_repo = SQLAlchemyTemplateRepository(db)
segments = tpl_repo.list_segments(plan_check.template_id)
for seg in segments:
avg_duration = (seg.duration_min + seg.duration_max) / 2
svc.create_clip(
plan_id=plan_id,
clip_type="main",
order=seg.segment_order,
duration=avg_duration,
config={
"material_type": seg.material_type or "",
"template_segment_id": seg.id,
},
)
logger.info(
"模板编辑器自动兜底: plan=%s 从旧模板 segments 复制了 %d 个片段",
plan_id,
len(segments),
)
def _auto_fallback_assign_assets(
svc: EditPlanService, plan_id: str, plan_check
) -> list:
"""自动兜底 3: 为没有素材的片段分配素材。返回剩余无素材片段列表。"""
all_clips = svc.list_clips(plan_id)
clips_without_asset = [c for c in all_clips if not c.asset_id]
config_asset_ids = (plan_check.config or {}).get("asset_ids", [])
if clips_without_asset and config_asset_ids:
logger.info(
"模板编辑器自动兜底3: plan=%s 为 %d 个无素材片段分配 %d 个指定素材",
plan_id,
len(clips_without_asset),
len(config_asset_ids),
)
for i, clip in enumerate(clips_without_asset):
asset_idx = i % len(config_asset_ids)
svc.assign_asset(clip.id, config_asset_ids[asset_idx])
logger.info("模板编辑器自动兜底3: plan=%s 素材分配完成", plan_id)
clips_without_asset = []
return clips_without_asset
def _auto_fallback_auto_material_mode(
svc: EditPlanService,
plan_id: str,
plan_check,
clips_without_asset: list,
asset_library_repo: Any,
asset_repo: Any,
) -> None:
"""自动兜底 4: 项目有视频素材库时自动选素材"""
if not clips_without_asset:
return
if not plan_check.project_id:
return
logger.info(
"模板编辑器自动兜底4: plan=%s 自动选素材分配给 %d 个无素材片段",
plan_id,
len(clips_without_asset),
)
libs = asset_library_repo.find_by_project(plan_check.project_id)
video_lib = None
for lib in libs:
lib_kind = lib.kind.value if hasattr(lib.kind, "value") else lib.kind
if lib_kind == "video":
video_lib = lib
break
if video_lib:
assets = asset_repo.find_by_library(video_lib.id)
ready_videos = [
a
for a in assets
if (a.status.value if hasattr(a.status, "value") else a.status) == "ready"
and a.mime_type
and a.mime_type.startswith("video")
]
if ready_videos:
random.shuffle(ready_videos)
for i, clip in enumerate(clips_without_asset):
asset = ready_videos[i % len(ready_videos)]
svc.assign_asset(clip.id, asset.id)
logger.info(
"模板编辑器自动兜底4: plan=%s 从素材库 %s 分配了 %d 个素材",
plan_id,
video_lib.name,
len(ready_videos),
)
+109
View File
@@ -0,0 +1,109 @@
"""模板编辑器内部工具函数.
纯函数,不依赖请求上下文。
"""
from __future__ import annotations
from typing import Any
from .schemas import ClipAdjustResponse
# 时间线场景颜色映射
_CLIP_TYPE_COLORS = {
"intro": "#6366f1",
"title": "#6366f1",
"product": "#818cf8",
"showcase": "#10b981",
"scene": "#10b981",
"subtitle": "#f59e0b",
"text": "#f59e0b",
"cta": "#ef4444",
"outro": "#ef4444",
"voiceover": "#8b5cf6",
"transition": "#64748b",
}
_DEFAULT_COLOR = "#6366f1"
def _format_time(seconds: float) -> str:
"""秒数格式化为 m:ss"""
m = int(seconds) // 60
s = int(seconds) % 60
return f"{m}:{s:02d}"
def _clip_type_to_scene_label(clip_type: str, text_content: str) -> str:
"""片段类型转时间线场景标签"""
type_labels = {
"intro": "开场",
"title": "标题",
"product": "产品展示",
"showcase": "场景展示",
"scene": "场景",
"subtitle": "字幕",
"text": "文字",
"cta": "结尾 CTA",
"outro": "结尾",
"voiceover": "配音",
"transition": "转场",
}
label = type_labels.get(clip_type, clip_type or "片段")
if text_content:
short = text_content[:20].strip()
if short:
return f"{label} - {short}"
return label
# ── 片段调整相关工具 ────────────────────────────────────────────────────────
def _get_clip_config(clip) -> dict:
"""安全获取 clip.config"""
config = getattr(clip, "config", {}) or {}
if not isinstance(config, dict):
config = {}
return config
def _get_adjust_volume(clip) -> float:
"""获取片段音量"""
config = _get_clip_config(clip)
return float(config.get("volume", 1.0))
def _get_adjust_trim(clip) -> tuple[float, float]:
"""获取片段裁剪起止"""
config = _get_clip_config(clip)
trim_start = float(config.get("trim_start", 0.0))
trim_end = float(config.get("trim_end", 0.0))
return trim_start, trim_end
def _build_adjust_response(clip) -> ClipAdjustResponse:
"""构造片段调整响应"""
trim_start, trim_end = _get_adjust_trim(clip)
return ClipAdjustResponse(
clip_id=clip.id,
speed=clip.playback_speed,
volume=_get_adjust_volume(clip),
trim_start=trim_start,
trim_end=trim_end,
duration=clip.duration,
)
def _validate_trim(trim_start: float, trim_end: float, total_duration: float) -> None:
"""校验裁剪时长合法性"""
if trim_start + trim_end >= total_duration:
raise ValueError(
f"裁剪总时长({trim_start + trim_end:.2f}s)不能大于等于片段总时长({total_duration:.2f}s)"
)
def _clip_value(value: Any) -> str:
"""获取枚举/字符串值的统一方法"""
if hasattr(value, "value"):
return value.value
return str(value)
+167
View File
@@ -0,0 +1,167 @@
"""片段调整路由.
端点:
- PUT /clips/{clip_id}/speed 调速
- PUT /clips/{clip_id}/volume 调音量
- PUT /clips/{clip_id}/trim 裁剪
- PUT /clips/{clip_id}/adjustments 统一调整
- POST /clips/batch-speed 批量调速
"""
from __future__ import annotations
from typing import Any
from app.auth import AuthenticatedUser, get_current_user
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends, HTTPException
from ._utils import _build_adjust_response, _get_adjust_trim, _get_clip_config, _validate_trim
from .dependencies import get_draft_plan_id, get_editor_services
from .schemas import (
BatchSpeedRequest,
BatchSpeedResponse,
ClipAdjustmentsRequest,
ClipAdjustResponse,
SpeedAdjustRequest,
TrimAdjustRequest,
VolumeAdjustRequest,
)
router = APIRouter(tags=["Template Editor"])
@router.put("/clips/{clip_id}/speed", response_model=ClipAdjustResponse)
def adjust_editor_clip_speed(
template_id: str,
clip_id: str,
body: SpeedAdjustRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> ClipAdjustResponse:
"""调整片段播放速度"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if not clip:
raise HTTPException(status_code=404, detail="片段不存在")
updated = plan_svc.update_clip(clip_id, playback_speed=body.speed)
return _build_adjust_response(updated)
@router.put("/clips/{clip_id}/volume", response_model=ClipAdjustResponse)
def adjust_editor_clip_volume(
template_id: str,
clip_id: str,
body: VolumeAdjustRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> ClipAdjustResponse:
"""调整片段音量"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if not clip:
raise HTTPException(status_code=404, detail="片段不存在")
config = dict(_get_clip_config(clip))
config["volume"] = body.volume
updated = plan_svc.update_clip(clip_id, config=config)
return _build_adjust_response(updated)
@router.put("/clips/{clip_id}/trim", response_model=ClipAdjustResponse)
def adjust_editor_clip_trim(
template_id: str,
clip_id: str,
body: TrimAdjustRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> ClipAdjustResponse:
"""裁剪片段(trim in/out)"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if not clip:
raise HTTPException(status_code=404, detail="片段不存在")
try:
_validate_trim(body.trim_start, body.trim_end, clip.duration)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
config = dict(_get_clip_config(clip))
config["trim_start"] = body.trim_start
config["trim_end"] = body.trim_end
updated = plan_svc.update_clip(clip_id, config=config)
return _build_adjust_response(updated)
@router.put("/clips/{clip_id}/adjustments", response_model=ClipAdjustResponse)
def adjust_editor_clip_all(
template_id: str,
clip_id: str,
body: ClipAdjustmentsRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> ClipAdjustResponse:
"""统一调整片段的 speed / volume / trim"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if not clip:
raise HTTPException(status_code=404, detail="片段不存在")
update_kwargs: dict[str, Any] = {}
config_updates: dict[str, Any] = {}
if body.speed is not None:
update_kwargs["playback_speed"] = body.speed
if body.volume is not None:
config_updates["volume"] = body.volume
if body.trim_start is not None:
config_updates["trim_start"] = body.trim_start
if body.trim_end is not None:
config_updates["trim_end"] = body.trim_end
current_trim_start, current_trim_end = _get_adjust_trim(clip)
new_trim_start = body.trim_start if body.trim_start is not None else current_trim_start
new_trim_end = body.trim_end if body.trim_end is not None else current_trim_end
if body.trim_start is not None or body.trim_end is not None:
try:
_validate_trim(new_trim_start, new_trim_end, clip.duration)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
if config_updates:
config = dict(_get_clip_config(clip))
config.update(config_updates)
update_kwargs["config"] = config
if not update_kwargs:
return _build_adjust_response(clip)
updated = plan_svc.update_clip(clip_id, **update_kwargs)
return _build_adjust_response(updated)
@router.post("/clips/batch-speed", response_model=BatchSpeedResponse)
def batch_adjust_editor_speed(
template_id: str,
body: BatchSpeedRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> BatchSpeedResponse:
"""批量调整草稿内所有片段的播放速度"""
_, plan_svc = services
clips = plan_svc.list_clips(plan_id, limit=500, skip=0)
count = 0
for clip in clips:
plan_svc.update_clip(clip.id, playback_speed=body.speed)
count += 1
return BatchSpeedResponse(updated_count=count, plan_id=plan_id)
+121
View File
@@ -0,0 +1,121 @@
"""AI 功能路由.
端点:
- POST /ai-recommend AI 推荐片段方案
"""
from __future__ import annotations
import logging
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_db_session
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from packages.domain.config_schemas import normalize_plan_config
from .dependencies import get_draft_plan_id, get_editor_services
from .schemas import AIRecommendRequest, AIRecommendResponse
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Template Editor"])
@router.post("/ai-recommend", response_model=AIRecommendResponse)
def editor_ai_recommend(
template_id: str,
body: AIRecommendRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
db: Session = Depends(get_db_session),
current_user: AuthenticatedUser = Depends(get_current_user),
) -> AIRecommendResponse:
"""AI 推荐片段方案"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
plan_status = plan.status.value if hasattr(plan.status, "value") else plan.status
if plan_status not in ("draft", "editing"):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="当前草稿状态不支持AI推荐,请先编辑后再试",
)
from packages.shared.ai_service import run_ai_recommend
result = run_ai_recommend(
plan_id=plan_id,
template_id=plan.template_id,
asset_ids=body.asset_ids,
editing_mode=body.editing_mode,
target_duration=body.target_duration,
)
try:
plan_svc.delete_all_clips(plan_id)
for clip_data in result["clips"]:
plan_svc.create_clip(
plan_id=plan_id,
clip_type=clip_data["clip_type"],
order=clip_data["order"],
text_content=clip_data.get("text_content", ""),
duration=clip_data["duration"],
transition_effect=clip_data.get("transition_effect", "cut"),
asset_id=clip_data.get("asset_id", ""),
start_time=clip_data.get("start_time", 0.0),
config=clip_data.get("config", {}),
)
normalized_config = normalize_plan_config(result.get("config", {}))
plan_svc.update_plan(
plan_id,
config=normalized_config,
total_duration=result["total_duration"],
)
except Exception as _e:
logger.exception(
"模板编辑器AI推荐写入失败: template_id=%s plan_id=%s",
template_id,
plan_id,
)
try:
db.rollback()
except Exception:
pass
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="AI推荐结果保存失败,请稍后重试",
) from _e
logger.info(
"模板编辑器AI推荐: template_id=%s plan_id=%s clips=%d duration=%.1f by user=%s",
template_id,
plan_id,
len(result["clips"]),
result["total_duration"],
current_user.user.id,
)
return AIRecommendResponse(
plan_id=plan_id,
clips=[
{
"clip_type": c["clip_type"],
"order": c["order"],
"text_content": c.get("text_content", ""),
"duration": c["duration"],
"transition_effect": c.get("transition_effect", "cut"),
"asset_id": c.get("asset_id", ""),
"start_time": c.get("start_time", 0.0),
"config": c.get("config", {}),
}
for c in result["clips"]
],
config=normalized_config,
total_duration=result["total_duration"],
confidence=result["confidence"],
)
+133
View File
@@ -0,0 +1,133 @@
"""BGM 管理路由.
端点:
- GET /bgm 获取 BGM 配置
- PUT /bgm 更新 BGM 配置
- GET /bgm/presets 预设 BGM 列表
"""
from __future__ import annotations
import logging
from typing import Any
from app.auth import AuthenticatedUser, get_current_user
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends, HTTPException, Query, status
from .dependencies import get_draft_plan_id, get_editor_services
from .schemas import BGMConfigUpdateRequest
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Template Editor"])
@router.get("/bgm", response_model=dict[str, Any])
def get_editor_bgm(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""获取草稿的 BGM 配置"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
config = plan.config or {}
return {
"plan_id": plan.id,
"bgm": config.get("bgm", {}),
}
@router.put("/bgm", response_model=dict[str, Any])
def update_editor_bgm(
template_id: str,
body: BGMConfigUpdateRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
current_user: AuthenticatedUser = Depends(get_current_user),
):
"""更新草稿的 BGM 配置"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
config = dict(plan.config) if plan.config else {}
current_bgm = dict(config.get("bgm", {}))
update_data = body.model_dump(exclude_none=True)
current_bgm.update(update_data)
if current_bgm.get("enabled"):
has_source = any(
current_bgm.get(key)
for key in ("asset_id", "preset_id", "audio_url")
if current_bgm.get(key)
)
if not has_source:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="启用 BGM 时需要指定素材来源(asset_id / preset_id / audio_url)",
)
config["bgm"] = current_bgm
updated_plan = plan_svc.update_plan_config(plan_id, config)
logger.info(
"模板编辑器更新BGM: template_id=%s plan_id=%s enabled=%s by user=%s",
template_id,
plan_id,
current_bgm.get("enabled", False),
current_user.user.id,
)
return {
"plan_id": updated_plan.id,
"bgm": current_bgm,
}
@router.get("/bgm/presets", response_model=dict[str, Any])
def list_editor_bgm_presets(
style: str | None = Query(default=None, description="按风格筛选"),
keyword: str | None = Query(default=None, description="关键词搜索"),
skip: int = Query(default=0, ge=0, description="分页偏移"),
limit: int = Query(default=50, ge=1, le=200, description="每页数量"),
_: AuthenticatedUser = Depends(get_current_user),
):
"""获取预设 BGM 列表"""
from packages.domain.preset_bgm import (
BGM_STYLES,
PRESET_BGM_LIBRARY,
list_preset_bgm_by_style,
search_preset_bgm,
)
bgm_list = PRESET_BGM_LIBRARY
if keyword:
bgm_list = search_preset_bgm(keyword)
elif style:
bgm_list = list_preset_bgm_by_style(style)
total = len(bgm_list)
paged = bgm_list[skip : skip + limit]
return {
"total": total,
"skip": skip,
"limit": limit,
"styles": BGM_STYLES,
"items": [
{
"id": bgm.id,
"name": bgm.name,
"style": bgm.style,
"style_label": BGM_STYLES.get(bgm.style, bgm.style),
"duration": bgm.duration,
"artist": bgm.artist,
"description": bgm.description,
"tags": bgm.tags,
"audio_url": bgm.audio_url,
}
for bgm in paged
],
}
+318
View File
@@ -0,0 +1,318 @@
"""片段管理路由.
端点:
- GET /clips 片段列表
- POST /clips 创建片段
- GET /clips/{clip_id} 片段详情
- PUT /clips/{clip_id} 更新片段
- DELETE /clips/{clip_id} 删除片段
- POST /clips/{clip_id}/split 分割片段
- POST /clips/merge 合并片段
- POST /clips/reorder 重排片段
- POST /clips/batch-delete 批量删除
- POST /clips/from-assets 从素材创建片段
"""
from __future__ import annotations
import logging
from typing import Any
from app.auth import AuthenticatedUser, get_current_user
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends, HTTPException, Query, status
from .dependencies import get_draft_plan_id, get_editor_services
from .schemas import (
ClipBatchDeleteRequest,
ClipBatchDeleteResponse,
ClipReorderRequest,
ClipReorderResponse,
ClipsFromAssetsRequest,
ClipsFromAssetsResponse,
EditorClipCreateRequest,
EditorClipListResponse,
EditorClipResponse,
EditorClipUpdateRequest,
MergeClipsRequest,
SplitClipRequest,
)
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Template Editor"])
def _clip_to_response(clip) -> EditorClipResponse:
"""统一构造片段响应"""
return EditorClipResponse(
id=clip.id,
plan_id=clip.plan_id,
clip_type=clip.clip_type.value
if hasattr(clip.clip_type, "value")
else str(clip.clip_type),
order=clip.order,
duration=clip.duration,
text_content=clip.text_content or "",
transition_effect=clip.transition_effect.value
if hasattr(clip.transition_effect, "value")
else str(clip.transition_effect),
playback_speed=clip.playback_speed or 1.0,
config=clip.config or {},
)
@router.get("/clips", response_model=EditorClipListResponse)
def list_draft_clips(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
skip: int = Query(default=0, ge=0),
limit: int = Query(default=100, ge=1, le=500),
_: AuthenticatedUser = Depends(get_current_user),
):
"""获取草稿的片段列表"""
_, plan_svc = services
clips = plan_svc.list_clips(plan_id, skip=skip, limit=limit)
total = plan_svc.count_clips(plan_id)
return EditorClipListResponse(
items=[_clip_to_response(c) for c in clips],
total=total,
)
@router.post("/clips", response_model=EditorClipResponse, status_code=status.HTTP_201_CREATED)
def create_draft_clip(
template_id: str,
req: EditorClipCreateRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""在草稿中创建新片段"""
_, plan_svc = services
try:
clip = plan_svc.create_clip(
plan_id,
clip_type=req.clip_type,
order=req.order,
duration=req.duration,
text_content=req.text_content,
transition_effect=req.transition_effect,
config=req.config,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return _clip_to_response(clip)
@router.put("/clips/{clip_id}", response_model=EditorClipResponse)
def update_draft_clip(
template_id: str,
clip_id: str,
req: EditorClipUpdateRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""更新草稿中的片段"""
_, plan_svc = services
try:
clip = plan_svc.update_clip(
clip_id,
order=req.order,
duration=req.duration,
text_content=req.text_content,
transition_effect=req.transition_effect,
playback_speed=req.playback_speed,
config=req.config,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return _clip_to_response(clip)
@router.delete("/clips/{clip_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_draft_clip(
template_id: str,
clip_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""删除草稿中的片段"""
_, plan_svc = services
success = plan_svc.delete_clip(clip_id)
if not success:
raise HTTPException(status_code=404, detail="片段不存在")
return None
@router.get("/clips/{clip_id}", response_model=EditorClipResponse)
def get_draft_clip_detail(
template_id: str,
clip_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""获取草稿中的片段详情"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if clip is None:
raise HTTPException(status_code=404, detail="片段不存在")
if clip.plan_id != plan_id:
raise HTTPException(status_code=404, detail="片段不存在")
return _clip_to_response(clip)
@router.post("/clips/{clip_id}/split", response_model=dict[str, Any], status_code=status.HTTP_200_OK)
def split_draft_clip(
template_id: str,
clip_id: str,
body: SplitClipRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""将一个片段从指定时间点分割为两个片段"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if clip is None or clip.plan_id != plan_id:
raise HTTPException(status_code=404, detail="片段不存在")
try:
result = plan_svc.split_clip(clip_id, body.split_time)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)
) from exc
left = result["left_clip"]
right = result["right_clip"]
return {
"left_clip": {
"id": left.id,
"plan_id": left.plan_id,
"clip_type": left.clip_type,
"order": left.order,
"duration": left.duration,
"start_time": left.start_time,
},
"right_clip": {
"id": right.id,
"plan_id": right.plan_id,
"clip_type": right.clip_type,
"order": right.order,
"duration": right.duration,
"start_time": right.start_time,
},
}
@router.post("/clips/merge", response_model=dict[str, Any], status_code=status.HTTP_200_OK)
def merge_draft_clips(
template_id: str,
body: MergeClipsRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""将多个连续的同类型片段合并为一个片段"""
_, plan_svc = services
for cid in body.clip_ids:
clip = plan_svc.get_clip(cid)
if clip is None or clip.plan_id != plan_id:
raise HTTPException(status_code=404, detail=f"片段不存在: {cid}")
try:
merged = plan_svc.merge_clips(body.clip_ids)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)
) from exc
return {
"id": merged.id,
"plan_id": merged.plan_id,
"clip_type": merged.clip_type,
"order": merged.order,
"duration": merged.duration,
"text_content": merged.text_content,
}
@router.post("/clips/reorder", response_model=ClipReorderResponse)
def reorder_editor_clips(
template_id: str,
body: ClipReorderRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> ClipReorderResponse:
"""批量重排片段顺序"""
_, plan_svc = services
count = 0
for item in body.items:
try:
plan_svc.update_clip(item.clip_id, order=item.new_order)
count += 1
except ValueError:
pass
return ClipReorderResponse(updated_count=count, plan_id=plan_id)
@router.post("/clips/batch-delete", response_model=ClipBatchDeleteResponse)
def batch_delete_editor_clips(
template_id: str,
body: ClipBatchDeleteRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> ClipBatchDeleteResponse:
"""批量删除片段"""
_, plan_svc = services
deleted = 0
for clip_id in body.clip_ids:
if plan_svc.delete_clip(clip_id):
deleted += 1
return ClipBatchDeleteResponse(deleted_count=deleted, plan_id=plan_id)
@router.post("/clips/from-assets", response_model=ClipsFromAssetsResponse)
def create_clips_from_assets_editor(
template_id: str,
body: ClipsFromAssetsRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
current_user: AuthenticatedUser = Depends(get_current_user),
) -> ClipsFromAssetsResponse:
"""从素材批量创建片段"""
_, plan_svc = services
clips = []
for i, asset_id in enumerate(body.asset_ids):
try:
clip = plan_svc.create_clip(
plan_id,
clip_type="main",
order=body.start_order + i if hasattr(body, "start_order") else i,
duration=5.0,
asset_id=asset_id,
)
clips.append(clip)
except ValueError:
pass
logger.info(
"模板编辑器从素材创建片段: template_id=%s plan_id=%s count=%d by user=%s",
template_id,
plan_id,
len(clips),
current_user.user.id,
)
return ClipsFromAssetsResponse(
created_count=len(clips),
plan_id=plan_id,
clip_ids=[c.id for c in clips],
)
+209
View File
@@ -0,0 +1,209 @@
"""封面管理路由.
端点:
- GET /cover 封面配置
- PUT /cover 更新封面
- POST /cover/extract 抽帧生成封面
- POST /cover/smart 智能选帧
- POST /generate-cover AI 生成封面
"""
from __future__ import annotations
import logging
from app.auth import AuthenticatedUser, get_current_user
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends, HTTPException
from packages.domain.config_schemas import normalize_plan_config
from .dependencies import get_draft_plan_id, get_editor_services
from .schemas import (
CoverConfigResponse,
CoverExtractRequest,
CoverGenerateResponse,
CoverSmartRequest,
CoverUpdateRequest,
GenerateCoverRequest,
GenerateCoverResponse,
)
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Template Editor"])
@router.get("/cover", response_model=CoverConfigResponse)
def get_editor_cover(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> CoverConfigResponse:
"""获取草稿封面配置"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
config = plan.config or {}
cover_config = config.get("cover", {})
return CoverConfigResponse(
type=cover_config.get("cover_type", "auto"),
image_url=cover_config.get("cover_image_url", ""),
frame_time=cover_config.get("frame_time", 0.0),
)
@router.put("/cover", response_model=CoverConfigResponse)
def update_editor_cover(
template_id: str,
body: CoverUpdateRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> CoverConfigResponse:
"""更新草稿封面配置"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
config = dict(plan.config) if plan.config else {}
current_cover = dict(config.get("cover", {}))
update_data = body.model_dump(exclude_none=True)
current_cover.update(update_data)
config["cover"] = current_cover
normalized = normalize_plan_config(config)
plan_svc.update_plan_config(plan_id, {"cover": normalized["cover"]})
return CoverConfigResponse(
type=current_cover.get("cover_type", "auto"),
image_url=current_cover.get("cover_image_url", ""),
frame_time=current_cover.get("frame_time", 0.0),
)
@router.post("/cover/extract", response_model=CoverGenerateResponse)
def extract_editor_cover(
template_id: str,
body: CoverExtractRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
current_user: AuthenticatedUser = Depends(get_current_user),
) -> CoverGenerateResponse:
"""从指定片段抽帧生成封面"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
clip = plan_svc.get_clip(body.clip_id)
if not clip or clip.plan_id != plan_id:
raise HTTPException(status_code=400, detail="片段不存在或不属于当前草稿")
cover_url = f"cover/extract/{plan_id}_{body.clip_id}_{body.frame_time}.jpg"
config = dict(plan.config) if plan.config else {}
cover_config = dict(config.get("cover", {}))
cover_config.update(
{
"cover_type": "extract",
"cover_image_url": cover_url,
"clip_id": body.clip_id,
"frame_time": body.frame_time,
}
)
config["cover"] = cover_config
normalized = normalize_plan_config(config)
plan_svc.update_plan_config(plan_id, {"cover": normalized["cover"]})
logger.info(
"模板编辑器封面抽帧: template_id=%s plan_id=%s clip_id=%s by user=%s",
template_id,
plan_id,
body.clip_id,
current_user.user.id,
)
return CoverGenerateResponse(
type="extract",
image_url=cover_url,
frame_time=body.frame_time,
)
@router.post("/cover/smart", response_model=CoverGenerateResponse)
def smart_editor_cover(
template_id: str,
body: CoverSmartRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
current_user: AuthenticatedUser = Depends(get_current_user),
) -> CoverGenerateResponse:
"""智能选帧生成封面"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
cover_url = f"cover/smart/{plan_id}_smart.jpg"
strategy = getattr(body, "strategy", "auto")
config = dict(plan.config) if plan.config else {}
cover_config = dict(config.get("cover", {}))
cover_config.update(
{
"cover_type": "smart",
"cover_image_url": cover_url,
"strategy": strategy,
}
)
config["cover"] = cover_config
normalized = normalize_plan_config(config)
plan_svc.update_plan_config(plan_id, {"cover": normalized["cover"]})
logger.info(
"模板编辑器智能封面: template_id=%s plan_id=%s strategy=%s by user=%s",
template_id,
plan_id,
strategy,
current_user.user.id,
)
return CoverGenerateResponse(
type="smart",
image_url=cover_url,
frame_time=None,
)
@router.post("/generate-cover", response_model=GenerateCoverResponse)
def editor_generate_cover(
template_id: str,
body: GenerateCoverRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
current_user: AuthenticatedUser = Depends(get_current_user),
) -> GenerateCoverResponse:
"""AI 生成封面"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
from packages.shared.ai_service import run_generate_cover
cover_data = run_generate_cover(
plan_id=plan_id,
asset_ids=body.asset_ids,
cover_type=body.cover_type,
frame_time=body.frame_time,
)
current_config = dict(plan.config) if plan.config else {}
current_config["cover"] = cover_data
normalized = normalize_plan_config(current_config)
plan_svc.update_plan_config(plan_id, {"cover": normalized["cover"]})
logger.info(
"模板编辑器封面生成: template_id=%s plan_id=%s type=%s by user=%s",
template_id,
plan_id,
body.cover_type,
current_user.user.id,
)
return GenerateCoverResponse(plan_id=plan_id, cover=cover_data)
+141
View File
@@ -0,0 +1,141 @@
"""模板编辑器依赖注入.
核心依赖:
- get_editor_services: 获取模板+计划服务
- get_draft_plan_id: 根据 template_id 获取或创建草稿,返回 plan_id
- _check_queue_limits: 生成队列限流检查
"""
from __future__ import annotations
import logging
from app.auth import AuthenticatedUser, get_current_user
from app.core.task_enqueue import GLOBAL_PENDING_LIMIT, USER_PENDING_LIMIT
from app.dependencies import get_db_session
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.template_repository import (
SQLAlchemyTemplateRepository,
)
logger = logging.getLogger(__name__)
def get_editor_services(
db: Session = Depends(get_db_session),
) -> tuple[EditTemplateService, EditPlanService]:
"""获取模板编辑器所需的两个服务"""
return EditTemplateService(db), EditPlanService(db)
def get_draft_plan_id(
template_id: str,
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
) -> str:
"""路径依赖:根据 template_id 获取或创建草稿,返回 plan_id.
这是模板编辑器路由的核心依赖——所有编辑器端点都先经过这里,
确保 template_id → plan_id 的映射始终存在。
兼容策略:优先从新模板系统(edit_templates 表)查找,
若不存在则回退到旧模板系统(templates 表),确保用户自建模板可用。
"""
tpl_svc, plan_svc = services
user_id = str(current_user.user.id)
# 1. 草稿已存在 → 直接返回
draft = tpl_svc.get_template_draft(template_id)
if draft is not None:
return draft.id
# 2. 新系统有模板 → 用新服务创建草稿
if tpl_svc.get_template(template_id) is not None:
draft = tpl_svc.create_template_draft(template_id, user_id=user_id)
return draft.id
# 3. 回退到旧模板系统(templates 表)
old_repo = SQLAlchemyTemplateRepository(db)
old_template = old_repo.get(template_id, user_id=user_id)
if old_template is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="模板不存在")
# 4. 基于旧模板创建草稿计划
from app.services.plan_generator_service import PlanGeneratorService
from packages.domain.edit_template import EditTemplate, EditTemplateStatus
from packages.domain.template_clip_config import ClipType, TemplateClipConfig
# 构造伪 EditTemplate 对象(只填 generate_from_template 需要的字段)
pseudo_template = EditTemplate(
id=old_template.id,
name=old_template.name,
editing_mode=old_template.mode,
status=EditTemplateStatus.ACTIVE,
)
# 将旧模板 segments 转换为 clip_configs
clip_configs: list[TemplateClipConfig] = []
for seg in old_template.segments or []:
clip_configs.append(
TemplateClipConfig(
id=f"seg_{seg.id}",
template_id=old_template.id,
clip_type=ClipType.MAIN,
order=seg.segment_order,
min_duration=seg.duration_min,
max_duration=seg.duration_max,
)
)
generator = PlanGeneratorService(db)
result = generator.generate_from_template(
template=pseudo_template,
clip_configs=clip_configs,
asset_ids=[],
created_by_user_id=user_id,
name=f"{old_template.name} - 草稿",
)
plan = result["plan"]
# 标记为模板草稿(后续可复用 tpl_svc.get_template_draft 的查找逻辑)
plan_svc.update_plan_config(plan.id, {"is_template_draft": True})
logger.info(
"旧模板自动创建草稿: template_id=%s draft_plan_id=%s user_id=%s",
template_id,
plan.id,
user_id,
)
return plan.id
def _check_queue_limits(gen_task_repo, user_id: str) -> None:
"""队列限流预检查"""
try:
has_count = (
hasattr(gen_task_repo, "count_pending_by_user")
and hasattr(gen_task_repo, "count_pending_total")
)
if has_count:
user_pending = gen_task_repo.count_pending_by_user(user_id)
global_pending = gen_task_repo.count_pending_total()
if user_pending >= USER_PENDING_LIMIT:
raise HTTPException(
status_code=429,
detail=f"您的待处理任务过多(当前 {user_pending}/{USER_PENDING_LIMIT}),请等待完成后再提交",
)
if global_pending >= GLOBAL_PENDING_LIMIT:
raise HTTPException(
status_code=503,
detail="系统繁忙,请稍后再试",
)
except HTTPException:
raise
except Exception as e:
logger.warning("[模板编辑器队列限流] 检查失败,跳过: %s", e)
+164
View File
@@ -0,0 +1,164 @@
"""草稿管理路由.
端点:
- GET / 获取草稿详情
- PUT / 更新草稿
- POST /publish 发布草稿到模板
- GET /versions 模板版本历史
- POST /rollback 回滚到指定版本
"""
from __future__ import annotations
from app.auth import AuthenticatedUser, get_current_user
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends, HTTPException, Query, status
from .dependencies import get_draft_plan_id, get_editor_services
from .schemas import (
EditorDraftResponse,
EditorPublishResponse,
EditorRollbackRequest,
EditorRollbackResponse,
EditorTemplateVersionItem,
EditorUpdateRequest,
EditorVersionListResponse,
)
router = APIRouter(tags=["Template Editor"])
@router.get("", response_model=EditorDraftResponse)
def get_editor_draft(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""获取模板编辑器草稿详情
首次访问时自动创建草稿。
"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
clips = plan_svc.list_clips(plan_id)
return EditorDraftResponse(
plan_id=plan.id,
template_id=plan.template_id,
name=plan.name,
status=plan.status.value if hasattr(plan.status, "value") else str(plan.status),
config=plan.config or {},
total_duration=plan.total_duration,
clip_count=len(clips),
)
@router.put("", response_model=EditorDraftResponse)
def update_editor_draft(
template_id: str,
req: EditorUpdateRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""更新模板编辑器草稿"""
_, plan_svc = services
plan = plan_svc.update_plan(
plan_id,
name=req.name,
config=req.config,
total_duration=req.total_duration,
)
clips = plan_svc.list_clips(plan_id)
return EditorDraftResponse(
plan_id=plan.id,
template_id=plan.template_id,
name=plan.name,
status=plan.status.value if hasattr(plan.status, "value") else str(plan.status),
config=plan.config or {},
total_duration=plan.total_duration,
clip_count=len(clips),
)
@router.post("/publish", response_model=EditorPublishResponse, status_code=status.HTTP_200_OK)
def publish_draft_to_template(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""将草稿发布(同步)到正式模板
草稿的 config 和 clips 会同步覆盖到模板,事务保证一致性。
"""
tpl_svc, plan_svc = services
try:
tpl = tpl_svc.publish_template_from_draft(template_id, plan_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
clips = plan_svc.list_clips(plan_id)
return EditorPublishResponse(
template_id=tpl.id,
status="published",
clip_count=len(clips),
version=tpl.version,
)
@router.get("/versions", response_model=EditorVersionListResponse)
def list_template_versions(
template_id: str,
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
limit: int = Query(default=50, ge=1, le=200),
):
"""查询模板发布版本历史"""
tpl_svc, _ = services
versions = tpl_svc.list_template_versions(template_id, limit=limit)
items = [
EditorTemplateVersionItem(
version=v.version,
name=v.name,
editing_mode=v.editing_mode,
clip_count=len(v.clip_configs),
change_note=v.change_note,
published_by=v.published_by,
created_at=(
v.created_at.isoformat()
if hasattr(v.created_at, "isoformat")
else str(v.created_at)
),
)
for v in versions
]
return EditorVersionListResponse(versions=items, total=len(items))
@router.post("/rollback", response_model=EditorRollbackResponse, status_code=status.HTTP_200_OK)
def rollback_template(
template_id: str,
request: EditorRollbackRequest,
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""回滚模板到指定历史版本
回滚本身也是一次发布,版本号会 +1,可以再次回滚。
"""
tpl_svc, _ = services
try:
tpl = tpl_svc.rollback_to_version(template_id, request.version)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
clip_configs = tpl_svc.list_clip_configs(template_id)
return EditorRollbackResponse(
template_id=tpl.id,
status="rolled_back",
rollback_to_version=request.version,
new_version=tpl.version,
clip_count=len(clip_configs),
)
+195
View File
@@ -0,0 +1,195 @@
"""转场 & 滤镜路由.
端点:
- GET /transition-presets 转场预设列表
- PUT /clips/{clip_id}/transition 单片段转场
- POST /transitions/batch 批量转场
- GET /filter-presets 滤镜预设列表
- GET /filter 滤镜配置
- PUT /filter 更新滤镜
"""
from __future__ import annotations
from app.auth import AuthenticatedUser, get_current_user
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends, HTTPException
from packages.domain.config_schemas import normalize_plan_config
from .dependencies import get_draft_plan_id, get_editor_services
from .schemas import (
BatchTransitionRequest,
BatchTransitionResponse,
ClipTransitionResponse,
FilterConfigResponse,
FilterPresetListResponse,
FilterUpdateRequest,
TransitionPresetListResponse,
TransitionUpdateRequest,
)
router = APIRouter(tags=["Template Editor"])
# ── 转场 ────────────────────────────────────────────────────────────────────
@router.get("/transition-presets", response_model=TransitionPresetListResponse)
def list_editor_transition_presets(
_: AuthenticatedUser = Depends(get_current_user),
) -> TransitionPresetListResponse:
"""获取转场预设列表"""
from packages.domain.transition_presets import TRANSITION_PRESETS
items = [
{
"id": p["id"],
"name": p["name"],
"category": p.get("category", "通用"),
"duration": p.get("default_duration", 0.5),
"description": p.get("description", ""),
}
for p in TRANSITION_PRESETS
]
return TransitionPresetListResponse(items=items, total=len(items))
@router.put("/clips/{clip_id}/transition", response_model=ClipTransitionResponse)
def update_editor_clip_transition(
template_id: str,
clip_id: str,
body: TransitionUpdateRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> ClipTransitionResponse:
"""设置单个片段的转场效果"""
_, plan_svc = services
try:
clip = plan_svc.update_clip(
clip_id,
transition_effect=body.effect,
transition_duration=body.duration,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return ClipTransitionResponse(
clip_id=clip.id,
effect=clip.transition_effect.value
if hasattr(clip.transition_effect, "value")
else clip.transition_effect,
duration=clip.transition_duration or 0.5,
)
@router.post("/transitions/batch", response_model=BatchTransitionResponse)
def batch_update_editor_transitions(
template_id: str,
body: BatchTransitionRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> BatchTransitionResponse:
"""批量设置所有片段的转场效果"""
_, plan_svc = services
clips = plan_svc.list_clips(plan_id, limit=500)
updated = 0
for clip in clips:
if clip.order > 0: # 第一个片段不加转场
try:
plan_svc.update_clip(
clip.id,
transition_effect=body.effect,
transition_duration=body.duration,
)
updated += 1
except ValueError:
pass
return BatchTransitionResponse(
updated_count=updated,
plan_id=plan_id,
)
# ── 滤镜 ────────────────────────────────────────────────────────────────────
@router.get("/filter-presets", response_model=FilterPresetListResponse)
def list_editor_filter_presets(
_: AuthenticatedUser = Depends(get_current_user),
) -> FilterPresetListResponse:
"""获取滤镜预设列表"""
from packages.domain.filter_presets import FILTER_PRESETS
items = [
{
"id": p["id"],
"name": p["name"],
"category": p.get("category", "通用"),
"thumbnail": p.get("thumbnail", ""),
"description": p.get("description", ""),
}
for p in FILTER_PRESETS
]
return FilterPresetListResponse(items=items, total=len(items))
@router.get("/filter", response_model=FilterConfigResponse)
def get_editor_filter(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> FilterConfigResponse:
"""获取草稿的全局滤镜配置"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
config = plan.config or {}
filter_config = config.get("filter", {})
return FilterConfigResponse(
plan_id=plan.id,
enabled=filter_config.get("enabled", False),
preset_id=filter_config.get("preset_id", ""),
intensity=filter_config.get("intensity", 1.0),
brightness=filter_config.get("brightness", 0.0),
contrast=filter_config.get("contrast", 1.0),
saturation=filter_config.get("saturation", 1.0),
warmth=filter_config.get("warmth", 0.0),
)
@router.put("/filter", response_model=FilterConfigResponse)
def update_editor_filter(
template_id: str,
body: FilterUpdateRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> FilterConfigResponse:
"""更新草稿的全局滤镜配置"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
config = dict(plan.config) if plan.config else {}
current_filter = dict(config.get("filter", {}))
update_data = body.model_dump(exclude_none=True)
current_filter.update(update_data)
config["filter"] = current_filter
updated_plan = plan_svc.update_plan_config(plan_id, normalize_plan_config(config))
return FilterConfigResponse(
plan_id=updated_plan.id,
enabled=current_filter.get("enabled", False),
preset_id=current_filter.get("preset_id", ""),
intensity=current_filter.get("intensity", 1.0),
brightness=current_filter.get("brightness", 0.0),
contrast=current_filter.get("contrast", 1.0),
saturation=current_filter.get("saturation", 1.0),
warmth=current_filter.get("warmth", 0.0),
)
+106
View File
@@ -0,0 +1,106 @@
"""导出配置路由.
端点:
- GET /export-presets 导出预设列表
- GET /export 导出配置
- PUT /export 更新导出配置
"""
from __future__ import annotations
from app.auth import AuthenticatedUser, get_current_user
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends
from packages.domain.config_schemas import normalize_plan_config
from .dependencies import get_draft_plan_id, get_editor_services
from .schemas import ExportConfigResponse, ExportPresetListResponse, ExportUpdateRequest
router = APIRouter(tags=["Template Editor"])
@router.get("/export-presets", response_model=ExportPresetListResponse)
def list_editor_export_presets(
_: AuthenticatedUser = Depends(get_current_user),
) -> ExportPresetListResponse:
"""获取导出预设列表"""
from packages.domain.export_presets import EXPORT_PRESETS
items = [
{
"id": p["id"],
"name": p["name"],
"resolution": p.get("resolution", "1080p"),
"fps": p.get("fps", 30),
"video_bitrate": p.get("bitrate", ""),
"audio_bitrate": p.get("audio_bitrate", 128),
"format": p.get("format", "mp4"),
"quality_preset": p.get("quality_preset", "balanced"),
"description": p.get("description", ""),
"size_hint": p.get("size_hint", ""),
}
for p in EXPORT_PRESETS
]
return ExportPresetListResponse(items=items, total=len(items))
@router.get("/export", response_model=ExportConfigResponse)
def get_editor_export(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> ExportConfigResponse:
"""获取草稿的导出配置"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
config = plan.config or {}
export_config = config.get("export", {})
return ExportConfigResponse(
plan_id=plan.id,
resolution=export_config.get("resolution", "1080p"),
fps=export_config.get("fps", 30),
video_bitrate=export_config.get("video_bitrate", 4000),
audio_bitrate=export_config.get("audio_bitrate", 128),
format=export_config.get("format", "mp4"),
quality_preset=export_config.get("quality_preset", "balanced"),
watermark_enabled=export_config.get("watermark_enabled", True),
watermark_text=export_config.get("watermark_text", ""),
)
@router.put("/export", response_model=ExportConfigResponse)
def update_editor_export(
template_id: str,
body: ExportUpdateRequest,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> ExportConfigResponse:
"""更新草稿的导出配置"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
config = dict(plan.config) if plan.config else {}
current_export = dict(config.get("export", {}))
update_data = body.model_dump(exclude_none=True)
current_export.update(update_data)
config["export"] = current_export
updated_plan = plan_svc.update_plan_config(plan_id, normalize_plan_config(config))
updated_export = (updated_plan.config or {}).get("export", {})
return ExportConfigResponse(
plan_id=updated_plan.id,
resolution=updated_export.get("resolution", "1080p"),
fps=updated_export.get("fps", 30),
video_bitrate=updated_export.get("video_bitrate", 4000),
audio_bitrate=updated_export.get("audio_bitrate", 128),
format=updated_export.get("format", "mp4"),
quality_preset=updated_export.get("quality_preset", "balanced"),
watermark_enabled=updated_export.get("watermark_enabled", True),
watermark_text=updated_export.get("watermark_text", ""),
)
+250
View File
@@ -0,0 +1,250 @@
"""草稿生成路由.
端点:
- POST /generate 触发生成
- GET /generation-status 生成进度
- GET /generations 生成记录列表
"""
from __future__ import annotations
import logging
from typing import Any
from app.auth import AuthenticatedUser, get_current_user
from app.core.celery_app import celery_app
from app.core.storage import OSSStorageService, get_storage_service
from app.dependencies import (
get_asset_library_repository,
get_asset_repository,
get_db_session,
)
from app.schemas.generation_task import GenerationTaskResponse
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.generation_task_repository import (
SQLAlchemyGenerationTaskRepository,
)
from packages.application.generation_tasks import (
CreateGenerationTaskCommand,
CreateGenerationTaskUseCase,
)
from packages.domain.edit_plan import EditPlanStatus
from ._fallback import (
_auto_fallback_assign_assets,
_auto_fallback_auto_material_mode,
_auto_fallback_copy_template_clips,
_auto_fallback_draft_to_editing,
)
from .dependencies import _check_queue_limits, get_draft_plan_id, get_editor_services
from .schemas import (
ClipStatusItem,
EditPlanGenerateResponse,
EditPlanGenerationsResponse,
EditPlanGenerationStatusResponse,
)
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Template Editor"])
@router.post("/generate", response_model=EditPlanGenerateResponse)
def generate_editor_draft(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
db: Session = Depends(get_db_session),
current_user: AuthenticatedUser = Depends(get_current_user),
asset_library_repo: Any = Depends(get_asset_library_repository),
asset_repo: Any = Depends(get_asset_repository),
) -> EditPlanGenerateResponse:
"""触发模板草稿渲染生成"""
_, plan_svc = services
plan_check = plan_svc.get_plan_or_raise(plan_id)
# 自动兜底流程
_auto_fallback_draft_to_editing(plan_svc, plan_id, plan_check)
_auto_fallback_copy_template_clips(plan_svc, plan_id, plan_check, db)
clips_without_asset = _auto_fallback_assign_assets(plan_svc, plan_id, plan_check)
_auto_fallback_auto_material_mode(
plan_svc, plan_id, plan_check, clips_without_asset, asset_library_repo, asset_repo
)
# 检查是否可生成
try:
can_gen, reason = plan_svc.can_generate(plan_id)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
) from exc
if not can_gen:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=reason
)
try:
clip_count = plan_svc.mark_clips_ready(plan_id)
gen_task_repo = SQLAlchemyGenerationTaskRepository(db)
user_id = current_user.user.id
_check_queue_limits(gen_task_repo, user_id)
gen_task_use_case = CreateGenerationTaskUseCase(gen_task_repo)
plan = plan_svc.get_plan_or_raise(plan_id)
config_asset_ids = (plan.config or {}).get("asset_ids", [])
gen_task = gen_task_use_case.execute(
CreateGenerationTaskCommand(
project_id=plan.project_id or "",
template_id=plan.template_id,
created_by_user_id=current_user.user.id,
source_edit_plan_id=plan_id,
asset_ids=list(config_asset_ids) if config_asset_ids else [],
),
)
plan_svc.update_plan_config(plan_id, {"generation_task_id": gen_task.id})
plan_svc.transition_status(plan_id, EditPlanStatus.RENDERING)
celery_app.send_task("worker.render_edit_plan", args=[plan_id])
updated_plan = plan_svc.get_plan_or_raise(plan_id)
logger.info(
"模板编辑器触发生成: template_id=%s plan_id=%s gen_task_id=%s clips=%d by user=%s",
template_id,
plan_id,
gen_task.id,
clip_count,
current_user.user.id,
)
return EditPlanGenerateResponse(
plan_id=plan_id,
plan_status=updated_plan.status.value
if hasattr(updated_plan.status, "value")
else updated_plan.status,
generation_task_id=gen_task.id,
clip_count=clip_count,
)
except HTTPException:
raise
except Exception as _e:
logger.exception(
"模板编辑器触发生成失败: template_id=%s plan_id=%s",
template_id,
plan_id,
)
try:
plan_svc.transition_status(plan_id, EditPlanStatus.FAILED)
except Exception:
pass
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="生成失败,请稍后重试",
) from _e
@router.get("/generation-status", response_model=EditPlanGenerationStatusResponse)
def get_editor_generation_status(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
storage_service: OSSStorageService = Depends(get_storage_service),
_: AuthenticatedUser = Depends(get_current_user),
) -> EditPlanGenerationStatusResponse:
"""查询草稿生成进度"""
_, plan_svc = services
try:
gen_status = plan_svc.get_generation_status(plan_id)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
) from exc
plan = gen_status["plan"]
clips = gen_status["clips"]
clip_items = [
ClipStatusItem(
clip_id=c.id,
clip_type=c.clip_type,
order=c.order,
status=c.status.value if hasattr(c.status, "value") else c.status,
asset_id=c.asset_id or "",
text_content=c.text_content or "",
duration=c.duration,
)
for c in clips
]
raw_video_url = (plan.config or {}).get("rendered_url", "")
video_url = ""
if raw_video_url:
try:
video_url = storage_service.get_download_url(
raw_video_url, expires_seconds=86400
)
except Exception as e:
logger.warning(
"生成视频签名URL失败: template_id=%s error=%s", template_id, e
)
video_url = raw_video_url
progress = gen_status.get("progress", 0.0)
error_message = gen_status.get("error_message", "")
gen_task_status = gen_status.get("generation_task_status")
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(
plan_id=plan_id,
plan_status=plan_status_val,
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,
)
@router.get("/generations", response_model=EditPlanGenerationsResponse)
def list_editor_generations(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
db: Session = Depends(get_db_session),
_: AuthenticatedUser = Depends(get_current_user),
) -> EditPlanGenerationsResponse:
"""查询草稿关联的生成记录列表"""
_, plan_svc = services
plan_svc.get_plan_or_raise(plan_id)
gen_task_repo = SQLAlchemyGenerationTaskRepository(db)
tasks = gen_task_repo.list_by_source_edit_plan(plan_id)
items = [
GenerationTaskResponse(
id=t.id,
project_id=t.project_id,
asset_library_id=t.asset_library_id,
strategy_id=t.strategy_id,
voice_library_id=t.voice_library_id,
template_id=t.template_id,
asset_ids=t.asset_ids,
title_ids=t.title_ids,
voice_ids=t.voice_ids,
source_edit_plan_id=t.source_edit_plan_id or "",
status=t.status.value if hasattr(t.status, "value") else t.status,
progress=t.progress,
result_count=t.result_count,
error_message=t.error_message,
)
for t in tasks
]
return EditPlanGenerationsResponse(items=items, total=len(items))
+622
View File
@@ -0,0 +1,622 @@
"""模板编辑器所有 Pydantic Schema 定义.
集中管理,避免在路由文件里散落 40+ 个 model。
"""
from __future__ import annotations
import re as _re
from typing import Any, List, Optional
from app.schemas.generation_task import GenerationTaskResponse
from pydantic import BaseModel, Field, validator
_EXPORT_RESOLUTION_PATTERN = _re.compile(r"^\d+x\d+$")
_EXPORT_VALID_QUALITY_PRESETS = {"ultra_fast", "fast", "balanced", "high", "best"}
_EXPORT_VALID_FORMATS = {"mp4", "mov"}
# ── 生成状态相关 ────────────────────────────────────────────────────────────
class ClipStatusItem(BaseModel):
"""片段生成状态"""
clip_id: str
clip_type: str
order: int
status: str
asset_id: str
text_content: str
duration: float
class EditPlanGenerationStatusResponse(BaseModel):
"""剪辑计划生成进度响应体"""
plan_id: str
plan_status: str
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]
class EditPlanGenerateResponse(BaseModel):
"""剪辑计划触发生成响应体"""
plan_id: str
plan_status: str
generation_task_id: str
clip_count: int
class EditPlanGenerationsResponse(BaseModel):
"""剪辑计划关联的生成记录列表响应体"""
items: List[GenerationTaskResponse]
total: int
# ── AI 推荐 ────────────────────────────────────────────────────────────────
class AIRecommendRequest(BaseModel):
"""AI 推荐片段方案请求体"""
asset_ids: List[str] = Field(default_factory=list, description="素材 ID 列表")
editing_mode: str = Field(
default="one_take", description="剪辑模式: one_take / pip / voice_over / voice_pip"
)
target_duration: float = Field(
default=30.0, ge=1.0, le=600.0, description="目标时长(秒)"
)
class AIRecommendClipItem(BaseModel):
"""AI 推荐的单个片段"""
clip_type: str = Field(..., description="片段类型: intro / showcase / title / subtitle / cta / outro")
order: int = Field(..., ge=0, description="片段顺序")
text_content: str = Field(default="", description="文字内容")
duration: float = Field(..., ge=0.0, description="片段时长(秒)")
transition_effect: str = Field(default="cut", description="转场效果")
transition_duration: float = Field(default=0.0, ge=0.0, description="转场时长(秒),0 表示使用默认值")
asset_id: str = Field(default="", description="关联素材 ID")
start_time: float = Field(default=0.0, ge=0.0, description="素材截取起始时间(秒)")
config: dict[str, Any] = Field(default_factory=dict, description="片段额外配置")
class AIRecommendResponse(BaseModel):
"""AI 推荐片段方案响应体"""
plan_id: str = Field(..., description="剪辑计划 ID")
clips: List[AIRecommendClipItem] = Field(..., description="推荐的片段列表")
config: dict[str, Any] = Field(..., description="推荐的 plan config(cover/title/subtitle/bgm)")
total_duration: float = Field(..., ge=0.0, description="推荐方案总时长(秒)")
confidence: float = Field(..., ge=0.0, le=1.0, description="AI 推荐置信度 (0~1)")
# ── 封面生成 ────────────────────────────────────────────────────────────────
class GenerateCoverRequest(BaseModel):
"""AI 封面生成请求体"""
asset_ids: List[str] = Field(default_factory=list, description="素材 ID 列表(确定视频来源)")
cover_type: str = Field(
default="ai_frame",
description="封面类型: ai_frame / manual / upload / ai_regenerate",
)
frame_time: Optional[float] = Field(
default=None,
ge=0.0,
description="手动选帧时间点(秒),仅 cover_type=manual 时有效",
)
class GenerateCoverResponse(BaseModel):
"""AI 封面生成响应体"""
plan_id: str = Field(..., description="剪辑计划 ID")
cover: dict[str, Any] = Field(..., description="封面数据(type / image_url / frame_time 等)")
# ── BGM ────────────────────────────────────────────────────────────────────
class BGMConfigUpdateRequest(BaseModel):
"""更新BGM配置请求体"""
enabled: Optional[bool] = Field(default=None, description="是否启用 BGM")
source: Optional[str] = Field(default=None, description="BGM 来源: library/upload/ai_recommend")
asset_id: Optional[str] = Field(default=None, max_length=64, description="BGM 素材 ID")
preset_id: Optional[str] = Field(default=None, max_length=64, description="预设 BGM ID")
audio_url: Optional[str] = Field(default=None, max_length=500, description="BGM 音频 URL")
volume: Optional[float] = Field(default=None, ge=0.0, le=1.0, description="音量 (0.0 ~ 1.0)")
fade_in: Optional[float] = Field(default=None, ge=0.0, le=30.0, description="淡入时长(秒)")
fade_out: Optional[float] = Field(default=None, ge=0.0, le=30.0, description="淡出时长(秒)")
loop_enabled: Optional[bool] = Field(default=None, description="是否循环播放")
sidechain_enabled: Optional[bool] = Field(default=None, description="是否启用人声闪避")
sidechain_ratio: Optional[float] = Field(default=None, ge=0.0, le=1.0, description="闪避音量降低比例")
# ── 片段调整 ────────────────────────────────────────────────────────────────
class SpeedAdjustRequest(BaseModel):
"""调速请求"""
speed: float = Field(..., ge=0.25, le=4.0, description="播放速度 0.25~4.0")
class VolumeAdjustRequest(BaseModel):
"""音量调节请求"""
volume: float = Field(..., ge=0.0, le=2.0, description="音量倍率 0~2.0(1.0=原音量)")
class TrimAdjustRequest(BaseModel):
"""裁剪请求"""
trim_start: float = Field(0.0, ge=0.0, description="开头裁剪秒数")
trim_end: float = Field(0.0, ge=0.0, description="结尾裁剪秒数")
class ClipAdjustmentsRequest(BaseModel):
"""统一调整请求"""
speed: Optional[float] = Field(default=None, ge=0.25, le=4.0)
volume: Optional[float] = Field(default=None, ge=0.0, le=2.0)
trim_start: Optional[float] = Field(default=None, ge=0.0)
trim_end: Optional[float] = Field(default=None, ge=0.0)
class BatchSpeedRequest(BaseModel):
"""批量调速请求"""
speed: float = Field(..., ge=0.25, le=4.0, description="播放速度")
class ClipAdjustResponse(BaseModel):
"""片段调整响应"""
clip_id: str
speed: float
volume: float
trim_start: float
trim_end: float
duration: float
class BatchSpeedResponse(BaseModel):
"""批量调速响应"""
updated_count: int
plan_id: str
# ── 片段批量操作 ────────────────────────────────────────────────────────────
class ClipReorderItem(BaseModel):
"""重排序条目"""
clip_id: str
new_order: int = Field(..., ge=0, description="新的排序序号")
class ClipReorderRequest(BaseModel):
"""片段重排序请求"""
items: List[ClipReorderItem] = Field(..., min_length=1, max_length=500, description="重排序条目列表")
class ClipReorderResponse(BaseModel):
"""片段重排序响应"""
success: bool = True
updated_count: int
message: str = ""
class ClipBatchDeleteRequest(BaseModel):
"""批量删除片段请求"""
clip_ids: List[str] = Field(..., min_length=1, max_length=500, description="要删除的片段ID列表")
class ClipBatchDeleteResponse(BaseModel):
"""批量删除片段响应"""
success: bool = True
deleted_count: int
message: str = ""
class ClipsFromAssetsRequest(BaseModel):
"""从素材批量创建片段请求"""
asset_ids: List[str] = Field(
..., min_length=1, max_length=200, description="素材 ID 列表,按顺序追加到时间线末尾"
)
clip_type: str = Field(default="main", description="片段类型,默认 main")
class ClipsFromAssetsResponse(BaseModel):
"""从素材批量创建片段响应"""
success: bool = True
created_count: int
message: str = ""
clip_ids: List[str] = Field(default_factory=list, description="创建的片段ID列表")
# ── 封面配置 ────────────────────────────────────────────────────────────────
class CoverConfigResponse(BaseModel):
"""封面配置响应"""
type: str = Field(..., description="封面类型: ai_frame / manual / upload")
image_url: str = Field(default="", description="封面图片 URL")
frame_time: Optional[float] = Field(default=None, description="抽帧时间点(秒)")
class CoverUpdateRequest(BaseModel):
"""更新封面配置请求"""
type: Optional[str] = Field(default=None, description="封面类型")
image_url: Optional[str] = Field(default=None, description="封面图片 URL")
frame_time: Optional[float] = Field(default=None, ge=0.0, description="抽帧时间点(秒)")
class CoverExtractRequest(BaseModel):
"""从片段抽帧生成封面请求"""
clip_id: str = Field(..., description="片段 ID")
frame_time: float = Field(1.0, ge=0.0, description="抽帧时间点(秒)")
class CoverSmartRequest(BaseModel):
"""智能选帧请求"""
clip_id: Optional[str] = Field(default=None, description="指定片段 ID(不传则用第一个视频片段)")
class CoverGenerateResponse(BaseModel):
"""封面生成响应"""
type: str = Field(..., description="封面类型")
image_url: str = Field(..., description="封面图片 URL")
frame_time: Optional[float] = Field(default=None, description="抽帧时间点(秒)")
# ── 导出配置 ────────────────────────────────────────────────────────────────
class ExportConfigResponse(BaseModel):
"""导出配置响应"""
resolution: str
fps: int
video_bitrate: int
audio_bitrate: int
format: str
quality_preset: str
watermark_enabled: bool
watermark_text: str
class ExportUpdateRequest(BaseModel):
"""更新导出配置请求"""
resolution: Optional[str] = None
fps: Optional[int] = Field(default=None, ge=15, le=60)
video_bitrate: Optional[int] = Field(default=None, ge=1000, le=20000)
audio_bitrate: Optional[int] = Field(default=None, ge=64, le=320)
format: Optional[str] = None
quality_preset: Optional[str] = None
watermark_enabled: Optional[bool] = None
watermark_text: Optional[str] = None
@validator("resolution")
def validate_resolution(cls, v):
if v is None:
return v
if not _EXPORT_RESOLUTION_PATTERN.match(v):
raise ValueError("分辨率格式错误,应为 宽x高,如 1080x1920")
w, h = v.split("x")
if int(w) < 100 or int(h) < 100:
raise ValueError("分辨率数值过小")
if int(w) > 4096 or int(h) > 4096:
raise ValueError("分辨率数值过大,最大 4096x4096")
return v
@validator("format")
def validate_format(cls, v):
if v is None:
return v
if v not in _EXPORT_VALID_FORMATS:
raise ValueError(f"无效格式: {v},支持: {_EXPORT_VALID_FORMATS}")
return v
@validator("quality_preset")
def validate_quality_preset(cls, v):
if v is None:
return v
if v not in _EXPORT_VALID_QUALITY_PRESETS:
raise ValueError(f"无效质量预设: {v},支持: {_EXPORT_VALID_QUALITY_PRESETS}")
return v
class ExportPresetItem(BaseModel):
"""导出预设条目"""
id: str
name: str
resolution: str
fps: int
video_bitrate: int
audio_bitrate: int
format: str
quality_preset: str
description: str
size_hint: str
class ExportPresetListResponse(BaseModel):
"""导出预设列表响应"""
items: List[ExportPresetItem]
total: int
# ── 滤镜 ────────────────────────────────────────────────────────────────────
class FilterPresetResponse(BaseModel):
"""滤镜预设响应"""
id: str
name: str
category: str
description: str
tags: List[str] = Field(default_factory=list)
class FilterConfigResponse(BaseModel):
"""滤镜配置响应"""
enabled: bool
preset_id: str
intensity: int
brightness: float
contrast: float
saturation: float
warmth: float
class FilterUpdateRequest(BaseModel):
"""更新滤镜配置请求"""
enabled: Optional[bool] = None
preset_id: Optional[str] = None
intensity: Optional[int] = Field(default=None, ge=0, le=100)
brightness: Optional[float] = Field(default=None, ge=-1.0, le=1.0)
contrast: Optional[float] = Field(default=None, ge=0.0, le=2.0)
saturation: Optional[float] = Field(default=None, ge=0.0, le=3.0)
warmth: Optional[float] = Field(default=None, ge=-1.0, le=1.0)
class FilterPresetListResponse(BaseModel):
"""滤镜预设列表响应"""
items: List[FilterPresetResponse]
total: int
# ── 转场 ────────────────────────────────────────────────────────────────────
class TransitionPresetResponse(BaseModel):
"""转场预设响应"""
id: str
name: str
category: str
description: str
tags: List[str] = Field(default_factory=list)
default_duration: float
min_duration: float
max_duration: float
class TransitionUpdateRequest(BaseModel):
"""更新转场请求"""
effect: str = Field(..., description="转场效果 ID")
duration: Optional[float] = Field(default=None, ge=0.0, description="转场时长(秒)")
class BatchTransitionRequest(BaseModel):
"""批量设置转场请求"""
effect: str = Field(..., description="转场效果 ID")
duration: Optional[float] = Field(default=None, ge=0.0, description="转场时长(秒)")
apply_to: str = Field(
default="all",
description="应用范围: all=所有片段, except_first=除第一个外, except_last=除最后一个, middle=中间片段",
)
class ClipTransitionResponse(BaseModel):
"""片段转场信息响应"""
clip_id: str
effect: str
duration: float
class BatchTransitionResponse(BaseModel):
"""批量转场响应"""
updated_count: int
plan_id: str
class TransitionPresetListResponse(BaseModel):
"""转场预设列表响应"""
items: List[TransitionPresetResponse]
total: int
# ── 编辑器草稿 & 片段 ───────────────────────────────────────────────────────
class EditorDraftResponse(BaseModel):
"""模板编辑器草稿详情响应"""
plan_id: str
template_id: str
name: str
status: str
config: dict[str, Any]
total_duration: float
clip_count: int
is_draft: bool = True
class EditorUpdateRequest(BaseModel):
"""更新草稿请求"""
name: Optional[str] = Field(default=None, min_length=1, max_length=200)
config: Optional[dict[str, Any]] = Field(default=None)
total_duration: Optional[float] = Field(default=None, ge=0.0)
class EditorClipResponse(BaseModel):
"""片段响应"""
id: str
plan_id: str
clip_type: str
order: int
duration: float
text_content: str = ""
transition_effect: str = "cut"
playback_speed: float = 1.0
config: dict[str, Any] = Field(default_factory=dict)
class EditorClipListResponse(BaseModel):
"""片段列表响应"""
items: List[EditorClipResponse]
total: int
class EditorClipCreateRequest(BaseModel):
"""创建片段请求"""
clip_type: str = Field(..., min_length=1, max_length=32)
order: int = Field(..., ge=0)
duration: float = Field(..., gt=0.0)
text_content: str = Field(default="", max_length=2000)
transition_effect: str = Field(default="cut", max_length=32)
config: dict[str, Any] = Field(default_factory=dict)
class EditorClipUpdateRequest(BaseModel):
"""更新片段请求"""
order: Optional[int] = Field(default=None, ge=0)
duration: Optional[float] = Field(default=None, gt=0.0)
text_content: Optional[str] = Field(default=None, max_length=2000)
transition_effect: Optional[str] = Field(default=None, max_length=32)
playback_speed: Optional[float] = Field(default=None, gt=0.0)
config: Optional[dict[str, Any]] = None
class EditorPublishResponse(BaseModel):
"""发布草稿响应"""
template_id: str
status: str = "published"
clip_count: int
version: int = 1
class EditorTemplateVersionItem(BaseModel):
"""模板版本历史条目"""
version: int
name: str
editing_mode: str
clip_count: int
change_note: str
published_by: str
created_at: str
class EditorVersionListResponse(BaseModel):
"""模板版本列表响应"""
versions: list[EditorTemplateVersionItem]
total: int
class EditorRollbackRequest(BaseModel):
"""回滚请求体"""
version: int
class EditorRollbackResponse(BaseModel):
"""回滚响应"""
template_id: str
status: str = "rolled_back"
rollback_to_version: int
new_version: int
clip_count: int
# ── 片段分割与合并 ──────────────────────────────────────────────────────────
class SplitClipRequest(BaseModel):
"""分割片段请求体"""
split_time: float = Field(..., gt=0, description="分割点(秒,相对于片段起始)")
class MergeClipsRequest(BaseModel):
"""合并片段请求体"""
clip_ids: list[str] = Field(..., min_length=2, description="要合并的片段 ID 列表")
# ── 时间线 ──────────────────────────────────────────────────────────────────
class EditorTimelineSceneResponse(BaseModel):
"""时间线场景"""
scene: str
time: str
duration: float
color: str
clip_id: str = ""
clip_type: str = ""
class EditorTimelineResponse(BaseModel):
"""时间线响应"""
plan_id: str
total_duration: float
scenes: List[EditorTimelineSceneResponse]
+173
View File
@@ -0,0 +1,173 @@
"""字幕管理路由.
端点:
- GET /clips/{clip_id}/subtitles 字幕列表
- POST /clips/{clip_id}/subtitles 新增字幕
- PUT /clips/{clip_id}/subtitles/{subtitle_id} 更新字幕
- DELETE /clips/{clip_id}/subtitles/{subtitle_id} 删除字幕
- PUT /clips/{clip_id}/subtitles 批量更新字幕(全量替换)
"""
from __future__ import annotations
from typing import Any
from app.auth import AuthenticatedUser, get_current_user
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends, HTTPException, status
from .dependencies import get_draft_plan_id, get_editor_services
router = APIRouter(tags=["Template Editor"])
def _get_clip_subtitles(plan_svc: EditPlanService, clip_id: str, plan_id: str) -> list[dict[str, Any]]:
"""获取片段字幕列表,统一校验"""
clip = plan_svc.get_clip(clip_id)
if not clip:
raise HTTPException(status_code=404, detail="片段不存在")
if clip.plan_id != plan_id:
raise HTTPException(status_code=404, detail="片段不存在")
config = clip.config or {}
subtitles = config.get("subtitles", [])
if not isinstance(subtitles, list):
subtitles = []
return subtitles
@router.get("/clips/{clip_id}/subtitles", response_model=list[dict[str, Any]])
def get_editor_clip_subtitles(
template_id: str,
clip_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> list[dict[str, Any]]:
"""获取片段的字幕列表"""
_, plan_svc = services
return _get_clip_subtitles(plan_svc, clip_id, plan_id)
@router.post("/clips/{clip_id}/subtitles", response_model=dict[str, Any])
def create_editor_clip_subtitle(
template_id: str,
clip_id: str,
body: dict[str, Any],
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> dict[str, Any]:
"""新增片段字幕"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if not clip:
raise HTTPException(status_code=404, detail="片段不存在")
config = dict(clip.config) if clip.config else {}
subtitles = config.get("subtitles", [])
if not isinstance(subtitles, list):
subtitles = []
new_id = f"sub_{len(subtitles) + 1}"
new_subtitle = {
"id": body.get("id", new_id),
"start_time": body.get("start_time", 0.0),
"end_time": body.get("end_time", 0.0),
"text": body.get("text", ""),
"style": body.get("style", {}),
}
subtitles.append(new_subtitle)
config["subtitles"] = subtitles
plan_svc.update_clip(clip_id, config=config)
return new_subtitle
@router.put("/clips/{clip_id}/subtitles/{subtitle_id}", response_model=dict[str, Any])
def update_editor_clip_subtitle(
template_id: str,
clip_id: str,
subtitle_id: str,
body: dict[str, Any],
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> dict[str, Any]:
"""更新片段字幕"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if not clip:
raise HTTPException(status_code=404, detail="片段不存在")
config = dict(clip.config) if clip.config else {}
subtitles = config.get("subtitles", [])
if not isinstance(subtitles, list):
subtitles = []
found = False
for i, sub in enumerate(subtitles):
if sub.get("id") == subtitle_id:
subtitles[i].update(body)
found = True
break
if not found:
raise HTTPException(status_code=404, detail="字幕不存在")
config["subtitles"] = subtitles
plan_svc.update_clip(clip_id, config=config)
return subtitles[i]
@router.delete(
"/clips/{clip_id}/subtitles/{subtitle_id}",
status_code=status.HTTP_204_NO_CONTENT,
)
def delete_editor_clip_subtitle(
template_id: str,
clip_id: str,
subtitle_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
):
"""删除片段字幕"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if not clip:
raise HTTPException(status_code=404, detail="片段不存在")
config = dict(clip.config) if clip.config else {}
subtitles = config.get("subtitles", [])
if not isinstance(subtitles, list):
subtitles = []
new_subtitles = [s for s in subtitles if s.get("id") != subtitle_id]
if len(new_subtitles) == len(subtitles):
raise HTTPException(status_code=404, detail="字幕不存在")
config["subtitles"] = new_subtitles
plan_svc.update_clip(clip_id, config=config)
return None
@router.put("/clips/{clip_id}/subtitles", response_model=list[dict[str, Any]])
def batch_update_editor_clip_subtitles(
template_id: str,
clip_id: str,
body: list[dict[str, Any]],
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> list[dict[str, Any]]:
"""批量更新片段字幕(全量替换)"""
_, plan_svc = services
clip = plan_svc.get_clip(clip_id)
if not clip:
raise HTTPException(status_code=404, detail="片段不存在")
config = dict(clip.config) if clip.config else {}
config["subtitles"] = body
plan_svc.update_clip(clip_id, config=config)
return body
+61
View File
@@ -0,0 +1,61 @@
"""时间线路由.
端点:
- GET /timeline 时间线场景数据
"""
from __future__ import annotations
from app.auth import AuthenticatedUser, get_current_user
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, Depends
from ._utils import _CLIP_TYPE_COLORS, _DEFAULT_COLOR, _clip_type_to_scene_label, _format_time
from .dependencies import get_draft_plan_id, get_editor_services
from .schemas import EditorTimelineResponse, EditorTimelineSceneResponse
router = APIRouter(tags=["Template Editor"])
@router.get("/timeline", response_model=EditorTimelineResponse)
def get_editor_timeline(
template_id: str,
plan_id: str = Depends(get_draft_plan_id),
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
_: AuthenticatedUser = Depends(get_current_user),
) -> EditorTimelineResponse:
"""获取草稿的时间线场景数据"""
_, plan_svc = services
plan = plan_svc.get_plan_or_raise(plan_id)
clips = plan_svc.list_clips(plan_id=plan_id, skip=0, limit=200)
clips.sort(key=lambda c: c.order)
scenes = []
current_time = 0.0
for clip in clips:
start = current_time
end = start + clip.duration
color = _CLIP_TYPE_COLORS.get(clip.clip_type, _DEFAULT_COLOR)
scene_label = _clip_type_to_scene_label(clip.clip_type, clip.text_content)
scenes.append(
EditorTimelineSceneResponse(
scene=scene_label,
time=f"{_format_time(start)} - {_format_time(end)}",
duration=clip.duration,
color=color,
clip_id=clip.id,
clip_type=clip.clip_type,
)
)
current_time = end
total_duration = sum(s.duration for s in scenes) or plan.total_duration
return EditorTimelineResponse(
plan_id=plan_id,
total_duration=total_duration,
scenes=scenes,
)
+3 -6
View File
@@ -6,6 +6,7 @@ import logging
from typing import Optional
from app.auth import AuthenticatedUser, get_current_user
from app.core.celery_app import celery_app
from app.dependencies import (
get_audio_url_signer,
get_cosyvoice_service,
@@ -181,13 +182,9 @@ def synthesize(
try:
if is_segment:
from worker_app.tasks import process_tts_segment_synthesis
process_tts_segment_synthesis.delay(job.id)
celery_app.send_task("worker.process_tts_segment_synthesis", args=[job.id])
else:
from worker_app.tasks import process_tts_synthesis
process_tts_synthesis.delay(job.id)
celery_app.send_task("worker.process_tts_synthesis", args=[job.id])
except Exception as e:
# Celery 调度失败,标记 job 为 failed
try:
+3 -6
View File
@@ -6,6 +6,7 @@ import logging
from typing import Optional
from app.auth import AuthenticatedUser, get_current_user
from app.core.celery_app import celery_app
from app.dependencies import get_cosyvoice_service, get_voice_clone_profile_repository
from app.schemas.voice_clone import (
CreateVoiceCloneRequest,
@@ -97,9 +98,7 @@ def create_voice_clone(
task_id = (profile.metadata or {}).get("cosyvoice_task_id", "")
if profile.status == "processing" and task_id:
try:
from worker_app.tasks import process_voice_clone
process_voice_clone.delay(profile.id)
celery_app.send_task("worker.process_voice_clone", args=[profile.id])
logger.info(f"Celery task dispatched for voice clone {profile.id}")
except Exception as e:
logger.error(f"Failed to dispatch Celery task: {e}")
@@ -213,9 +212,7 @@ def retry_voice_clone(
task_id = (profile.metadata or {}).get("cosyvoice_task_id", "")
if profile.status == "processing" and task_id:
try:
from worker_app.tasks import process_voice_clone
process_voice_clone.delay(profile.id)
celery_app.send_task("worker.process_voice_clone", args=[profile.id])
logger.info(f"Celery task dispatched for voice clone retry {profile.id}")
except Exception as e:
logger.error(f"Failed to dispatch Celery task: {e}")
+23 -166
View File
@@ -1,171 +1,28 @@
import os
from typing import Optional
"""API 服务配置(向后兼容层)。
from pydantic import AliasChoices, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
统一配置已迁移到 packages.config.api_settings。
新代码请使用:
from packages.config import APISettings, get_api_settings
本文件保留 Settings 类名、get_settings() 函数、settings 模块级单例,
确保所有旧的 import 路径仍然有效。
"""
class Settings(BaseSettings):
APP_NAME: str = "xiaoxia-saas"
APP_VERSION: str = "0.1.61"
ENVIRONMENT: str = "development"
DEBUG: bool = True
# 应用基础 URL,用于生成认证邮件中的链接
# 开发环境默认 http://localhost:3000
# 生产环境应通过环境变量 APP_BASE_URL 设置
APP_BASE_URL: str = "http://localhost:3000"
# Container bind address; external expose is controlled by Docker/Nginx.
API_HOST: str = "0.0.0.0" # nosec: B104
API_PORT: int = 8000
DATABASE_URL: str = "postgresql+psycopg://postgres:postgres@localhost:5432/xiaoxia_saas"
DATABASE_POOL_SIZE: int = 20
DATABASE_MAX_OVERFLOW: int = 10 # 调整为合理值:pool_size(20) + max_overflow(10) = 最大30连接
DATABASE_POOL_TIMEOUT: int = 30
DATABASE_POOL_RECYCLE: int = 3600
USE_IN_MEMORY_DB: bool = False
AUTO_CREATE_SCHEMA: bool = False
REDIS_URL: str = "redis://localhost:6379/0"
ENABLE_REDIS_SESSIONS: bool = False
# JWT secret key - MUST be set via environment variable, no default allowed
JWT_SECRET_KEY: Optional[str] = None
# JWT 算法与过期时间(与 .env.example 对齐)
JWT_ALGORITHM: str = "HS256"
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 30
@field_validator("JWT_SECRET_KEY", mode="before")
@classmethod
def validate_jwt_secret_key(cls, v):
if v is None or v == "":
raise ValueError(
"JWT_SECRET_KEY must be set via environment variable. " "Do not use default value in production!"
)
# Block known insecure default values
insecure_defaults = [
"your-secret-key-change-in-production",
"your-secret-key",
"secret",
"changeme",
"password",
]
if v.lower() in [d.lower() for d in insecure_defaults]:
raise ValueError(
f"JWT_SECRET_KEY '{v}' is insecure. " "Please set a strong random secret via environment variable."
)
return v
ENABLE_EMAIL_DELIVERY: bool = False
SMTP_HOST: str = "smtp.gmail.com"
SMTP_PORT: int = 587
SMTP_USER: str = ""
SMTP_PASSWORD: str = ""
SMTP_FROM_EMAIL: str = ""
SMTP_FROM_NAME: str = "小虾 SaaS"
SMTP_USE_TLS: bool = True
CELERY_BROKER_URL: str = "redis://localhost:6379/0"
CELERY_RESULT_BACKEND: str = "redis://localhost:6379/1"
# OSS 七牛云相关
OSS_ENDPOINT: str = "oss-cn-hangzhou.aliyuncs.com"
OSS_ACCESS_KEY_ID: str = ""
OSS_ACCESS_KEY_SECRET: str = ""
OSS_BUCKET_NAME: str = "xiaoxia-autocut"
@field_validator("OSS_ACCESS_KEY_ID", mode="before")
@classmethod
def validate_oss_access_key_id(cls, v):
if (v is None or v == "") and os.getenv("APP_ENV", "development") != "development":
raise ValueError(
"OSS_ACCESS_KEY_ID must be set via environment variable in non-development environments. "
"Check the server .env file (e.g. /var/lib/xiaoxia-saas-staging/.env)."
)
return v or ""
@field_validator("OSS_ACCESS_KEY_SECRET", mode="before")
@classmethod
def validate_oss_access_key_secret(cls, v):
if (v is None or v == "") and os.getenv("APP_ENV", "development") != "development":
raise ValueError(
"OSS_ACCESS_KEY_SECRET must be set via environment variable in non-development environments. "
"Check the server .env file (e.g. /var/lib/xiaoxia-saas-staging/.env)."
)
return v or ""
OSS_DIRECT_UPLOAD_MAX_MB: int = Field(
default=2000,
validation_alias=AliasChoices("OSS_DIRECT_UPLOAD_MAX_MB", "MAX_UPLOAD_SIZE_MB"),
)
OSS_DIRECT_UPLOAD_EXPIRE_SECONDS: int = 900
CORS_ORIGINS_RAW: str = "http://localhost:3000,http://localhost:5173,http://localhost:8000"
# 渲染引擎选择:legacy=旧VideoComposeService,unified=新UnifiedRenderService
RENDER_ENGINE: str = "legacy"
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
@property
def CORS_ORIGINS(self) -> list[str]:
return [origin.strip() for origin in self.CORS_ORIGINS_RAW.split(",") if origin.strip()]
@property
def database_url(self) -> str:
return self.DATABASE_URL
@property
def redis_url(self) -> str:
return self.REDIS_URL
@property
def celery_broker_url(self) -> str:
return self.CELERY_BROKER_URL
@property
def celery_result_backend(self) -> str:
return self.CELERY_RESULT_BACKEND
@property
def oss_endpoint(self) -> str:
return self.OSS_ENDPOINT
@property
def oss_access_key_id(self) -> str:
return self.OSS_ACCESS_KEY_ID
@property
def oss_access_key_secret(self) -> str:
return self.OSS_ACCESS_KEY_SECRET
@property
def oss_bucket_name(self) -> str:
return self.OSS_BUCKET_NAME
_settings: Optional[Settings] = None
def get_settings() -> Settings:
global _settings
if _settings is None:
env = os.getenv("APP_ENV", "development")
env_file = f".env.{env}" if env != "development" else ".env"
if os.path.exists(env_file):
_settings = Settings(_env_file=env_file)
else:
_settings = Settings()
return _settings
from packages.config import APISettings as Settings
from packages.config import get_api_settings as get_settings
from packages.config import reload_settings_cache
# 模块级单例(向后兼容)
settings = get_settings()
# 暴露旧的 reload_settings 函数名
def reload_settings():
"""重新加载配置(测试用)。"""
reload_settings_cache()
global settings
settings = get_settings()
return settings
__all__ = ["Settings", "get_settings", "settings", "reload_settings"]
Regular → Executable
+10 -9
View File
@@ -1,19 +1,20 @@
"""Compatibility layer for the canonical API settings module.
"""向后兼容层 — 配置已统一到 packages.config。
Use `app.config` as the single source of truth for API configuration.
This module remains only for older imports during migration.
新代码请使用:
from packages.config import get_api_settings, APISettings
"""
from app.config import Settings as AppSettings
from app.config import get_settings, settings
from packages.config import APISettings as AppSettings
from packages.config import get_api_settings as get_settings
from packages.config import reload_settings_cache
def reload_settings() -> AppSettings:
"""Reload settings for tests and legacy callers."""
import app.config as canonical_config
"""重新加载配置(测试用)。"""
reload_settings_cache()
return get_settings()
canonical_config.settings = canonical_config.get_settings()
return canonical_config.settings
settings = get_settings()
__all__ = ["AppSettings", "get_settings", "reload_settings", "settings"]
+6
View File
@@ -51,6 +51,11 @@ class CreateGenerationTaskRequest(BaseModel):
default="",
description="输出分辨率,格式为 WIDTHxHEIGHT,如 1280x720、1080x1920。为空使用默认 1280x720",
)
# ── 自定义 BGM ──
bgm_config: dict = Field(
default_factory=dict,
description="自定义BGM配置,覆盖模板BGM设置。支持 enabled/source/asset_id/preset_id/audio_url/volume 等字段",
)
@model_validator(mode="after")
def _check_at_least_one_mode(self) -> "CreateGenerationTaskRequest":
@@ -80,6 +85,7 @@ class GenerationTaskResponse(BaseModel):
batch_id: str = ""
video_title: str = ""
resolution: str = ""
bgm_config: dict = Field(default_factory=dict)
status: str
progress: float
result_count: int
+16 -15
View File
@@ -298,11 +298,13 @@ def _semantic_match_fallback(
score = min(1.0, base_score * 0.8 + name_bonus)
score = round(score, 3)
results.append({
**asset,
"match_score": score,
"match_reason": "fallback_keyword",
})
results.append(
{
**asset,
"match_score": score,
"match_reason": "fallback_keyword",
}
)
# 按匹配度降序
results.sort(key=lambda x: x["match_score"], reverse=True)
@@ -414,9 +416,7 @@ def semantic_match_assets(
tags = asset.get("tags", [])
tags_str = ",".join(str(t) for t in tags[:5])
desc = str(asset.get("description", ""))[:80]
asset_summaries.append(
f"ID:{aid} | 名称:{name} | 标签:[{tags_str}] | 描述:{desc}"
)
asset_summaries.append(f"ID:{aid} | 名称:{name} | 标签:[{tags_str}] | 描述:{desc}")
asset_ids = [str(a.get("id", "")) for a in assets[:50]]
@@ -434,8 +434,7 @@ def semantic_match_assets(
user_prompt = (
f"目标视频描述:{description}\n\n"
f"素材列表:\n" + "\n".join(asset_summaries) +
f"\n\n请返回每个素材的匹配分数JSON:"
f"素材列表:\n" + "\n".join(asset_summaries) + "\n\n请返回每个素材的匹配分数JSON:"
)
messages = [
@@ -457,11 +456,13 @@ def semantic_match_assets(
for asset in assets:
aid = str(asset.get("id", ""))
score = scores.get(aid, 0.3) # 没评分的给默认偏低分
matched.append({
**asset,
"match_score": round(score, 3),
"match_reason": "doubao_semantic",
})
matched.append(
{
**asset,
"match_score": round(score, 3),
"match_reason": "doubao_semantic",
}
)
matched.sort(key=lambda x: x["match_score"], reverse=True)
logger.info(
-8
View File
@@ -1,8 +0,0 @@
from fastapi import APIRouter
router = APIRouter()
@router.get("/health")
def health():
return {"ok": True, "service": "api"}
+6117
View File
File diff suppressed because it is too large Load Diff
Executable → Regular
View File
Executable → Regular
View File
+2 -2
View File
@@ -8,8 +8,8 @@
* - POST /api/v1/templates/{id}/toggle-favorite — 收藏/取消收藏
*/
import apiClient from "./client"
import type { TitleConfig, SubtitleConfig, BgmConfig } from "./editingPlanner"
import type { EditPlanConfig } from "./templateEditor"
import type { TitleConfig, SubtitleConfig, BgmConfig } from "./editing-planner"
import type { EditPlanConfig } from "./template-editor"
/* ──────────── 类型定义 ──────────── */
@@ -12,8 +12,8 @@
import React, { useState, useMemo, useCallback, useRef, useEffect } from "react"
import "./AssetSelector.css"
import { Input, Select, Button } from "@/components/ui"
import type { MediaAsset } from "@/api/templateEditor"
import { MATERIAL_TYPE_LABELS, MATERIAL_TYPE_ICONS, QUALITY_OPTIONS } from "@/api/templateEditor"
import type { MediaAsset } from "@/api/template-editor"
import { MATERIAL_TYPE_LABELS, MATERIAL_TYPE_ICONS, QUALITY_OPTIONS } from "@/api/template-editor"
/* ──────────── 类型 ──────────── */
View File
View File
+2 -2
View File
@@ -12,8 +12,8 @@
*/
import React, { useState, useCallback, useRef, useEffect } from "react"
import { Modal, Button } from "@/components/ui"
import { createVoiceClone, toVoiceClone } from "@/api/voiceClone"
import type { VoiceClone } from "@/api/voiceClone"
import { createVoiceClone, toVoiceClone } from "@/api/voice-clone"
import type { VoiceClone } from "@/api/voice-clone"
import { uploadAsset } from "@/api/assets"
import "./clone-modal.css"
View File
Executable → Regular
View File
+2 -2
View File
@@ -5,8 +5,8 @@
* 全部完成(ready / failed)后停止轮询。
*/
import { useState, useEffect, useCallback, useRef } from "react"
import { getVoiceClones } from "@/api/voiceClone"
import type { VoiceClone } from "@/api/voiceClone"
import { getVoiceClones } from "@/api/voice-clone"
import type { VoiceClone } from "@/api/voice-clone"
const POLL_INTERVAL = 3000 // 3 秒
View File
+21 -178
View File
@@ -52,140 +52,26 @@ import {
type BatchOperationResult,
} from "@/api/assets"
import { Button, Input, Select } from "@/components/ui"
import {
type AssetItem,
type AssetKind,
type StatusType,
mapLibrary,
mapAsset,
} from "@/pages/assets/types"
import {
MAX_FILE_SIZE,
LARGE_FILE_THRESHOLD,
TYPE_FILTER_OPTIONS,
TIME_FILTER_OPTIONS,
LIBRARY_KIND_OPTIONS,
CATEGORY_OPTIONS,
} from "@/pages/assets/constants"
import { kindLabel, thumbGradient } from "@/pages/assets/utils/asset"
import "./assets.css"
/* ============================================================
* 类型
* ============================================================ */
type AssetKind = "video" | "image" | "voice"
type StatusType = "ok" | "warn" | "bad" | "info"
interface LibraryItem {
id: string
name: string
kind: AssetKind
count: number
}
interface AssetItem {
id: string
name: string
kind: AssetKind
thumbUrl?: string
fileUrl?: string
status: StatusType
statusLabel: string
/** 是否处于处理中状态(上传中/入库中/诊断中) */
loading?: boolean
duration?: string
size: number
createdAt: string
}
/* ============================================================
* 映射:后端 → 前端
* ============================================================ */
/** 根据 mime_type 推断前端 AssetKind */
const inferKind = (mimeType: string): AssetKind => {
if (mimeType.startsWith("video/")) return "video"
if (mimeType.startsWith("audio/")) return "voice"
return "image"
}
/** 根据 quality_score / classification_status / asset status 推断前端状态 */
const inferStatus = (
score?: number,
classificationStatus?: string,
assetStatus?: string,
): { status: StatusType; label: string; loading?: boolean } => {
// 已删除素材(正常情况列表已过滤,这里是防御性处理)
if (assetStatus === "deleted") {
return { status: "bad", label: "已删除" }
}
// 处理中状态:上传中 / 入库中 / 处理中
if (
assetStatus === "uploading" ||
assetStatus === "ingesting" ||
assetStatus === "processing" ||
assetStatus === "pending"
) {
return { status: "info", label: "处理中", loading: true }
}
// 失败状态
if (assetStatus === "error" || assetStatus === "failed") {
return { status: "bad", label: "处理失败" }
}
// 素材已就绪(status=ready)时,不应因 classification 未执行而显示"处理中"
if (assetStatus === "ready") {
if (score == null) return { status: "info", label: "待诊断" }
if (score >= 70) return { status: "ok", label: "合格" }
if (score >= 40) return { status: "warn", label: "待优化" }
return { status: "bad", label: "不合格" }
}
// 素材未就绪:classification 正在处理中
if (classificationStatus === "processing" || classificationStatus === "pending") {
return { status: "info", label: "处理中", loading: true }
}
if (score == null) return { status: "info", label: "待诊断" }
if (score >= 70) return { status: "ok", label: "合格" }
if (score >= 40) return { status: "warn", label: "待优化" }
return { status: "bad", label: "不合格" }
}
/** 格式化时长(秒 → mm:ss) */
const formatDuration = (seconds: number): string => {
const m = Math.floor(seconds / 60)
const s = Math.floor(seconds % 60)
return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`
}
/** 将后端 AssetLibraryItem 映射为前端 LibraryItem */
const mapLibrary = (item: AssetLibraryItem): LibraryItem => ({
id: item.id,
name: item.name,
kind: item.kind || inferKind("video"),
count: item.asset_count ?? 0,
})
/** 将后端 ApiAssetItem 映射为前端 AssetItem */
const mapAsset = (item: ApiAssetItem): AssetItem => {
const { status, label, loading } = inferStatus(
item.quality_score ?? undefined,
item.classification_status ?? undefined,
item.status ?? undefined,
)
const metadata = item.metadata || {}
const kind = inferKind(item.mime_type || "")
return {
id: item.id,
name: item.name,
kind,
// 视频类型不能用 file_url 做缩略图(是视频文件,<img> 无法渲染)
// 处理中的素材没有缩略图,显示占位符
thumbUrl: loading
? undefined
: (item.thumbnail_url as string | undefined) ||
(metadata.thumbnail_url as string | undefined) ||
(kind !== "video" ? (item.file_url as string | undefined) : undefined),
fileUrl: (item.file_url as string | undefined) || (metadata.file_url as string | undefined),
status,
statusLabel: label,
loading,
duration: metadata.duration != null ? formatDuration(metadata.duration as number) : undefined,
size: item.file_size ? +(item.file_size / (1024 * 1024)).toFixed(1) : 0,
createdAt: item.created_at ? new Date(item.created_at).toISOString().slice(0, 10) : "—",
}
}
/* ============================================================
* 常量
* ============================================================ */
const MAX_FILE_SIZE = 2048 * 1024 * 1024
const LARGE_FILE_THRESHOLD = 100 * 1024 * 1024
/* ============================================================
* 工具函数
* 工具函数(UI 相关)
* ============================================================ */
const kindIcon = (kind: AssetKind) => {
switch (kind) {
@@ -198,29 +84,6 @@ const kindIcon = (kind: AssetKind) => {
}
}
const kindLabel = (kind: AssetKind) => {
switch (kind) {
case "video":
return "视频"
case "image":
return "图片"
case "voice":
return "配音"
}
}
/** 根据素材类型返回渐变背景 */
const thumbGradient = (kind: AssetKind): string => {
switch (kind) {
case "video":
return "linear-gradient(135deg, #312e81 0%, #4f46e5 50%, #6366f1 100%)"
case "image":
return "linear-gradient(135deg, #78350f 0%, #d97706 50%, #f59e0b 100%)"
case "voice":
return "linear-gradient(135deg, #064e3b 0%, #059669 50%, #10b981 100%)"
}
}
/* ============================================================
* StatusPill 组件
* ============================================================ */
@@ -922,22 +785,13 @@ const AssetLibrary: React.FC = () => {
value={filterType}
onChange={setFilterType}
style={{ width: 120 }}
options={[
{ value: "all", label: "全部类型" },
{ value: "video", label: "视频" },
{ value: "image", label: "图片" },
]}
options={TYPE_FILTER_OPTIONS}
/>
<Select
value={filterTime}
onChange={setFilterTime}
style={{ width: 120 }}
options={[
{ value: "all", label: "全部时间" },
{ value: "today", label: "今天" },
{ value: "week", label: "近一周" },
{ value: "month", label: "近一月" },
]}
options={TIME_FILTER_OPTIONS}
/>
</div>
<div className="xx-assets-filters-right">
@@ -1062,10 +916,7 @@ const AssetLibrary: React.FC = () => {
value={newLibKind}
onChange={(v) => setNewLibKind(v)}
style={{ width: "100%" }}
options={[
{ value: "video", label: "视频" },
{ value: "image", label: "图片" },
]}
options={LIBRARY_KIND_OPTIONS}
/>
</div>
</div>
@@ -1160,15 +1011,7 @@ const AssetLibrary: React.FC = () => {
onChange={(v) => setBatchCategory(v)}
placeholder="请选择分类"
style={{ width: "100%" }}
options={[
{ value: "person", label: "人物" },
{ value: "scenic", label: "风景" },
{ value: "product", label: "产品" },
{ value: "food", label: "美食" },
{ value: "animal", label: "动物" },
{ value: "architecture", label: "建筑" },
{ value: "other", label: "其他" },
]}
options={CATEGORY_OPTIONS}
/>
</div>
</AntModal>
+66
View File
@@ -0,0 +1,66 @@
/**
* 素材库常量
*/
import type { AssetKind } from "./types"
/** 文件大小限制 */
export const MAX_FILE_SIZE = 2048 * 1024 * 1024
export const LARGE_FILE_THRESHOLD = 100 * 1024 * 1024
/** 素材类型标签 */
export const KIND_LABELS: Record<AssetKind, string> = {
video: "视频",
image: "图片",
voice: "配音",
}
/** 类型筛选选项 */
export const TYPE_FILTER_OPTIONS: { value: string; label: string }[] = [
{ value: "all", label: "全部类型" },
{ value: "video", label: "视频" },
{ value: "image", label: "图片" },
]
/** 时间筛选选项 */
export const TIME_FILTER_OPTIONS: { value: string; label: string }[] = [
{ value: "all", label: "全部时间" },
{ value: "today", label: "今天" },
{ value: "week", label: "近一周" },
{ value: "month", label: "近一月" },
]
/** 新建库类型选项(当前仅支持视频和图片) */
export const LIBRARY_KIND_OPTIONS: { value: AssetKind; label: string }[] = [
{ value: "video", label: "视频" },
{ value: "image", label: "图片" },
]
/** 分类选项 */
export const CATEGORY_OPTIONS: { value: string; label: string }[] = [
{ value: "person", label: "人物" },
{ value: "scenic", label: "风景" },
{ value: "product", label: "产品" },
{ value: "food", label: "美食" },
{ value: "animal", label: "动物" },
{ value: "architecture", label: "建筑" },
{ value: "other", label: "其他" },
]
/** 智能标记视图 */
export const SMART_VIEW_OPTIONS: {
value: "recommended" | "caution" | "high_risk"
label: string
color: string
}[] = [
{ value: "recommended", label: "推荐", color: "success" },
{ value: "caution", label: "慎用", color: "warning" },
{ value: "high_risk", label: "高风险", color: "error" },
]
/** 状态配置 */
export const STATUS_CONFIG: Record<string, { label: string; className: string }> = {
ok: { label: "合格", className: "xx-status-pill xx-status-pill-ok" },
warn: { label: "待优化", className: "xx-status-pill xx-status-pill-warn" },
bad: { label: "不合格", className: "xx-status-pill xx-status-pill-bad" },
info: { label: "待诊断", className: "xx-status-pill xx-status-pill-info" },
}
+115
View File
@@ -0,0 +1,115 @@
/**
* 素材库类型定义
*/
import type { AssetLibraryItem, AssetItem as ApiAssetItem } from "@/api/assets"
import { formatDuration } from "./utils/format"
export type AssetKind = "video" | "image" | "voice"
export type StatusType = "ok" | "warn" | "bad" | "info"
export interface LibraryItem {
id: string
name: string
kind: AssetKind
count: number
}
export interface AssetItem {
id: string
name: string
kind: AssetKind
thumbUrl?: string
fileUrl?: string
status: StatusType
statusLabel: string
/** 是否处于处理中状态(上传中/入库中/诊断中) */
loading?: boolean
duration?: string
size: number
createdAt: string
}
/** 根据 mime_type 推断前端 AssetKind */
export const inferKind = (mimeType: string): AssetKind => {
if (mimeType.startsWith("video/")) return "video"
if (mimeType.startsWith("audio/")) return "voice"
return "image"
}
/** 根据 quality_score / classification_status / asset status 推断前端状态 */
export const inferStatus = (
score?: number,
classificationStatus?: string,
assetStatus?: string,
): { status: StatusType; label: string; loading?: boolean } => {
// 已删除素材(正常情况列表已过滤,这里是防御性处理)
if (assetStatus === "deleted") {
return { status: "bad", label: "已删除" }
}
// 处理中状态:上传中 / 入库中 / 处理中
if (
assetStatus === "uploading" ||
assetStatus === "ingesting" ||
assetStatus === "processing" ||
assetStatus === "pending"
) {
return { status: "info", label: "处理中", loading: true }
}
// 失败状态
if (assetStatus === "error" || assetStatus === "failed") {
return { status: "bad", label: "处理失败" }
}
// 素材已就绪(status=ready)时,不应因 classification 未执行而显示"处理中"
if (assetStatus === "ready") {
if (score == null) return { status: "info", label: "待诊断" }
if (score >= 70) return { status: "ok", label: "合格" }
if (score >= 40) return { status: "warn", label: "待优化" }
return { status: "bad", label: "不合格" }
}
// 素材未就绪:classification 正在处理中
if (classificationStatus === "processing" || classificationStatus === "pending") {
return { status: "info", label: "处理中", loading: true }
}
if (score == null) return { status: "info", label: "待诊断" }
if (score >= 70) return { status: "ok", label: "合格" }
if (score >= 40) return { status: "warn", label: "待优化" }
return { status: "bad", label: "不合格" }
}
/** 将后端 AssetLibraryItem 映射为前端 LibraryItem */
export const mapLibrary = (item: AssetLibraryItem): LibraryItem => ({
id: item.id,
name: item.name,
kind: item.kind || inferKind("video"),
count: item.asset_count ?? 0,
})
/** 将后端 ApiAssetItem 映射为前端 AssetItem */
export const mapAsset = (item: ApiAssetItem): AssetItem => {
const { status, label, loading } = inferStatus(
item.quality_score ?? undefined,
item.classification_status ?? undefined,
item.status ?? undefined,
)
const metadata = item.metadata || {}
const kind = inferKind(item.mime_type || "")
return {
id: item.id,
name: item.name,
kind,
// 视频类型不能用 file_url 做缩略图(是视频文件,<img> 无法渲染)
// 处理中的素材没有缩略图,显示占位符
thumbUrl: loading
? undefined
: (item.thumbnail_url as string | undefined) ||
(metadata.thumbnail_url as string | undefined) ||
(kind !== "video" ? (item.file_url as string | undefined) : undefined),
fileUrl: (item.file_url as string | undefined) || (metadata.file_url as string | undefined),
status,
statusLabel: label,
loading,
duration: metadata.duration != null ? formatDuration(metadata.duration as number) : undefined,
size: item.file_size ? +(item.file_size / (1024 * 1024)).toFixed(1) : 0,
createdAt: item.created_at ? new Date(item.created_at).toISOString().slice(0, 10) : "—",
}
}
+19
View File
@@ -0,0 +1,19 @@
/**
* 素材相关工具函数
*/
import type { AssetKind } from "../types"
import { KIND_LABELS } from "../constants"
export const kindLabel = (kind: AssetKind): string => KIND_LABELS[kind] ?? kind
/** 根据素材类型返回渐变背景 */
export const thumbGradient = (kind: AssetKind): string => {
switch (kind) {
case "video":
return "linear-gradient(135deg, #312e81 0%, #4f46e5 50%, #6366f1 100%)"
case "image":
return "linear-gradient(135deg, #78350f 0%, #d97706 50%, #f59e0b 100%)"
case "voice":
return "linear-gradient(135deg, #064e3b 0%, #059669 50%, #10b981 100%)"
}
}
+22
View File
@@ -0,0 +1,22 @@
/**
* 格式化工具函数
*/
export const formatDuration = (seconds: number): string => {
const m = Math.floor(seconds / 60)
const s = Math.floor(seconds % 60)
return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`
}
export const formatFileSize = (bytes: number): string => {
if (bytes < 1024) return `${bytes} B`
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
}
export const formatDate = (iso: string): string =>
new Date(iso).toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})
View File
+7 -1
View File
@@ -39,7 +39,13 @@ const WechatCallback: React.FC = () => {
const result = await wechatCallback(code, state)
// 获取用户信息
// 先把 token 存到 localStorage,让请求拦截器能拿到(getCurrentUser 需要带 token)
localStorage.setItem("access_token", result.access_token)
if (result.refresh_token) {
localStorage.setItem("refresh_token", result.refresh_token)
}
// 获取用户信息(这时候请求拦截器能拿到 token 了)
const userData = await getCurrentUser()
const user: User = normalizeUser(userData)
setAuth(user, result.access_token, result.refresh_token)
View File
@@ -11,7 +11,7 @@ import type {
TemplateCategory,
TemplateMode,
SaveTemplatePayload,
} from "@/api/editingPlanner"
} from "@/api/editing-planner"
import {
getEditingTemplates,
getEditingTemplate,
@@ -19,9 +19,9 @@ import {
updateEditingTemplate,
getTemplateCategories,
MODE_LABELS,
} from "@/api/editingPlanner"
import type { MediaAsset, TransitionEffect, TitleConfig } from "@/api/templateEditor"
import { getMediaAssets, getEditPlan, getEditPlanClips } from "@/api/templateEditor"
} from "@/api/editing-planner"
import type { MediaAsset, TransitionEffect, TitleConfig } from "@/api/template-editor"
import { getMediaAssets, getEditPlan, getEditPlanClips } from "@/api/template-editor"
import { useUndoRedo } from "./hooks/useUndoRedo"
import type {
ClipData,
@@ -4,9 +4,9 @@
*/
import React, { useRef, useState, useCallback } from "react"
import { useNavigate } from "react-router-dom"
import type { TemplateMode } from "@/api/editingPlanner"
import type { TemplateMode } from "@/api/editing-planner"
import type { ClipData, ClipType } from "../types"
import { TRANSITION_OPTIONS } from "@/api/templateEditor"
import { TRANSITION_OPTIONS } from "@/api/template-editor"
import type { AssetItem } from "@/api/assets"
interface SubtitleSettings {
View File
@@ -4,8 +4,8 @@
*/
import React from "react"
import { CloseOutlined, InboxOutlined } from "@ant-design/icons"
import type { EditPlanGeneration } from "@/api/templateEditor"
import { PLAN_STATUS_LABELS } from "@/api/templateEditor"
import type { EditPlanGeneration } from "@/api/template-editor"
import { PLAN_STATUS_LABELS } from "@/api/template-editor"
interface GenerationHistoryModalProps {
open: boolean
@@ -7,7 +7,7 @@ import React, { useCallback } from "react"
import { Drawer } from "antd"
import type { IntroOutroConfig, IntroOutroItem, IntroOutroKind, TransitionType } from "../types"
import { DEFAULT_INTRO_OUTRO } from "../types"
import { TRANSITION_OPTIONS } from "@/api/templateEditor"
import { TRANSITION_OPTIONS } from "@/api/template-editor"
/* ──────────── 常量 ──────────── */
@@ -3,10 +3,10 @@
* Tab 切换:模板列表 + 素材库
*/
import React, { useState } from "react"
import type { EditingTemplate } from "@/api/editingPlanner"
import { MODE_LABELS } from "@/api/editingPlanner"
import type { MediaAsset } from "@/api/templateEditor"
import AssetSelector from "@/components/AssetSelector/AssetSelector"
import type { EditingTemplate } from "@/api/editing-planner"
import { MODE_LABELS } from "@/api/editing-planner"
import type { MediaAsset } from "@/api/template-editor"
import AssetSelector from "@/components/asset-selector/AssetSelector"
interface MediaPanelProps {
templates: EditingTemplate[]
View File
@@ -5,7 +5,7 @@
*/
import React from "react"
import type { ClipData, ClipType } from "../types"
import type { TitleConfig } from "@/api/templateEditor"
import type { TitleConfig } from "@/api/template-editor"
import type { CoverConfig } from "../types"
interface SubtitleSettings {
@@ -4,7 +4,7 @@
*/
import React from "react"
import { Modal, Input, Select } from "@/components/ui"
import type { TemplateCategory } from "@/api/editingPlanner"
import type { TemplateCategory } from "@/api/editing-planner"
interface SaveModalProps {
open: boolean
View File
@@ -10,7 +10,7 @@
*/
import React, { useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo } from "react"
import type { ClipData, ClipType, TrimConfig } from "../types"
import { TRANSITION_OPTIONS } from "@/api/templateEditor"
import { TRANSITION_OPTIONS } from "@/api/template-editor"
interface TimelinePanelProps {
clips: ClipData[]
@@ -5,7 +5,7 @@
*/
import React, { useCallback } from "react"
import { Drawer, Slider } from "antd"
import { TRANSITION_OPTIONS } from "@/api/templateEditor"
import { TRANSITION_OPTIONS } from "@/api/template-editor"
import type { TransitionConfig, TransitionType } from "../types"
import { DEFAULT_TRANSITION } from "../types"
@@ -18,7 +18,7 @@ import type {
CreateEditPlanClipRequest,
UpdateEditPlanClipRequest,
ClipReorderItem,
} from "@/api/templateEditor"
} from "@/api/template-editor"
import {
getEditPlanClips,
createEditPlanClip,
@@ -27,7 +27,7 @@ import {
reorderEditPlanClips,
batchDeleteEditPlanClips,
createClipsFromAssets,
} from "@/api/templateEditor"
} from "@/api/template-editor"
import { useUndoRedo } from "./useUndoRedo"
const QUERY_KEY = "editPlanClips"
View File
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,40 @@
/**
* 智能剪辑页头组件
*/
import React from "react"
import { ThunderboltOutlined } from "@ant-design/icons"
interface GenerateHeaderProps {
/** 是否来自模板草稿(URL 带 edit_plan_id) */
fromEditPlan?: boolean
}
const GenerateHeader: React.FC<GenerateHeaderProps> = ({ fromEditPlan }) => {
return (
<div className="xx-generate-head">
<div>
<h2>
<ThunderboltOutlined style={{ marginRight: 8 }} />
智能剪辑
</h2>
<p>快速生成短视频,支持多种风格和素材组合</p>
</div>
{fromEditPlan && (
<span
style={{
background: "#dbeafe",
color: "#1d4ed8",
fontSize: 12,
padding: "4px 10px",
borderRadius: 999,
fontWeight: 600,
}}
>
🎬 来自模板草稿
</span>
)}
</div>
)
}
export default GenerateHeader
@@ -0,0 +1,187 @@
/**
* 智能剪辑右侧生成结果面板
*/
import React from "react"
import { Typography } from "antd"
import {
PlayCircleOutlined,
CloseCircleOutlined,
DownloadOutlined,
ShareAltOutlined,
} from "@ant-design/icons"
import type { GeneratedVideo } from "@/api/template-editor"
import { formatDuration } from "@/api/voice-clone"
const { Text } = Typography
interface GenerateResultPanelProps {
/** 是否已生成完成 */
generated: boolean
/** 是否正在生成中 */
generating: boolean
/** 生成进度(0-100) */
progress: number
/** 生成错误信息 */
generateError: string | null
/** 生成的视频列表 */
generatedVideos: GeneratedVideo[]
/** 点击视频卡片预览回调 */
onVideoPreview: (video: GeneratedVideo) => void
/** 下载回调 */
onDownload: () => void
/** 分享回调 */
onShare: () => void
/** 前往成片库回调 */
onGoToLibrary: () => void
}
const GenerateResultPanel: React.FC<GenerateResultPanelProps> = ({
generated,
generating,
progress,
generateError,
generatedVideos,
onVideoPreview,
onDownload,
onShare,
onGoToLibrary,
}) => {
return (
<div className="xx-generate-result">
<div className="xx-result-header">
<h3>生成结果</h3>
{generated && generatedVideos.length > 0 && (
<span className="xx-result-count">{generatedVideos.length} 个视频</span>
)}
</div>
{/* 生成中进度 */}
{generating && (
<div className="xx-result-progress">
<div className="xx-progress-circle">
<svg viewBox="0 0 80 80">
<circle
cx="40"
cy="40"
r="36"
fill="none"
stroke="var(--border-color)"
strokeWidth="6"
/>
<circle
cx="40"
cy="40"
r="36"
fill="none"
stroke="var(--primary-color)"
strokeWidth="6"
strokeDasharray={`${Math.round(progress) * 2.26} 226`}
strokeLinecap="round"
transform="rotate(-90 40 40)"
/>
</svg>
<span className="xx-progress-percent">{Math.round(progress)}%</span>
</div>
<div className="xx-progress-text">
<Text strong style={{ fontSize: 14, display: "block", marginBottom: 4 }}>
正在生成视频
</Text>
<Text style={{ fontSize: 12, color: "var(--text-secondary)" }}>
AI 正在处理素材,请稍候…
</Text>
</div>
</div>
)}
{/* 生成失败 */}
{generateError && !generating && (
<div className="xx-result-empty">
<CloseCircleOutlined style={{ fontSize: 40, color: "#ff4d4f", marginBottom: 12 }} />
<Text strong style={{ display: "block", marginBottom: 4 }}>
生成失败
</Text>
<Text style={{ fontSize: 12, color: "var(--text-secondary)" }}>
{typeof generateError === "string" ? generateError : "请重试"}
</Text>
</div>
)}
{/* 空状态 */}
{!generated && !generating && !generateError && (
<div className="xx-result-empty">
<PlayCircleOutlined
style={{ fontSize: 48, color: "var(--text-tertiary)", marginBottom: 12 }}
/>
<Text style={{ color: "var(--text-secondary)", fontSize: 13 }}>
完成配置后点击「确认生成」
</Text>
<Text style={{ color: "var(--text-tertiary)", fontSize: 12, marginTop: 4 }}>
生成的视频将在这里展示
</Text>
</div>
)}
{/* 生成结果卡片列表 */}
{generated && generatedVideos.length > 0 && (
<div className="xx-video-grid">
{generatedVideos.map((video, idx) => (
<div
key={video.id || idx}
className="xx-video-card"
onClick={() => onVideoPreview(video)}
>
<div className="xx-video-thumb">
{video.thumbnail_url ? (
<img src={video.thumbnail_url} alt="" />
) : (
<div className="xx-video-thumb-placeholder">
<PlayCircleOutlined style={{ fontSize: 32, opacity: 0.5 }} />
</div>
)}
<div className="xx-video-play-overlay">
<PlayCircleOutlined style={{ fontSize: 36, color: "#fff" }} />
</div>
{video.duration && (
<span className="xx-video-duration">{formatDuration(video.duration)}</span>
)}
</div>
<div className="xx-video-info">
<div className="xx-video-title">视频 {idx + 1}</div>
<div className="xx-video-actions">
<button
className="xx-video-action-btn"
onClick={(e) => {
e.stopPropagation()
onDownload()
}}
>
<DownloadOutlined />
</button>
<button
className="xx-video-action-btn"
onClick={(e) => {
e.stopPropagation()
onShare()
}}
>
<ShareAltOutlined />
</button>
</div>
</div>
</div>
))}
</div>
)}
{generated && (
<div className="xx-result-footer">
<button className="xx-btn xx-btn-ghost xx-btn-block" onClick={onGoToLibrary}>
前往成片库 →
</button>
</div>
)}
</div>
)
}
export default GenerateResultPanel
@@ -0,0 +1,47 @@
/**
* 智能剪辑步骤条组件
*/
import React from "react"
import { STEPS } from "../constants"
interface GenerateStepsBarProps {
/** 当前步骤(1-based) */
currentStep: number
/** 点击已完成步骤的回调(用于回退) */
onStepClick?: (step: number) => void
}
const GenerateStepsBar: React.FC<GenerateStepsBarProps> = ({ currentStep, onStepClick }) => {
return (
<div className="xx-steps-bar">
{STEPS.map((step, idx) => {
const isActive = currentStep === step.key
const isDone = currentStep > step.key
const cls = ["xx-step-item", isActive ? "active" : "", isDone ? "done" : ""]
.filter(Boolean)
.join(" ")
return (
<React.Fragment key={step.key}>
{idx > 0 && <span className="xx-step-arrow">→</span>}
<div
className={cls}
onClick={() => {
// 允许点击已完成的步骤回退
if (isDone && onStepClick) {
onStepClick(step.key)
}
}}
role="button"
tabIndex={0}
>
<div className="xx-step-num">{isDone ? "✓" : step.key}</div>
<span className="xx-step-label">{step.label}</span>
</div>
</React.Fragment>
)
})}
</div>
)
}
export default GenerateStepsBar
@@ -0,0 +1,86 @@
/**
* Step 1 模板选择组件
*/
import React from "react"
import type { EditingTemplate } from "@/api/editing-planner"
import { MODE_GRADIENTS } from "../constants"
import { useStep1Template } from "../hooks/useStep1Template"
interface Step1TemplateSelectProps {
templates: EditingTemplate[]
selectedTemplate: string
onSelectTemplate: (id: string) => void
}
const Step1TemplateSelect: React.FC<Step1TemplateSelectProps> = (props) => {
const { templates, selectedTemplate, handleSelect, handleKeySelect } = useStep1Template(props)
return (
<div className="xx-form-section">
<h3>🎨 选择模板</h3>
{templates.length === 0 ? (
<div className="xx-empty-state">
<p>暂无可用模板</p>
<p style={{ fontSize: 13, color: "var(--text-tertiary)" }}>
请先在「模板编辑器」中创建模板
</p>
</div>
) : (
<div className="xx-choice-list">
{templates.map((tpl) => (
<div
key={tpl.id}
className={`xx-choice-item ${selectedTemplate === tpl.id ? "selected" : ""}`}
onClick={() => handleSelect(tpl.id)}
role="button"
tabIndex={0}
aria-pressed={selectedTemplate === tpl.id}
onKeyDown={(e) => handleKeySelect(e, tpl.id)}
>
<span className="xx-choice-check">✓</span>
<div
className="xx-choice-thumb"
style={{
background: MODE_GRADIENTS[tpl.mode] || MODE_GRADIENTS.pip,
}}
>
🎬
</div>
<h4>{tpl.name}</h4>
<p>
{tpl.estimated_duration}s · {tpl.segments.length}片段
</p>
{tpl.tags.length > 0 && (
<div
style={{
display: "flex",
gap: 4,
flexWrap: "wrap",
marginTop: 4,
}}
>
{tpl.tags.map((tag) => (
<span
key={tag}
style={{
fontSize: 11,
padding: "1px 6px",
borderRadius: 6,
background: "var(--bg-secondary)",
color: "var(--text-secondary)",
}}
>
{tag}
</span>
))}
</div>
)}
</div>
))}
</div>
)}
</div>
)
}
export default Step1TemplateSelect
@@ -0,0 +1,95 @@
/**
* Step 2 素材选择组件
*/
import React from "react"
import { useStep2Materials } from "../hooks/useStep2Materials"
import MaterialModeTabs from "./material/MaterialModeTabs"
import ManualMaterialList from "./material/ManualMaterialList"
import SmartMatchInput from "./material/SmartMatchInput"
import SmartMatchResults from "./material/SmartMatchResults"
interface Step2MaterialSelectProps {
materialMode: "manual" | "auto"
onMaterialModeChange: (mode: "manual" | "auto") => void
selectedMaterials: string[]
onSelectedMaterialsChange: (ids: string[]) => void
smartSelectedIds: string[]
onSmartSelectedIdsChange: (ids: string[]) => void
}
const Step2MaterialSelect: React.FC<Step2MaterialSelectProps> = (props) => {
const m = useStep2Materials(props)
return (
<div className="xx-form-section">
<h3>📦 选择素材</h3>
<MaterialModeTabs mode={m.materialMode} onModeChange={m.onMaterialModeChange} />
<div className="xx-form-field" style={{ marginTop: 12 }}>
<label>选择视频库</label>
<select
value={m.selectedLibraryId}
onChange={(e) => m.setSelectedLibraryId(e.target.value)}
>
{m.libraries.map((lib) => (
<option key={lib.id} value={lib.id}>
{lib.name}
</option>
))}
</select>
</div>
{m.materialMode === "manual" && (
<>
<div
style={{
marginTop: 14,
display: "flex",
alignItems: "center",
gap: 10,
}}
>
<span className="xx-pill xx-pill-ok">已选 {m.selectedMaterials.length} 个素材</span>
</div>
<ManualMaterialList
materials={m.materials}
materialsLoading={m.materialsLoading}
selectedMaterials={m.selectedMaterials}
onToggle={m.handleToggleMaterial}
/>
</>
)}
{m.materialMode === "auto" && (
<div className="xx-smart-match-section">
<SmartMatchInput
inputValue={m.smartMatchInput}
onInputChange={m.setSmartMatchInput}
matching={m.smartMatching}
onMatch={m.handleSmartMatch}
hasMatched={m.hasMatched}
onRefresh={m.handleRefreshMatch}
materialsCount={m.materials.items.length}
loading={m.materialsLoading}
/>
<SmartMatchResults
results={m.smartMatchedResults}
selectedIds={m.smartSelectedIds}
matching={m.smartMatching}
hasMatched={m.hasMatched}
onToggle={m.handleToggleSmartSelect}
onSelectAll={m.handleSelectAllMatched}
onClear={m.handleClearSmartSelect}
formatDuration={m.formatDuration}
selectedTotalDuration={m.smartSelectedTotalDuration}
/>
</div>
)}
</div>
)
}
export default Step2MaterialSelect
@@ -0,0 +1,57 @@
/**
* Step 3 生成预览组件
*/
import React from "react"
import { CheckCircleFilled } from "@ant-design/icons"
import type { EditingTemplate } from "@/api/editing-planner"
import { useStep3Preview } from "../hooks/useStep3Preview"
interface Step3GeneratePreviewProps {
templates: EditingTemplate[]
selectedTemplate: string
materialMode: "manual" | "auto"
selectedMaterials: string[]
smartSelectedIds: string[]
duration: number
videoRatio: string
}
const Step3GeneratePreview: React.FC<Step3GeneratePreviewProps> = (props) => {
const { templateName, materialCount, duration, videoRatio } = useStep3Preview(props)
return (
<div className="xx-form-section">
<h3>🎬 生成预览</h3>
<div className="xx-preview-tip">
<CheckCircleFilled style={{ color: "#52c41a", marginRight: 8 }} />
<span>素材已选好,AI 将为您智能匹配剪辑方案</span>
</div>
<div className="xx-preview-plan-card">
<div className="xx-preview-plan-title">模板草稿预览</div>
<div className="xx-preview-plan-info">
<div className="xx-preview-plan-row">
<span className="xx-preview-plan-label">模板</span>
<span className="xx-preview-plan-value">{templateName}</span>
</div>
<div className="xx-preview-plan-row">
<span className="xx-preview-plan-label">素材数量</span>
<span className="xx-preview-plan-value">{materialCount}</span>
</div>
<div className="xx-preview-plan-row">
<span className="xx-preview-plan-label">预计时长</span>
<span className="xx-preview-plan-value">{duration} 秒</span>
</div>
<div className="xx-preview-plan-row">
<span className="xx-preview-plan-label">视频比例</span>
<span className="xx-preview-plan-value">{videoRatio}</span>
</div>
</div>
<div className="xx-preview-plan-hint">
💡 点击「下一步」进入标题设置,AI 将根据素材内容为您推荐标题
</div>
</div>
</div>
)
}
export default Step3GeneratePreview
@@ -0,0 +1,108 @@
/**
* Step 4 标题设置组件
*/
import React from "react"
import { Select } from "antd"
import { POSITION_OPTIONS, FONT_OPTIONS } from "../constants"
import type { TitleSettings } from "../types"
import { useStep4Title } from "../hooks/useStep4Title"
import AiTitleGenerator from "./title/AiTitleGenerator"
import TitleStylePanel from "./title/TitleStylePanel"
interface Step4TitleSettingsProps {
titleSettings: TitleSettings
onTitleSettingsChange: (settings: TitleSettings) => void
}
const Step4TitleSettings: React.FC<Step4TitleSettingsProps> = (props) => {
const t = useStep4Title(props)
return (
<div className="xx-form-section">
<h3>📝 选择标题</h3>
<AiTitleGenerator
inputValue={t.aiTitleInput}
onInputChange={t.setAiTitleInput}
generating={t.aiTitleGenerating}
onGenerate={t.handleGenerateAiTitles}
results={t.aiTitleResults}
hasGenerated={t.hasGeneratedTitles}
onSelect={t.handleSelectAiTitle}
selectedTitle={t.titleSettings.title}
onRefresh={t.handleRefreshAiTitles}
/>
<div className="xx-divider">
<span>或手动选择</span>
</div>
<div className="xx-title-ai-toggle">
<span className="xx-toggle-label">AI 自动选择标题</span>
<div
className={`xx-switch ${t.titleSettings.aiAutoSelect ? "active" : ""}`}
onClick={t.toggleAiAutoSelect}
>
<div className="xx-switch-knob" />
</div>
</div>
{!t.titleSettings.aiAutoSelect && (
<>
<div className="xx-form-field">
<label>从标题库选择</label>
<Select
placeholder="请选择标题…"
allowClear
showSearch
style={{ width: "100%" }}
value={t.titleSettings.title || undefined}
onChange={(val) => t.updateTitle(val || "")}
options={t.userTitles.map((ut) => ({
label: ut.content,
value: ut.content,
}))}
filterOption={(input, option) =>
((option?.label as string) || "").toLowerCase().includes(input.toLowerCase())
}
notFoundContent={
t.userTitles.length === 0 ? (
<span style={{ color: "var(--text-tertiary)", fontSize: 13 }}>
标题库为空,请前往「标题管理」添加
</span>
) : null
}
/>
</div>
<div className="xx-form-field" style={{ marginTop: 14 }}>
<label>或手动输入</label>
<input
placeholder="输入自定义标题…"
value={t.titleSettings.title}
onChange={(e) => t.updateTitle(e.target.value)}
maxLength={50}
/>
</div>
<TitleStylePanel
settings={t.titleSettings}
onUpdatePosition={t.updatePosition}
onUpdateFont={t.updateFont}
onUpdateSize={t.updateSize}
onToggleBold={t.toggleBold}
onToggleItalic={t.toggleItalic}
onToggleStroke={t.toggleStroke}
onToggleShadow={t.toggleShadow}
onApplyPreset={t.applyPreset}
activePreset={t.activePreset}
titlePresets={t.titlePresets}
POSITION_OPTIONS={POSITION_OPTIONS}
FONT_OPTIONS={FONT_OPTIONS}
/>
</>
)}
</div>
)
}
export default Step4TitleSettings
@@ -0,0 +1,137 @@
/**
* Step 5 配音选择组件
*/
import React from "react"
import type { VoiceClone } from "@/api/voice-clone"
import { useStep5Voice } from "../hooks/useStep5Voice"
import VoiceRecommendSection from "./voice/VoiceRecommendSection"
import VoiceChoiceCard from "./voice/VoiceChoiceCard"
import PresetVoiceDetail from "./voice/PresetVoiceDetail"
import CustomVoicePanel from "./voice/CustomVoicePanel"
import SaveVoiceModal from "./voice/SaveVoiceModal"
import CloneVoiceSection from "./voice/CloneVoiceSection"
interface Step5VoiceSelectProps {
selectedVoice: string
onSelectedVoiceChange: (voiceId: string) => void
voiceMode: "preset" | "custom" | "clone"
onVoiceModeChange: (mode: "preset" | "custom" | "clone") => void
selectedClonedVoice: string
onSelectedClonedVoiceChange: (voiceId: string) => void
clonedVoices: VoiceClone[]
addClone: (voice: VoiceClone) => void
hasProcessing: boolean
cloneModalOpen: boolean
onCloneModalOpenChange: (open: boolean) => void
titleText: string
}
const Step5VoiceSelect: React.FC<Step5VoiceSelectProps> = (props) => {
const v = useStep5Voice(props)
return (
<div className="xx-form-section">
<h3>🎙️ 选择配音</h3>
<VoiceRecommendSection
presetVoices={v.presetVoices}
voiceRecommendLoading={v.voiceRecommendLoading}
voiceRecommendations={v.voiceRecommendations}
hasVoiceRecommend={v.hasVoiceRecommend}
onRecommend={v.handleVoiceRecommend}
onSelectVoice={v.handleSelectRecommendedVoice}
selectedVoiceId={v.selectedVoice}
voiceMode={v.voiceMode}
VOICE_GENDER_ICON={v.VOICE_GENDER_ICON}
/>
<div className="xx-divider">
<span>全部音色</span>
</div>
<div className="xx-voice-choice-list" style={{ marginBottom: 16 }}>
{v.presetVoices.slice(0, 3).map((pv) => (
<VoiceChoiceCard
key={pv.voice_id}
selected={v.voiceMode === "preset" && v.selectedVoice === pv.voice_id}
onClick={() => v.handleSelectPresetVoice(pv.voice_id)}
avatar={v.VOICE_GENDER_ICON[pv.gender] ?? "✨"}
title={pv.name}
description={pv.description}
/>
))}
{v.presetVoicesLoading && (
<VoiceChoiceCard
selected={false}
onClick={() => {}}
avatar="⏳"
title="加载中…"
loading
/>
)}
<VoiceChoiceCard
selected={v.voiceMode === "clone"}
onClick={v.handleSelectCloneVoice}
avatar="🎤"
title="克隆我的声音"
description="上传语音样本克隆"
avatarStyle={{ background: "linear-gradient(135deg, #10b981, #059669)" }}
/>
</div>
{v.voiceMode === "preset" && (
<PresetVoiceDetail
presetVoices={v.presetVoices}
selectedVoice={v.selectedVoice}
onSelect={v.handleSelectPresetVoice}
playingVoice={v.playingVoice}
onTogglePlay={v.toggleVoicePlay}
presetVoicesLoading={v.presetVoicesLoading}
/>
)}
{v.voiceMode === "custom" && (
<>
<CustomVoicePanel
customVoiceText={v.customVoiceText}
onTextChange={v.setCustomVoiceText}
synthesizePending={v.synthesizeMutation.isPending}
onSynthesize={v.handleSynthesizeVoice}
ttsError={v.ttsError}
customAudioUrl={v.customAudioUrl}
completedTtsJobId={v.completedTtsJobId}
onOpenSaveModal={v.handleOpenSaveModal}
/>
<SaveVoiceModal
open={v.saveModalOpen}
onClose={() => v.setSaveModalOpen(false)}
saveName={v.saveName}
onNameChange={v.setSaveName}
saveTagIds={v.saveTagIds}
onTagIdsChange={v.setSaveTagIds}
saveNewTag={v.saveNewTag}
onNewTagChange={v.setSaveNewTag}
onAddTag={v.handleAddTagInModal}
allTags={v.allTags}
savePending={v.saveToLibraryMutation.isPending}
onConfirm={v.handleConfirmSave}
/>
</>
)}
{v.voiceMode === "clone" && (
<CloneVoiceSection
clonedVoices={v.clonedVoices}
hasProcessing={v.hasProcessing}
selectedClonedVoice={v.selectedClonedVoice}
onSelect={v.handleSelectClonedVoice}
onOpenCloneModal={v.handleOpenCloneModal}
CLONE_STATUS_CONFIG={v.CLONE_STATUS_CONFIG}
formatDuration={v.formatDuration}
/>
)}
</div>
)
}
export default Step5VoiceSelect
@@ -0,0 +1,187 @@
/**
* Step 6 封面设置组件
*/
import React from "react"
import type { CoverConfig } from "../../editing-planner/types"
import { useStep6Cover } from "../hooks/useStep6Cover"
interface Step6CoverSettingsProps {
coverSettings: CoverConfig
onCoverSettingsChange: (settings: CoverConfig) => void
duration: number
}
const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
const {
coverSettings,
formatTime,
toggleEnabled,
setMode,
setFrameTime,
handleUpload,
totalDuration,
COVER_MODE_LABELS,
COVER_MODE_ICONS,
} = useStep6Cover(props)
return (
<div className="xx-form-section">
<h3>🖼️ 选择封面</h3>
{/* 启用开关 */}
<div className="xx-cover-header">
<span className="xx-cover-header-label">启用自定义封面</span>
<label className="xx-switch">
<input
type="checkbox"
checked={coverSettings.enabled}
onChange={(e) => toggleEnabled(e.target.checked)}
/>
<span className="xx-switch-slider" />
</label>
</div>
{coverSettings.enabled && (
<>
{/* 模式选择 */}
<div className="xx-section-title">封面来源</div>
<div className="xx-cover-mode-tabs">
{(["auto", "frame", "upload"] as const).map((m) => (
<button
key={m}
className={`xx-cover-mode-tab${coverSettings.mode === m ? " active" : ""}`}
onClick={() => setMode(m)}
>
<span className="xx-cover-mode-icon">{COVER_MODE_ICONS[m]}</span>
<span className="xx-cover-mode-label">{COVER_MODE_LABELS[m]}</span>
</button>
))}
</div>
{/* 智能封面 */}
{coverSettings.mode === "auto" && (
<div className="xx-cover-auto">
<div className="xx-cover-auto-desc">
AI 将分析视频内容,自动选择最具吸引力的画面作为封面。
</div>
<div className="xx-cover-auto-badge">
<span style={{ fontSize: 24 }}>🤖</span>
<span>AI 智能选帧</span>
</div>
</div>
)}
{/* 抽帧选封面 */}
{coverSettings.mode === "frame" && (
<div className="xx-cover-frame">
<div className="xx-cover-frame-preview">
<div className="xx-cover-frame-placeholder">
<span className="xx-cover-frame-icon">🎞️</span>
<span className="xx-cover-frame-time">
{formatTime(coverSettings.frame_time)}
</span>
</div>
</div>
<div className="xx-cover-frame-slider">
<div className="xx-cover-frame-slider-header">
<span>拖动选择封面帧</span>
<span className="xx-cover-frame-value">
{formatTime(coverSettings.frame_time)}
</span>
</div>
<input
type="range"
min={0}
max={Math.max(totalDuration, 1)}
step={0.1}
value={coverSettings.frame_time}
onChange={(e) => setFrameTime(Number(e.target.value))}
className="xx-cover-range"
/>
<div className="xx-cover-frame-range">
<span>00:00</span>
<span>{formatTime(totalDuration)}</span>
</div>
</div>
<div className="xx-cover-frame-quick">
<span className="xx-cover-quick-label">快捷选帧:</span>
{[0, 0.25, 0.5, 0.75].map((ratio) => {
const t = totalDuration * ratio
return (
<button
key={ratio}
className="xx-cover-quick-btn"
onClick={() => setFrameTime(t)}
>
{formatTime(t)}
</button>
)
})}
</div>
</div>
)}
{/* 上传封面 */}
{coverSettings.mode === "upload" && (
<div className="xx-cover-upload">
<div
className="xx-cover-upload-area"
onClick={() => {
const input = document.getElementById("cover-upload-input")
input?.click()
}}
>
{coverSettings.upload_url ? (
<div className="xx-cover-upload-preview">
<img src={coverSettings.upload_url} alt="封面预览" />
<div className="xx-cover-upload-overlay">点击更换</div>
</div>
) : (
<div className="xx-cover-upload-placeholder">
<span style={{ fontSize: 32 }}>📤</span>
<span className="xx-cover-upload-text">点击上传封面图片</span>
<span className="xx-cover-upload-hint">支持 JPG / PNG,建议 16:9 比例</span>
</div>
)}
<input
id="cover-upload-input"
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={(e) => {
const file = e.target.files?.[0]
if (file) {
handleUpload(file)
}
}}
/>
</div>
</div>
)}
{/* 封面预览 */}
<div className="xx-section-title">封面预览</div>
<div className="xx-cover-preview-box">
{coverSettings.upload_url ? (
<img src={coverSettings.upload_url} alt="封面预览" className="xx-cover-preview-img" />
) : (
<div className="xx-cover-preview-placeholder">
<span style={{ fontSize: 28 }}>🖼️</span>
<span>
{coverSettings.mode === "auto"
? "AI 智能选择"
: coverSettings.mode === "frame"
? `帧 ${formatTime(coverSettings.frame_time)}`
: "未上传封面"}
</span>
</div>
)}
<div className="xx-cover-preview-ratio">16:9</div>
</div>
</>
)}
</div>
)
}
export default Step6CoverSettings
@@ -0,0 +1,194 @@
/**
* Step 7 确认生成组件
*/
import React from "react"
import {
LoadingOutlined,
CheckCircleFilled,
CloseCircleOutlined,
MinusOutlined,
PlusOutlined,
} from "@ant-design/icons"
import type { EditingTemplate } from "@/api/editing-planner"
import type { GeneratedVideo } from "@/api/template-editor"
import type { CoverConfig } from "../../editing-planner/types"
import type { VoiceClone } from "@/api/voice-clone"
import type { PresetVoiceItem } from "@/api/voices"
import { useStep7Generate } from "../hooks/useStep7Generate"
interface Step7ConfirmGenerateProps {
templates: EditingTemplate[]
selectedTemplate: string
materialMode: "manual" | "auto"
selectedMaterials: string[]
smartSelectedIds: string[]
title: string
voiceMode: "preset" | "custom" | "clone"
selectedVoice: string
selectedClonedVoice: string
presetVoices: PresetVoiceItem[]
clonedVoices: VoiceClone[]
coverSettings: CoverConfig
generateCount: number
onGenerateCountChange: (count: number) => void
generating: boolean
generated: boolean
generateError: string | null
progress: number
generatedVideos: GeneratedVideo[]
onRetry: () => void
onDismissError: () => void
}
const Step7ConfirmGenerate: React.FC<Step7ConfirmGenerateProps> = (props) => {
const {
templateName,
materialSummary,
title,
voiceName,
coverSummary,
generateCount,
handleDecrement,
handleIncrement,
generating,
generated,
generateError,
progress,
generatedVideos,
getGenerationPhase,
handleScrollToPreview,
} = useStep7Generate(props)
const { onRetry, onDismissError } = props
return (
<div className="xx-form-section">
<h3>✨ 确认生成</h3>
<div className="xx-summary-card">
<div className="xx-summary-row">
<span className="xx-summary-label">模板</span>
<span className="xx-summary-value">{templateName}</span>
</div>
<div className="xx-summary-row">
<span className="xx-summary-label">素材</span>
<span className="xx-summary-value">{materialSummary}</span>
</div>
<div className="xx-summary-row">
<span className="xx-summary-label">标题</span>
<span className="xx-summary-value">{title || "未选择"}</span>
</div>
<div className="xx-summary-row">
<span className="xx-summary-label">配音</span>
<span className="xx-summary-value">{voiceName}</span>
</div>
<div className="xx-summary-row">
<span className="xx-summary-label">封面</span>
<span className="xx-summary-value">{coverSummary}</span>
</div>
<div className="xx-summary-row">
<span className="xx-summary-label">生成数量</span>
<span className="xx-summary-value">
<div className="xx-count-stepper">
<button
className="xx-count-stepper-btn"
disabled={generateCount <= 1 || generating}
onClick={handleDecrement}
>
<MinusOutlined />
</button>
<span className="xx-count-stepper-value">{generateCount}</span>
<button
className="xx-count-stepper-btn"
disabled={generateCount >= 10 || generating}
onClick={handleIncrement}
>
<PlusOutlined />
</button>
<span className="xx-count-stepper-hint">条视频</span>
</div>
</span>
</div>
</div>
{/* 生成进度 / 结果反馈 */}
{(generating || generated || generateError) && (
<div style={{ marginTop: 16 }}>
{generating && (
<div className="xx-gen-progress-card">
<div className="xx-gen-progress-header">
<div className="xx-gen-progress-icon">
<LoadingOutlined />
</div>
<div className="xx-gen-progress-info">
<div className="xx-gen-progress-phase">
{getGenerationPhase(progress).icon} {getGenerationPhase(progress).label}
</div>
<div className="xx-gen-progress-sub">预计还需 1-2 分钟,请稍候…</div>
</div>
<div className="xx-gen-progress-percent">{Math.round(progress)}%</div>
</div>
<div className="xx-gen-progress-bar">
<div
className="xx-gen-progress-bar-fill"
style={{ width: `${Math.min(Math.round(progress), 100)}%` }}
/>
</div>
<div className="xx-gen-progress-tip">
💡 生成过程中可以切换到其他页面操作,完成后会自动通知
</div>
</div>
)}
{generated && !generating && (
<div className="xx-gen-success-card">
<div className="xx-gen-success-icon">
<CheckCircleFilled style={{ fontSize: 32, color: "#52c41a" }} />
</div>
<div className="xx-gen-success-info">
<div className="xx-gen-success-title">视频生成完成!</div>
<div className="xx-gen-success-sub">
共生成 {generatedVideos.length} 条视频,可在右侧预览或前往成片库查看
</div>
</div>
<button
type="button"
className="xx-btn xx-btn-primary xx-btn-sm"
onClick={handleScrollToPreview}
>
查看结果
</button>
</div>
)}
{generateError && !generating && (
<div className="xx-gen-error-card">
<div className="xx-gen-error-icon">
<CloseCircleOutlined style={{ fontSize: 28, color: "#ef4444" }} />
</div>
<div className="xx-gen-error-info">
<div className="xx-gen-error-title">生成失败</div>
<div className="xx-gen-error-msg">
{typeof generateError === "string"
? generateError
: JSON.stringify(generateError)}
</div>
</div>
<div style={{ display: "flex", gap: 8 }}>
<button type="button" className="xx-btn xx-btn-primary xx-btn-sm" onClick={onRetry}>
🔄 重试
</button>
<button
type="button"
className="xx-btn xx-btn-ghost xx-btn-sm"
onClick={onDismissError}
>
知道了
</button>
</div>
</div>
)}
</div>
)}
</div>
)
}
export default Step7ConfirmGenerate
@@ -0,0 +1,84 @@
/**
* 手动选择素材列表
*/
import React from "react"
import { Typography } from "antd"
import type { AssetItem } from "@/api/assets"
const { Text } = Typography
interface ManualMaterialListProps {
materials: { items: AssetItem[]; total: number }
materialsLoading: boolean
selectedMaterials: string[]
onToggle: (materialId: string) => void
}
const ManualMaterialList: React.FC<ManualMaterialListProps> = ({
materials,
materialsLoading,
selectedMaterials,
onToggle,
}) => {
return (
<div style={{ marginTop: 14 }}>
{materialsLoading ? (
<Text style={{ color: "var(--text-secondary)", padding: "16px 0" }}>加载素材中…</Text>
) : materials.items.length === 0 ? (
<Text style={{ color: "var(--text-secondary)", padding: "16px 0" }}>
暂无素材,请先在视频库中上传
</Text>
) : (
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
{materials.items.map((m) => {
const checked = selectedMaterials.includes(m.id)
return (
<label
key={m.id}
style={{
display: "flex",
alignItems: "center",
gap: 10,
padding: "8px 12px",
background: checked ? "var(--primary-soft, #eef2ff)" : "#f8fafc",
borderRadius: 10,
cursor: "pointer",
border: checked
? "1px solid var(--primary-color, #4f46e5)"
: "1px solid transparent",
transition: "all 0.15s ease",
}}
>
<input
type="checkbox"
checked={checked}
onChange={() => onToggle(m.id)}
style={{ accentColor: "var(--primary-color, #4f46e5)" }}
/>
<span
style={{
fontSize: 13,
color: "var(--text-primary)",
flex: 1,
}}
>
{m.name}
</span>
<span
style={{
fontSize: 11,
color: "var(--text-tertiary, #94a3b8)",
}}
>
{m.mime_type.split("/")[1].toUpperCase()}
</span>
</label>
)
})}
</div>
)}
</div>
)
}
export default ManualMaterialList
@@ -0,0 +1,32 @@
/**
* 素材模式切换 Tab
*/
import React from "react"
interface MaterialModeTabsProps {
mode: "manual" | "auto"
onModeChange: (mode: "manual" | "auto") => void
}
const MaterialModeTabs: React.FC<MaterialModeTabsProps> = ({ mode, onModeChange }) => {
return (
<div className="xx-material-mode-tabs">
<button
className={`xx-material-mode-tab ${mode === "manual" ? "active" : ""}`}
onClick={() => onModeChange("manual")}
type="button"
>
手动选择素材
</button>
<button
className={`xx-material-mode-tab ${mode === "auto" ? "active" : ""}`}
onClick={() => onModeChange("auto")}
type="button"
>
选择视频库自动匹配
</button>
</div>
)
}
export default MaterialModeTabs
@@ -0,0 +1,60 @@
/**
* 单个智能匹配卡片
*/
import React from "react"
import { PlayCircleOutlined, CheckCircleFilled } from "@ant-design/icons"
import type { AssetItem } from "@/api/assets"
interface SmartMatchResultItem {
asset: AssetItem
matchScore: number
matchReason: string
}
interface SmartMatchCardProps {
result: SmartMatchResultItem
selected: boolean
onClick: () => void
formatDuration: (seconds: number) => string
}
const SmartMatchCard: React.FC<SmartMatchCardProps> = ({
result,
selected,
onClick,
formatDuration,
}) => {
const { asset, matchScore, matchReason } = result
return (
<div className={`xx-smart-match-card ${selected ? "selected" : ""}`} onClick={onClick}>
{/* 缩略图 */}
<div className="xx-smart-match-thumb">
{asset.thumbnail_url ? (
<img src={asset.thumbnail_url} alt={asset.name} />
) : (
<div className="xx-smart-match-thumb-placeholder">
<PlayCircleOutlined style={{ fontSize: 32, opacity: 0.5 }} />
</div>
)}
<div className="xx-smart-match-score">{matchScore}%</div>
{selected && (
<div className="xx-smart-match-check">
<CheckCircleFilled style={{ fontSize: 20, color: "#fff" }} />
</div>
)}
{asset.duration && (
<div className="xx-smart-match-duration">{formatDuration(asset.duration)}</div>
)}
</div>
{/* 信息区 */}
<div className="xx-smart-match-info">
<div className="xx-smart-match-name" title={asset.name}>
{asset.name}
</div>
<div className="xx-smart-match-reason">🎯 {matchReason}</div>
</div>
</div>
)
}
export default SmartMatchCard
@@ -0,0 +1,80 @@
/**
* 智能匹配输入区
* textarea + 提示 + 按钮组
*/
import React from "react"
import { LoadingOutlined } from "@ant-design/icons"
interface SmartMatchInputProps {
inputValue: string
onInputChange: (value: string) => void
matching: boolean
onMatch: () => void
hasMatched: boolean
onRefresh: () => void
materialsCount: number
loading: boolean
}
const SmartMatchInput: React.FC<SmartMatchInputProps> = ({
inputValue,
onInputChange,
matching,
onMatch,
hasMatched,
onRefresh,
materialsCount,
loading,
}) => {
return (
<div className="xx-smart-match-input-area">
<label className="xx-smart-match-label">🤖 描述你想要的视频内容</label>
<textarea
className="xx-smart-match-input"
placeholder="例如:一个科技感十足的产品宣传视频,画面要有现代办公场景、团队协作、数据分析图表…"
value={inputValue}
onChange={(e) => onInputChange(e.target.value)}
rows={3}
onKeyDown={(e) => {
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
onMatch()
}
}}
/>
<div className="xx-smart-match-input-footer">
<span className="xx-smart-match-tip">
{loading ? "扫描视频库中…" : `当前视频库共 ${materialsCount} 个素材可供匹配`}
</span>
<div style={{ display: "flex", gap: 8 }}>
{hasMatched && (
<button
type="button"
className="xx-btn xx-btn-ghost xx-btn-sm"
onClick={onRefresh}
disabled={matching || loading}
>
🔄 换一批
</button>
)}
<button
type="button"
className="xx-btn xx-btn-primary xx-btn-sm"
onClick={onMatch}
disabled={matching || loading || !inputValue.trim()}
>
{matching ? (
<>
<LoadingOutlined style={{ marginRight: 6 }} />
匹配中…
</>
) : (
"✨ 智能匹配"
)}
</button>
</div>
</div>
</div>
)
}
export default SmartMatchInput
@@ -0,0 +1,114 @@
/**
* 智能匹配结果区(含加载/空状态/已选汇总)
*/
import React from "react"
import { LoadingOutlined } from "@ant-design/icons"
import type { AssetItem } from "@/api/assets"
import SmartMatchCard from "./SmartMatchCard"
interface SmartMatchResultItem {
asset: AssetItem
matchScore: number
matchReason: string
}
interface SmartMatchResultsProps {
results: SmartMatchResultItem[]
selectedIds: string[]
matching: boolean
hasMatched: boolean
onToggle: (assetId: string) => void
onSelectAll: () => void
onClear: () => void
formatDuration: (seconds: number) => string
selectedTotalDuration: number
}
const SmartMatchResults: React.FC<SmartMatchResultsProps> = ({
results,
selectedIds,
matching,
hasMatched,
onToggle,
onSelectAll,
onClear,
formatDuration,
selectedTotalDuration,
}) => {
// 匹配中状态
if (matching) {
return (
<div className="xx-smart-match-loading">
<LoadingOutlined
style={{ fontSize: 32, color: "var(--primary-color)", marginBottom: 12 }}
/>
<div style={{ color: "var(--text-primary)", fontSize: 14 }}>AI 正在分析素材…</div>
<div style={{ color: "var(--text-tertiary)", fontSize: 12, marginTop: 4 }}>
正在根据描述从视频库中匹配最合适的素材
</div>
</div>
)
}
// 未匹配状态提示
if (!hasMatched) {
return (
<div className="xx-smart-match-empty">
<div style={{ fontSize: 36, marginBottom: 8 }}>💡</div>
<div style={{ color: "var(--text-secondary)", fontSize: 13 }}>
输入视频内容描述,点击「智能匹配」让 AI 帮你选素材
</div>
</div>
)
}
// 无结果
if (results.length === 0) return null
return (
<>
<div className="xx-smart-match-results">
<div className="xx-smart-match-results-header">
<span className="xx-smart-match-results-title">推荐素材 ({results.length}个)</span>
<div className="xx-smart-match-results-actions">
<button type="button" className="xx-link-btn" onClick={onSelectAll}>
全选
</button>
<span style={{ color: "var(--border-color)" }}>|</span>
<button type="button" className="xx-link-btn" onClick={onClear}>
清空
</button>
</div>
</div>
<div className="xx-smart-match-grid">
{results.map((result) => {
const isSelected = selectedIds.includes(result.asset.id)
return (
<SmartMatchCard
key={result.asset.id}
result={result}
selected={isSelected}
onClick={() => onToggle(result.asset.id)}
formatDuration={formatDuration}
/>
)
})}
</div>
</div>
{/* 已选素材汇总 */}
{selectedIds.length > 0 && (
<div className="xx-smart-match-summary">
<div className="xx-smart-match-summary-header">
<span className="xx-pill xx-pill-ok">已选 {selectedIds.length} 个素材</span>
<span style={{ color: "var(--text-tertiary)", fontSize: 12 }}>
预计总时长约 {selectedTotalDuration.toFixed(0)} 秒
</span>
</div>
</div>
)}
</>
)
}
export default SmartMatchResults
@@ -0,0 +1,35 @@
/**
* 单个 AI 标题卡片
*/
import React from "react"
import { CheckCircleFilled } from "@ant-design/icons"
interface AiTitleCardProps {
title: string
highlight: string
style: "catchy" | "emotional" | "informative"
selected: boolean
onClick: () => void
}
const AiTitleCard: React.FC<AiTitleCardProps> = ({
title,
highlight,
style,
selected,
onClick,
}) => {
return (
<div className={`xx-ai-title-card ${selected ? "selected" : ""} ${style}`} onClick={onClick}>
<div className="xx-ai-title-card-text">{title}</div>
<div className="xx-ai-title-card-tag">{highlight}</div>
{selected && (
<div className="xx-ai-title-card-check">
<CheckCircleFilled style={{ color: "#fff", fontSize: 14 }} />
</div>
)}
</div>
)
}
export default AiTitleCard
@@ -0,0 +1,105 @@
/**
* AI 智能生成标题
* 输入框 + 生成按钮 + 结果列表 + 加载状态
*/
import React from "react"
import { LoadingOutlined } from "@ant-design/icons"
import AiTitleCard from "./AiTitleCard"
interface AiTitleItem {
title: string
highlight: string
style: "catchy" | "emotional" | "informative"
}
interface AiTitleGeneratorProps {
inputValue: string
onInputChange: (value: string) => void
generating: boolean
onGenerate: () => void
results: AiTitleItem[]
hasGenerated: boolean
onSelect: (title: string) => void
selectedTitle: string
onRefresh: () => void
}
const AiTitleGenerator: React.FC<AiTitleGeneratorProps> = ({
inputValue,
onInputChange,
generating,
onGenerate,
results,
hasGenerated,
onSelect,
selectedTitle,
onRefresh,
}) => {
return (
<div className="xx-ai-title-section">
<div className="xx-ai-title-header">
<span className="xx-ai-title-label">✨ AI 智能生成标题</span>
</div>
<div className="xx-ai-title-input-row">
<input
className="xx-ai-title-input"
placeholder="输入视频内容描述或关键词,如:职场成长、副业赚钱…"
value={inputValue}
onChange={(e) => onInputChange(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") onGenerate()
}}
/>
<button
type="button"
className="xx-btn xx-btn-primary"
onClick={onGenerate}
disabled={generating || !inputValue.trim()}
>
{generating ? (
<>
<LoadingOutlined style={{ marginRight: 6 }} />
生成中
</>
) : (
"生成标题"
)}
</button>
</div>
{/* 生成结果 */}
{hasGenerated && !generating && results.length > 0 && (
<div className="xx-ai-title-results">
<div className="xx-ai-title-results-header">
<span className="xx-ai-title-results-count">为你生成 {results.length} 个标题</span>
<button type="button" className="xx-link-btn" onClick={onRefresh} disabled={generating}>
🔄 换一批
</button>
</div>
<div className="xx-ai-title-list">
{results.map((item, idx) => (
<AiTitleCard
key={idx}
title={item.title}
highlight={item.highlight}
style={item.style}
selected={selectedTitle === item.title}
onClick={() => onSelect(item.title)}
/>
))}
</div>
</div>
)}
{/* 生成中 */}
{generating && (
<div className="xx-ai-title-loading">
<LoadingOutlined style={{ color: "var(--primary-color)", marginRight: 8 }} />
AI 正在为你创作标题…
</div>
)}
</div>
)
}
export default AiTitleGenerator
@@ -0,0 +1,41 @@
/**
* 标题预设样式网格
*/
import React from "react"
interface TitlePresetItem {
key: string
label: string
previewStyle: React.CSSProperties
}
interface TitlePresetsGridProps {
presets: TitlePresetItem[]
activePreset: string | null
onApply: (presetKey: string) => void
}
const TitlePresetsGrid: React.FC<TitlePresetsGridProps> = ({ presets, activePreset, onApply }) => {
return (
<div className="xx-title-presets-grid">
{presets.map((p) => {
const isActive = activePreset === p.key
return (
<button
key={p.key}
className={`xx-title-preset-card${isActive ? " active" : ""}`}
onClick={() => onApply(p.key)}
title={p.label}
>
<span className="xx-title-preset-preview-text" style={p.previewStyle}>
标题
</span>
<span className="xx-title-preset-card-label">{p.label}</span>
</button>
)
})}
</div>
)
}
export default TitlePresetsGrid
@@ -0,0 +1,151 @@
/**
* 标题样式设置区
* 位置/字体/字号/样式按钮/预设
*/
import React from "react"
import type { TitleSettings } from "../../types"
import TitlePresetsGrid from "./TitlePresetsGrid"
interface PositionOption {
value: string
label: string
}
interface TitlePresetItem {
key: string
label: string
previewStyle: React.CSSProperties
}
interface TitleStylePanelProps {
settings: TitleSettings
onUpdatePosition: (position: string) => void
onUpdateFont: (font: string) => void
onUpdateSize: (size: number) => void
onToggleBold: () => void
onToggleItalic: () => void
onToggleStroke: () => void
onToggleShadow: () => void
onApplyPreset: (presetKey: string) => void
activePreset: string | null
titlePresets: TitlePresetItem[]
POSITION_OPTIONS: PositionOption[]
FONT_OPTIONS: string[]
}
const TitleStylePanel: React.FC<TitleStylePanelProps> = ({
settings,
onUpdatePosition,
onUpdateFont,
onUpdateSize,
onToggleBold,
onToggleItalic,
onToggleStroke,
onToggleShadow,
onApplyPreset,
activePreset,
titlePresets,
POSITION_OPTIONS,
FONT_OPTIONS,
}) => {
return (
<div className="xx-title-style-section">
<h4 className="xx-section-subtitle">标题样式</h4>
{/* 位置 + 字体 一行 */}
<div className="xx-title-style-row">
<div className="xx-form-field xx-half-field">
<label>位置</label>
<select
className="xx-form-select"
value={settings.position}
onChange={(e) => onUpdatePosition(e.target.value)}
>
{POSITION_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
<div className="xx-form-field xx-half-field">
<label>字体</label>
<select
className="xx-form-select"
value={settings.font}
onChange={(e) => onUpdateFont(e.target.value)}
>
{FONT_OPTIONS.map((f) => (
<option key={f} value={f}>
{f}
</option>
))}
</select>
</div>
</div>
{/* 字号滑块 */}
<div className="xx-form-field">
<div className="xx-field-label-row">
<label>字号</label>
<span className="xx-field-value">{settings.size}px</span>
</div>
<input
className="xx-slider"
type="range"
min={12}
max={48}
value={settings.size}
onChange={(e) => onUpdateSize(Number(e.target.value))}
/>
</div>
{/* 预设样式 */}
<div className="xx-form-field">
<label>预设样式</label>
<TitlePresetsGrid
presets={titlePresets}
activePreset={activePreset}
onApply={onApplyPreset}
/>
</div>
{/* 样式按钮:粗体/斜体/描边/阴影 */}
<div className="xx-form-field">
<label>样式</label>
<div className="xx-style-btns">
<button
className={`xx-style-btn ${settings.bold ? "active" : ""}`}
onClick={onToggleBold}
title="粗体"
>
<b>B</b>
</button>
<button
className={`xx-style-btn ${settings.italic ? "active" : ""}`}
onClick={onToggleItalic}
title="斜体"
>
<i>I</i>
</button>
<button
className={`xx-style-btn ${settings.stroke ? "active" : ""}`}
onClick={onToggleStroke}
title="描边"
>
S
</button>
<button
className={`xx-style-btn ${settings.shadow ? "active" : ""}`}
onClick={onToggleShadow}
title="阴影"
>
☁
</button>
</div>
</div>
</div>
)
}
export default TitleStylePanel
@@ -0,0 +1,132 @@
/**
* 克隆声音展开区域
* 克隆按钮、轮询提示、已克隆列表、空状态
*/
import React from "react"
import { Typography } from "antd"
import { ThunderboltOutlined, AudioOutlined, CheckCircleFilled } from "@ant-design/icons"
import type { VoiceClone } from "@/api/voice-clone"
const { Text } = Typography
interface CloneVoiceSectionProps {
clonedVoices: VoiceClone[]
hasProcessing: boolean
selectedClonedVoice: string
onSelect: (voiceId: string) => void
onOpenCloneModal: () => void
CLONE_STATUS_CONFIG: Record<string, { label: string; color: string }>
formatDuration: (seconds: number) => string
}
const CloneVoiceSection: React.FC<CloneVoiceSectionProps> = ({
clonedVoices,
hasProcessing,
selectedClonedVoice,
onSelect,
onOpenCloneModal,
CLONE_STATUS_CONFIG,
formatDuration,
}) => {
return (
<div className="xx-clone-section">
<p className="xx-clone-section-title">克隆我的声音</p>
<Text style={{ fontSize: 12, color: "var(--text-tertiary, #94a3b8)" }}>
上传一段您的语音样本,AI 将克隆您的声音用于视频配音
</Text>
<div style={{ marginTop: 12 }}>
<button
className="xx-btn xx-btn-primary"
style={{ height: 36, fontSize: 13 }}
onClick={onOpenCloneModal}
>
<ThunderboltOutlined /> 克隆新声音
</button>
</div>
{/* 轮询提示 */}
{hasProcessing && (
<div className="xx-clone-polling-hint" style={{ marginTop: 10 }}>
<span className="xx-clone-polling-dot" />
正在同步克隆进度...
</div>
)}
{/* 已克隆声音列表 */}
{clonedVoices.length > 0 && (
<div className="xx-clone-voices-list">
{clonedVoices.map((cv) => {
const statusCfg = CLONE_STATUS_CONFIG[cv.status]
const isReady = cv.status === "ready"
const selected = selectedClonedVoice === cv.id
return (
<div
key={cv.id}
className={`xx-clone-voice-row ${selected ? "selected" : ""} ${
!isReady ? "disabled" : ""
}`}
onClick={() => {
if (isReady) onSelect(cv.id)
}}
role="button"
tabIndex={isReady ? 0 : -1}
aria-pressed={selected}
>
<div className={`xx-clone-avatar ${cv.status}`}>
<AudioOutlined />
</div>
<div className="xx-clone-info">
<div className="xx-clone-name">{cv.name}</div>
<div className="xx-clone-status">
<span className="xx-clone-status-dot" style={{ background: statusCfg.color }} />
<span style={{ color: statusCfg.color }}>{statusCfg.label}</span>
{isReady && (
<span
style={{
color: "var(--text-secondary)",
marginLeft: 8,
}}
>
{formatDuration(cv.duration_seconds)}
</span>
)}
</div>
</div>
{selected && isReady && (
<CheckCircleFilled style={{ color: "var(--primary-color, #4f46e5)" }} />
)}
</div>
)
})}
</div>
)}
{clonedVoices.length === 0 && (
<Text
style={{
color: "var(--text-secondary)",
display: "block",
textAlign: "center",
padding: "16px 0",
}}
>
暂无克隆音色,点击「克隆新声音」开始
</Text>
)}
<Text
style={{
fontSize: 12,
color: "var(--text-tertiary, #94a3b8)",
marginTop: 10,
display: "block",
}}
>
💡 提示:录音环境越安静,克隆效果越好。
</Text>
</div>
)
}
export default CloneVoiceSection
@@ -0,0 +1,93 @@
/**
* 自定义录制面板
* textarea + 合成按钮 + 结果区 + 存为素材按钮
*/
import React from "react"
import { Typography } from "antd"
import { AudioOutlined, SaveOutlined } from "@ant-design/icons"
const { Text } = Typography
interface CustomVoicePanelProps {
customVoiceText: string
onTextChange: (text: string) => void
synthesizePending: boolean
onSynthesize: () => void
ttsError: string | null
customAudioUrl: string | null
completedTtsJobId: string | null
onOpenSaveModal: () => void
}
const CustomVoicePanel: React.FC<CustomVoicePanelProps> = ({
customVoiceText,
onTextChange,
synthesizePending,
onSynthesize,
ttsError,
customAudioUrl,
completedTtsJobId,
onOpenSaveModal,
}) => {
return (
<div>
<textarea
placeholder="输入配音文案,点击合成按钮生成语音…"
value={customVoiceText}
onChange={(e) => onTextChange(e.target.value)}
maxLength={500}
style={{
width: "100%",
minHeight: 100,
border: "1px solid var(--border-color, #e2e8f0)",
borderRadius: "var(--radius-sm, 10px)",
padding: 12,
fontSize: 14,
resize: "vertical",
outline: "none",
}}
/>
<div style={{ marginTop: 12, display: "flex", gap: 8 }}>
<button
className="xx-btn xx-btn-ghost"
disabled={!customVoiceText.trim() || synthesizePending}
onClick={onSynthesize}
>
<AudioOutlined /> {synthesizePending ? "合成中…" : "合成语音"}
</button>
</div>
{ttsError && (
<Text
style={{
color: "var(--error, #ef4444)",
marginTop: 8,
display: "block",
}}
>
{ttsError}
</Text>
)}
{customAudioUrl && completedTtsJobId && (
<div
style={{
marginTop: 8,
display: "flex",
alignItems: "center",
gap: 12,
}}
>
<Text style={{ color: "var(--success, #10b981)" }}>✓ 语音合成完成</Text>
<button
className="xx-btn xx-btn-primary"
style={{ height: 30, padding: "0 14px", fontSize: 12 }}
onClick={onOpenSaveModal}
>
<SaveOutlined /> 存为素材
</button>
</div>
)}
</div>
)
}
export default CustomVoicePanel
@@ -0,0 +1,77 @@
/**
* 预设音色下拉选择 + 试听按钮
* voiceMode === "preset" 时显示的详情区
*/
import React from "react"
import { PlayCircleOutlined, PauseCircleOutlined } from "@ant-design/icons"
import type { PresetVoiceItem } from "@/api/voices"
interface PresetVoiceDetailProps {
presetVoices: PresetVoiceItem[]
selectedVoice: string
onSelect: (voiceId: string) => void
playingVoice: string | null
onTogglePlay: (voiceId: string, previewUrl: string | null) => void
presetVoicesLoading: boolean
}
const PresetVoiceDetail: React.FC<PresetVoiceDetailProps> = ({
presetVoices,
selectedVoice,
onSelect,
playingVoice,
onTogglePlay,
presetVoicesLoading,
}) => {
return (
<div>
<div className="xx-form-field">
<label>从配音库选择</label>
<select value={selectedVoice} onChange={(e) => onSelect(e.target.value)}>
<option value="">请选择配音…</option>
{presetVoicesLoading ? (
<option disabled>加载中…</option>
) : (
presetVoices.map((v) => (
<option key={v.voice_id} value={v.voice_id}>
{v.name} — {v.description}
</option>
))
)}
</select>
</div>
{/* 试听按钮 */}
{presetVoices.length > 0 && (
<div
style={{
display: "flex",
gap: 8,
flexWrap: "wrap",
marginTop: 8,
}}
>
{presetVoices.slice(0, 4).map((v) => (
<button
key={v.voice_id}
className="xx-btn xx-btn-ghost"
style={{ height: 32, padding: "0 12px", fontSize: 12 }}
onClick={() => onTogglePlay(v.voice_id, v.preview_url)}
>
{playingVoice === v.voice_id ? (
<>
<PauseCircleOutlined /> 停止
</>
) : (
<>
<PlayCircleOutlined /> {v.name}
</>
)}
</button>
))}
</div>
)}
</div>
)
}
export default PresetVoiceDetail
@@ -0,0 +1,125 @@
/**
* 保存到配音库弹窗
*/
import React from "react"
import { CloseOutlined, PlusOutlined } from "@ant-design/icons"
import type { TagItem } from "@/api/tags"
interface SaveVoiceModalProps {
open: boolean
onClose: () => void
saveName: string
onNameChange: (name: string) => void
saveTagIds: string[]
onTagIdsChange: (tags: string[] | ((prev: string[]) => string[])) => void
saveNewTag: string
onNewTagChange: (tag: string) => void
onAddTag: (tagName: string) => void
allTags: TagItem[]
savePending: boolean
onConfirm: () => void
}
const SaveVoiceModal: React.FC<SaveVoiceModalProps> = ({
open,
onClose,
saveName,
onNameChange,
saveTagIds,
onTagIdsChange,
saveNewTag,
onNewTagChange,
onAddTag,
allTags,
savePending,
onConfirm,
}) => {
if (!open) return null
return (
<div className="xx-save-modal-overlay" onClick={onClose}>
<div className="xx-save-modal" onClick={(e) => e.stopPropagation()}>
<div className="xx-save-modal-header">
<span>保存到配音库</span>
<button className="xx-save-modal-close" onClick={onClose}>
<CloseOutlined />
</button>
</div>
<div className="xx-save-modal-body">
<label className="xx-save-modal-label">素材名称</label>
<input
className="xx-save-modal-input"
placeholder="留空则自动生成名称"
value={saveName}
onChange={(e) => onNameChange(e.target.value)}
maxLength={50}
/>
<label className="xx-save-modal-label">
标签
<span
style={{
fontWeight: 400,
color: "var(--text-tertiary, #94a3b8)",
}}
>
(可选)
</span>
</label>
<div className="xx-save-modal-tags">
{saveTagIds.map((id) => {
const tag = allTags.find((t) => t.id === id)
return tag ? (
<span key={id} className="xx-save-modal-tag active">
{tag.name}
<CloseOutlined
className="xx-save-modal-tag-remove"
onClick={() => onTagIdsChange((prev: string[]) => prev.filter((x) => x !== id))}
/>
</span>
) : null
})}
<input
className="xx-save-modal-tag-input"
placeholder="输入标签名回车添加"
value={saveNewTag}
onChange={(e) => onNewTagChange(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
onAddTag(saveNewTag)
}
}}
/>
</div>
{allTags.length > 0 && (
<div className="xx-save-modal-tag-presets">
{allTags
.filter((t) => !saveTagIds.includes(t.id))
.slice(0, 12)
.map((t) => (
<button
key={t.id}
className="xx-save-modal-tag-preset"
onClick={() => onTagIdsChange((prev: string[]) => [...prev, t.id])}
>
{t.name}
<PlusOutlined style={{ fontSize: 10, marginLeft: 4 }} />
</button>
))}
</div>
)}
</div>
<div className="xx-save-modal-footer">
<button className="xx-btn xx-btn-ghost" onClick={onClose}>
取消
</button>
<button className="xx-btn xx-btn-primary" disabled={savePending} onClick={onConfirm}>
{savePending ? "保存中…" : "保存"}
</button>
</div>
</div>
</div>
)
}
export default SaveVoiceModal
@@ -0,0 +1,53 @@
/**
* 音色选择卡片
* 用于顶部预设音色卡片和克隆入口卡片
*/
import React from "react"
interface VoiceChoiceCardProps {
selected: boolean
onClick: () => void
avatar: React.ReactNode
title: string
description?: string
avatarStyle?: React.CSSProperties
loading?: boolean
}
const VoiceChoiceCard: React.FC<VoiceChoiceCardProps> = ({
selected,
onClick,
avatar,
title,
description,
avatarStyle,
loading = false,
}) => {
return (
<div
className={`xx-voice-choice-item ${selected ? "selected" : ""}`}
onClick={onClick}
role="button"
tabIndex={0}
aria-pressed={selected}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
onClick()
}
}}
style={loading ? { opacity: 0.5 } : undefined}
>
<span className="xx-voice-choice-check">✓</span>
<div className="xx-voice-choice-avatar" style={avatarStyle}>
{avatar}
</div>
<div className="xx-voice-choice-info">
<h4>{title}</h4>
{description && <p>{description}</p>}
</div>
</div>
)
}
export default VoiceChoiceCard
@@ -0,0 +1,100 @@
/**
* AI 智能推荐配音区域
*/
import React from "react"
import { LoadingOutlined, CheckCircleFilled } from "@ant-design/icons"
import type { PresetVoiceItem } from "@/api/voices"
interface VoiceRecommendSectionProps {
presetVoices: PresetVoiceItem[]
voiceRecommendLoading: boolean
voiceRecommendations: string[]
hasVoiceRecommend: boolean
onRecommend: () => void
onSelectVoice: (voiceId: string) => void
selectedVoiceId: string
voiceMode: "preset" | "custom" | "clone"
VOICE_GENDER_ICON: Record<string, string>
}
const VoiceRecommendSection: React.FC<VoiceRecommendSectionProps> = ({
presetVoices,
voiceRecommendLoading,
voiceRecommendations,
hasVoiceRecommend,
onRecommend,
onSelectVoice,
selectedVoiceId,
voiceMode,
VOICE_GENDER_ICON,
}) => {
return (
<div className="xx-voice-recommend-section">
<div className="xx-voice-recommend-header">
<span className="xx-voice-recommend-label">✨ AI 智能推荐</span>
<button
type="button"
className="xx-btn xx-btn-primary xx-btn-sm"
onClick={onRecommend}
disabled={voiceRecommendLoading}
>
{voiceRecommendLoading ? (
<>
<LoadingOutlined style={{ marginRight: 6 }} />
推荐中
</>
) : hasVoiceRecommend ? (
"换一批"
) : (
"智能推荐"
)}
</button>
</div>
{voiceRecommendLoading && (
<div className="xx-voice-recommend-loading">
<LoadingOutlined style={{ color: "var(--primary-color)", marginRight: 8 }} />
根据视频内容为你匹配最合适的音色…
</div>
)}
{!voiceRecommendLoading && hasVoiceRecommend && voiceRecommendations.length > 0 && (
<div className="xx-voice-recommend-list">
{voiceRecommendations.map((voiceId) => {
const v = presetVoices.find((pv) => pv.voice_id === voiceId)
if (!v) return null
const isSelected = voiceMode === "preset" && selectedVoiceId === v.voice_id
return (
<div
key={v.voice_id}
className={`xx-voice-recommend-card ${isSelected ? "selected" : ""}`}
onClick={() => onSelectVoice(v.voice_id)}
>
<div className="xx-voice-recommend-avatar">
{VOICE_GENDER_ICON[v.gender] ?? "✨"}
</div>
<div className="xx-voice-recommend-info">
<div className="xx-voice-recommend-name">{v.name}</div>
<div className="xx-voice-recommend-desc">{v.description}</div>
</div>
{isSelected && (
<div className="xx-voice-recommend-check">
<CheckCircleFilled style={{ color: "#fff", fontSize: 16 }} />
</div>
)}
</div>
)
})}
</div>
)}
{!voiceRecommendLoading && !hasVoiceRecommend && (
<div className="xx-voice-recommend-empty">
<span>点击「智能推荐」,AI 根据视频内容匹配音色</span>
</div>
)}
</div>
)
}
export default VoiceRecommendSection

Some files were not shown because too many files have changed in this diff Show More