Compare commits

...

44 Commits

Author SHA1 Message Date
CI Bot cedb9b4837 style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 10s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m22s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 3m16s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m40s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m42s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m11s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m49s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m38s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m31s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m16s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 7m36s
AI Code Review / AI Code Review (pull_request) Successful in 7m48s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m48s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 17m20s
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 5m46s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 35s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 50s
2026-10-15 09:32:12 +00:00
Agent 0f1d027c73 fix(lipsync): #2076 预合成模式补齐 script_text,修复 Ditto 任务 script_len=0
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m10s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m50s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 48s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 16s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m33s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m50s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m19s
AI Code Review / AI Code Review (pull_request) Successful in 6m49s
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
根因:前端 #1875(053b0063) 起预合成 payload 不带 script_text,
job.script_text 落库为空 → Ditto 任务 script_len=0 → 情绪分析被跳过。

修复(双保险):
- 前端 AiAvatarPage 预合成 payload 补传 script_text: state.scriptText
- 后端 create_job 预合成模式 script_text 为空时从 sentence_timings 拼接兜底
  (覆盖所有调用方;timings 每项自带 text 字段)

与 PR#2292 无关(2292 只动 build_timeline/intensity 内部逻辑)。

测试:新增 3 用例(空时拼接兜底/显式传入保留原文/空 timings 退化直接音频),
test_lipsync_routes.py 45 passed
2026-10-15 17:19:45 +08:00
xiaoxia 006e58ae54 refactor(viral-audio): #2311 短音色样本模式重构音频链路 (#2311)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 5s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m4s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m18s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m37s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 37s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 29s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m46s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m10s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m25s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m46s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m46s
CI/CD Pipeline / Integration Tests (push) Successful in 7m32s
AI Code Review / AI Code Review (pull_request) Successful in 7m30s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m50s
CI/CD Pipeline / Validate - Style (push) Successful in 8m5s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 8m18s
CI/CD Pipeline / Unit Tests (push) Failing after 13m56s
CI/CD Pipeline / Validate - Security (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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
refactor(viral-audio): #2311 短音色样本模式重构音频链路

- 预置音色直接读OSS静态样本(零TTS)
- 克隆音色ffmpeg预处理缓存
- TTS只做兜底
- Wan max_duration 15→30
- 删除atempo/截断逻辑
- seed_preset_voice_samples.py部署脚本
2026-10-15 16:58:49 +08:00
xiaoxia 7f825b7e55 fix(viral-prompt): #2310 占位符保留日志降级DEBUG,疑似真bug才WARNING (#2310)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 0s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 41s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m34s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m14s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m19s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m22s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m27s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m46s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 11s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m33s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 7m8s
AI Code Review / AI Code Review (pull_request) Successful in 7m13s
CI/CD Pipeline / Validate - Style (push) Successful in 7m11s
CI/CD Pipeline / Integration Tests (push) Successful in 8m31s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m24s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 3m27s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 3m29s
CI/CD Pipeline / Unit Tests (push) Successful in 14m15s
CI/CD Pipeline / Validate - Security (push) Successful in 15m6s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-15 14:19:02 +08:00
xiaoxia 410d686579 fix(viral): #2307 补充——TTS硬限按model细分(Seedance2.0=14.5s/2.5=29s/Wan=14.5s)cherry-pick (#2309)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m17s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m24s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m24s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m17s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m55s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 32s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m21s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (push) Successful in 3m22s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m23s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m46s
CI/CD Pipeline / Integration Tests (push) Failing after 4m58s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m42s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m19s
AI Code Review / AI Code Review (pull_request) Successful in 6m49s
CI/CD Pipeline / Validate - Security (push) Successful in 8m4s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m51s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 5m2s
CI/CD Pipeline / Unit Tests (push) Successful in 11m46s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-15 13:15:59 +08:00
xiaoxia 4879029c46 fix(viral): #2307 TTS音频时长按模型硬限对齐+max_chars按实际语速重校+reviewer超时收紧 (#2307)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 59s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 42s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m32s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m32s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 31s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m6s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m18s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m23s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m26s
CI/CD Pipeline / Validate - Style (push) Successful in 4m40s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m19s
CI/CD Pipeline / Integration Tests (push) Successful in 5m50s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m5s
AI Code Review / AI Code Review (pull_request) Successful in 6m59s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m8s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 4m31s
CI/CD Pipeline / Validate - Security (push) Successful in 8m17s
CI/CD Pipeline / Unit Tests (push) Successful in 12m4s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-15 12:22:28 +08:00
xiaoxia 6cfab71d6e fix(viral-parser): #2308 分镜字段映射修复——<shot>/<visual>/<action_details>三字段拆分 (#2308)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 5s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m43s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m16s
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 56s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m35s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m25s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m1s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m8s
CI/CD Pipeline / Integration Tests (push) Successful in 3m53s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 5s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 24s
CI/CD Pipeline / Validate - Style (push) Successful in 5m33s
AI Code Review / AI Code Review (pull_request) Successful in 6m54s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m42s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m49s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m15s
CI/CD Pipeline / Validate - Security (push) Successful in 9m17s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m5s
CI/CD Pipeline / Unit Tests (push) Successful in 12m12s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-15 12:07:38 +08:00
xiaoxia 96786c4f85 fix(viral-script): finish=length救回逻辑挪到StopIteration(chunk分支死代码) (#2306)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m17s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m26s
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 34s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m39s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 17s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m4s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m16s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m28s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m25s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Failing after 4m57s
CI/CD Pipeline / Validate - Style (push) Successful in 4m58s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m25s
CI/CD Pipeline / Integration Tests (push) Successful in 6m6s
AI Code Review / AI Code Review (pull_request) Successful in 7m3s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m2s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 4m11s
CI/CD Pipeline / Validate - Security (push) Successful in 10m23s
CI/CD Pipeline / Unit Tests (push) Successful in 12m15s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-15 10:42:46 +08:00
xiaoxia bf4b11c49d fix(viral-script): last_finish_reason跨调用泄漏+finish=length内容救回+pro档固定加+early-break门槛 (#2305)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 57s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m31s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m48s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m7s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 1m32s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Failing after 3m38s
CI/CD Pipeline / Integration Tests (push) Successful in 3m40s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m45s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (push) Successful in 4m11s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m14s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 5m18s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m51s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
AI Code Review / AI Code Review (pull_request) Successful in 7m17s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m9s
CI/CD Pipeline / Validate - Security (push) Successful in 8m41s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m41s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 4m46s
CI/CD Pipeline / Unit Tests (push) Successful in 12m37s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-15 10:04:06 +08:00
xiaoxia 5b6079964e fix(ai_client): enable_thinking→thinking参数翻译+流式禁SDK重试,根治v6分镜reasoning超时 (#2304)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m13s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m28s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m42s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m48s
CI/CD Pipeline / CI Gate (pull_request) Successful in 11s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m10s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m9s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 1m19s
CI/CD Pipeline / Integration Tests (push) Successful in 3m34s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m15s
CI/CD Pipeline / Validate - Style (push) Failing after 4m46s
CI/CD Pipeline / Validate - Security (push) Successful in 5m48s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m8s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m3s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 6m32s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 7m0s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m18s
CI/CD Pipeline / Unit Tests (push) Successful in 11m22s
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Successful in 22m12s
2026-10-15 03:13:24 +08:00
xiaoxia 0e668fda8c fix(viral-script): 分镜生成关豆包思考模式+放宽超时,根治reasoning超时fallback (#2303)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 8s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 53s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m3s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m57s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m44s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m9s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m36s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m51s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 3m57s
CI/CD Pipeline / Validate - Style (push) Successful in 4m29s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 2m3s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m48s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m6s
CI/CD Pipeline / Validate - Security (push) Successful in 6m19s
AI Code Review / AI Code Review (pull_request) Successful in 6m51s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m20s
CI/CD Pipeline / Unit Tests (push) Successful in 11m31s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 26m56s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Successful in 27m4s
2026-10-15 02:00:58 +08:00
xiaoxia b4b6b32211 fix(viral-prompt): 修正_safe_format告警文案误导,区分样例花括号保留与真回落 (#2302)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m4s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m34s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 49s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 6s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m12s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m30s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m12s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m37s
CI/CD Pipeline / Integration Tests (push) Successful in 3m45s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m36s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m47s
CI/CD Pipeline / Validate - Style (push) Successful in 5m1s
AI Code Review / AI Code Review (pull_request) Successful in 6m53s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m56s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m54s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m3s
CI/CD Pipeline / Validate - Security (push) Successful in 10m51s
CI/CD Pipeline / Unit Tests (push) Successful in 11m40s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Successful in 22m19s
2026-10-15 00:43:09 +08:00
xiaoxia 50e833eeeb fix(web): Google Fonts 延迟到 window.load 后注入,彻底排除E2E load事件阻塞 (#2301)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 6s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m4s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 40s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m34s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 44s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m5s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m12s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m4s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m50s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 4m18s
CI/CD Pipeline / Validate - Style (push) Successful in 5m23s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m35s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m1s
AI Code Review / AI Code Review (pull_request) Successful in 6m49s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m36s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m8s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 3m14s
CI/CD Pipeline / Validate - Security (push) Successful in 13m3s
CI/CD Pipeline / Unit Tests (push) Successful in 18m18s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-14 22:56:14 +08:00
xiaoxia 79624587c4 fix(web): Google Fonts 非阻塞加载修复 E2E page.goto 超时 (#2300)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m41s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m32s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m8s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 28s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 33s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m30s
CI/CD Pipeline / CI Gate (pull_request) Successful in 4s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m21s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m41s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m21s
CI/CD Pipeline / Integration Tests (push) Successful in 5m18s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m57s
CI/CD Pipeline / Validate - Style (push) Successful in 6m0s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m19s
AI Code Review / AI Code Review (pull_request) Successful in 7m4s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m19s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 4m51s
CI/CD Pipeline / Unit Tests (push) Successful in 12m39s
CI/CD Pipeline / Validate - Security (push) Successful in 15m38s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-14 21:39:29 +08:00
xiaoxia ee871ac4aa Merge pull request 'fix(ditto): blend_frames 默认值 12→6,与 GPU 端 clamp 最优值对齐' (#2299) from fix/ditto-blend-frames-default into develop
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 47s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 16s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 55s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 20s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 17s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m6s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m8s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m24s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 5m2s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m50s
CI/CD Pipeline / Integration Tests (push) Successful in 5m35s
CI/CD Pipeline / Validate - Style (push) Successful in 6m3s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m10s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m15s
AI Code Review / AI Code Review (pull_request) Successful in 6m50s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 5m6s
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Unit 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 / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
2026-10-14 21:26:46 +08:00
xiaoxia 50c8618b87 fix(viral-script): 加固_safe_format防花括号炸format+默认模板回落ERROR+markdown时间分块 (#2298)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m22s
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 36s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 27s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m14s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 5m31s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 45s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m32s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m24s
CI/CD Pipeline / Validate - Style (push) Successful in 6m29s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m58s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m33s
AI Code Review / AI Code Review (pull_request) Successful in 6m29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m48s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m46s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m20s
CI/CD Pipeline / Unit Tests (push) Successful in 13m20s
CI/CD Pipeline / Validate - Security (push) Successful in 15m44s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m8s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-14 20:41:34 +08:00
Agent 712464f030 fix(ditto): blend_frames 默认值 12→6,与 GPU 端 clamp 最优值对齐
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 53s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m15s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m53s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m59s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m11s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 6m27s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 6m36s
AI Code Review / AI Code Review (pull_request) Successful in 6m47s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 9m51s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 15m37s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 2s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 9m11s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 28s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 4m22s
GPU 端三层表情优化(眨眼+情绪头动+眼球微动+呼吸感+非唇动态混合+低表情检测)
已完成,blend_frames=6 时表情切换最灵敏;GPU 端仍保留 max(6,min(8,...)) clamp
兜底,本改动仅让 SaaS 端默认值与 GPU 最优值对齐。

注意:staging DB 当前 ditto_blend_frames=10(DB 优先于 env 默认),本 PR 合入后
需通过后台配置将 DB 值改为 6 才会在 staging 实际生效;生产未开 Ditto 无影响。
范围校验 6-30 不变。
2026-10-14 20:39:09 +08:00
xiaoxia c4ca302242 fix(viral-script): 流式max_tokens截断检测+早期格式预判早停+死代码删除 (#2297)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 50s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m24s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m37s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m24s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 25s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m16s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m32s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m6s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 4m10s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Integration Tests (push) Successful in 6m5s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m42s
CI/CD Pipeline / Validate - Style (push) Successful in 6m35s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 7m11s
AI Code Review / AI Code Review (pull_request) Successful in 7m14s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m20s
CI/CD Pipeline / Validate - Security (push) Successful in 14m42s
CI/CD Pipeline / Unit Tests (push) Successful in 18m21s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
2026-10-14 19:58:37 +08:00
xiaoxia 500203db5d fix(viral-script): tone全链路暴露+ShotScript voiceover字段+删_safe_json_loads死代码(#2295追加) (#2296)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 6s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m10s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m29s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m19s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m7s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m30s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m24s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 4m2s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 18s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m44s
CI/CD Pipeline / Validate - Style (push) Successful in 4m52s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m11s
AI Code Review / AI Code Review (pull_request) Successful in 7m11s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m46s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m24s
CI/CD Pipeline / Validate - Security (push) Successful in 11m22s
CI/CD Pipeline / Unit Tests (push) Successful in 12m31s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 2m20s
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 29m11s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-14 18:42:45 +08:00
CI Bot 2a9089edb0 style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 10s
CI/CD Pipeline / Check push changed paths (push) Successful in 12s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 15s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m14s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m5s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m29s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 33s
CI/CD Pipeline / CI Gate (pull_request) Successful in 9s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 18s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m14s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m19s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m22s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m36s
CI/CD Pipeline / Integration Tests (push) Successful in 4m13s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m27s
CI/CD Pipeline / Validate - Style (push) Successful in 5m54s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m44s
AI Code Review / AI Code Review (pull_request) Successful in 7m17s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m27s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 8m37s
CI/CD Pipeline / Validate - Security (push) Successful in 14m12s
CI/CD Pipeline / Unit Tests (push) Successful in 15m2s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-14 10:22:44 +00:00
xiaoxia 6a3fbba813 feat(viral-script): 双格式parser+voiceover字段修复+全面审计 (#2295)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 4s
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 7s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 10s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 57s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m11s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 28s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 1m41s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m20s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 3m52s
CI/CD Pipeline / CI Gate (pull_request) Successful in 5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m50s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 7m10s
CI/CD Pipeline / Integration Tests (push) Successful in 7m37s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 8m11s
AI Code Review / AI Code Review (pull_request) Successful in 6m49s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 3m39s
CI/CD Pipeline / Unit 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 / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Validate - Security (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 / Validate - Style (push) Failing after 9m45s
2026-10-14 18:12:46 +08:00
xiaoxia 5e5a9b6cda fix(viral-script): v4编导分镜四叠bug(max_chars/超时强制/口播截断/兜底去重/few-shot) (#2294)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m5s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m28s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m42s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m11s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 27s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m20s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m16s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 3m52s
CI/CD Pipeline / Integration Tests (push) Successful in 4m26s
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Failing after 5m21s
CI/CD Pipeline / Validate - Style (push) Successful in 5m29s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m47s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m24s
AI Code Review / AI Code Review (pull_request) Successful in 7m12s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m14s
CI/CD Pipeline / Validate - Security (push) Successful in 10m30s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 7m1s
CI/CD Pipeline / Unit Tests (push) Successful in 12m58s
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-14 13:49:46 +08:00
xiaoxia ec2e2249fd fix(viral-video): prompt example_output 作为 few-shot 注入 LLM messages,修复v4编导级分镜提示词不生效 (#2293)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 48s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m19s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 56s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m3s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m29s
CI/CD Pipeline / Integration Tests (push) Successful in 3m38s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (push) Successful in 4m53s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m16s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 5m13s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 5m24s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 7m4s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 3m27s
CI/CD Pipeline / Validate - Security (push) Successful in 9m43s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m54s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m53s
CI/CD Pipeline / Unit Tests (push) Successful in 15m47s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 5m21s
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 11m0s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-14 13:09:04 +08:00
xiaoxia cfc584ad11 fix(ditto-emotion): GPU表情公式配套——全兜底置空+强度保底0.35+对齐固化 (#2292)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Check push changed paths (push) Successful in 7s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 52s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 57s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 53s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m29s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m27s
CI/CD Pipeline / Integration Tests (push) Successful in 3m27s
CI/CD Pipeline / Validate - Style (push) Successful in 4m17s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m38s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m46s
CI/CD Pipeline / Build Staging API Image (push) Successful in 4m54s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Successful in 17s
AI Code Review / AI Code Review (pull_request) Successful in 6m55s
CI/CD Pipeline / Validate - Security (push) Successful in 7m45s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 2m57s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m45s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m25s
CI/CD Pipeline / Unit Tests (push) Successful in 12m3s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m17s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-14 07:26:10 +08:00
xiaoxia afc89142c1 fix(ai-avatar): finalize 接口超时延长到120s (#2291)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m5s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 27s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m25s
CI/CD Pipeline / Build Staging API Image (push) Successful in 44s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m1s
CI/CD Pipeline / Validate - Style (push) Successful in 6m42s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m51s
AI Code Review / AI Code Review (pull_request) Successful in 6m51s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 3m22s
CI/CD Pipeline / Integration Tests (push) Successful in 6m39s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m40s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m16s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m13s
CI/CD Pipeline / Validate - Security (push) Successful in 12m47s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m45s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 4m53s
CI/CD Pipeline / Unit Tests (push) Successful in 14m58s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-14 06:29:41 +08:00
xiaoxia 6ee4d93d03 test(coverage): ai-avatar 页面组件加入覆盖率排除,修复 CI 门禁 (#2290)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 5s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 7s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 44s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m28s
CI/CD Pipeline / Build Staging API Image (push) Successful in 41s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Has been cancelled
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (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 / Retag skipped Staging API Image (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web 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 / Retag skipped 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 / Retag skipped Staging API Image (pull_request) Has been skipped
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 / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m8s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m30s
AI Code Review / AI Code Review (pull_request) Successful in 6m39s
test(coverage): ai-avatar 页面组件加入覆盖率排除,修复 CI 门禁
2026-10-13 23:29:25 +08:00
xiaoxia 5f0533e937 feat(ai-avatar): finalize 接口支持 cover_url/video_title 可选字段 + 兜底自动抽帧 (#2288)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 56s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m12s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m56s
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m8s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m23s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m4s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m38s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Integration Tests (push) Successful in 6m26s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m46s
CI/CD Pipeline / Validate - Style (push) Successful in 7m41s
AI Code Review / AI Code Review (pull_request) Successful in 7m31s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 8m25s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m55s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m36s
CI/CD Pipeline / Unit Tests (push) Successful in 15m16s
CI/CD Pipeline / Validate - Security (push) Successful in 44m27s
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m43s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 4m2s
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
10 个 finalize 路由单测 + 修复 blob URL 清空后未回退 job.output_cover_url 的优先级 bug
2026-10-13 22:42:59 +08:00
xiaoxia 01fa5dabc4 fix(ai-avatar): 修复选封面点完成后视频封面丢失/未入库 (#2289)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m25s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m21s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 3m29s
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 17s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m54s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 7m44s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 7m50s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 8m57s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 8m43s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 7m57s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m3s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m43s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m38s
CI/CD Pipeline / Integration Tests (push) Successful in 37m2s
CI/CD Pipeline / Validate - Style (push) Successful in 37m32s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 39m49s
CI/CD Pipeline / Unit Tests (push) Successful in 46m40s
CI/CD Pipeline / Validate - Security (push) Successful in 1h1m59s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
fix(ai-avatar): 修复选封面点完成后视频封面丢失/未入库

根因:finalize 请求体为空,cover_config 只在 submitRender 阶段传空对象,本地上传封面未走 OSS 直传导致后端拿 blob URL 无效。

前端改动:
- finalizeRenderJob 支持传 {cover_url}
- AiAvatarCoverConfig 新增 upload_file 保存本地 File
- 本地上传封面先走 uploadAssetDirect OSS 直传,finalize 带 cover_url
- smart-cover 路径 finalize 不传(后端 /smart-cover 已写入)
- 未选封面时 finalize 不传 body,后端兜底抽帧
- 移除必须选封面的阻塞校验
2026-10-13 21:19:17 +08:00
xiaoxia 9d42b6e2a9 feat(viral-video): fusion_level文案模式分流 + 新增marketing_purpose_hint/target_customer_hint/image_count模板变量 (#2287)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m5s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m21s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m32s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m8s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 3m35s
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m55s
CI/CD Pipeline / Validate - Style (push) Successful in 5m13s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m23s
CI/CD Pipeline / Build Staging API Image (push) Successful in 6m22s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 6m39s
AI Code Review / AI Code Review (pull_request) Successful in 6m58s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m21s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m59s
CI/CD Pipeline / Validate - Security (push) Successful in 12m52s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m47s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 4m56s
CI/CD Pipeline / Integration Tests (push) Successful in 13m23s
CI/CD Pipeline / Unit Tests (push) Successful in 13m52s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-13 18:17:24 +08:00
xiaoxia 4364bc951d feat(viral-video): 角色三视图持久化到OSS(替代临时URL) (#2286)
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 6s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m40s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m55s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m25s
CI/CD Pipeline / Integration Tests (push) Successful in 4m5s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m39s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m18s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m24s
CI/CD Pipeline / Validate - Style (push) Successful in 4m32s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m17s
CI/CD Pipeline / Validate - Security (push) Successful in 6m13s
AI Code Review / AI Code Review (pull_request) Successful in 7m1s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 6m56s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m59s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m44s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 3m54s
CI/CD Pipeline / Unit Tests (push) Successful in 12m24s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 45s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 52s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m9s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m44s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 5m22s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 8m46s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 12m37s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-13 02:42:22 +08:00
xiaoxia 495506e2a1 feat(viral-video): MVP 自动生成角色三视图绕过肖像审核(50411) (#2285)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 54s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m8s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m19s
CI/CD Pipeline / CI Gate (pull_request) Successful in 6s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m12s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m26s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 2m56s
CI/CD Pipeline / Integration Tests (push) Successful in 3m0s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m35s
CI/CD Pipeline / Validate - Style (push) Successful in 3m37s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m35s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m31s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 4m54s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m56s
CI/CD Pipeline / Validate - Security (push) Successful in 6m58s
AI Code Review / AI Code Review (pull_request) Successful in 7m0s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 4m20s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 4m35s
CI/CD Pipeline / Unit Tests (push) Successful in 15m46s
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-13 02:14:32 +08:00
xiaoxia 749cee9838 refactor(viral-video): 删除intent_parsing死代码和旧一键流水线 (#2284)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m26s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m42s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m27s
CI/CD Pipeline / CI Gate (pull_request) Successful in 14s
CI/CD Pipeline / Build Staging API Image (push) Successful in 3m41s
CI/CD Pipeline / Integration Tests (push) Successful in 4m46s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m38s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m7s
CI/CD Pipeline / Validate - Style (push) Successful in 5m10s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 5m11s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Security (push) Successful in 6m45s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m26s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 7m33s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 7m46s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m55s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m31s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 3m38s
AI Code Review / AI Code Review (pull_request) Successful in 11m13s
CI/CD Pipeline / Unit Tests (push) Successful in 13m11s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
2026-10-13 00:35:40 +08:00
xiaoxia 35f04f746b Merge pull request 'fix(viral-video): seed脚本 engine 获取修复' (#2283) from fix/viral-video-seed-script-engine into develop
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 57s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 48s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m17s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m25s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 42s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 2m51s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m31s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m18s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Successful in 1m29s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m49s
CI/CD Pipeline / Integration Tests (push) Successful in 5m14s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 5m9s
CI/CD Pipeline / Validate - Style (push) Successful in 6m5s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m27s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m14s
AI Code Review / AI Code Review (pull_request) Successful in 7m1s
CI/CD Pipeline / Unit Tests (push) Successful in 13m17s
CI/CD Pipeline / Validate - Security (push) Successful in 17m18s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-12 21:22:44 +08:00
CI Bot 8ea3e26ab6 style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 0s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m41s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m2s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m47s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m34s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m52s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m50s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m17s
AI Code Review / AI Code Review (pull_request) Successful in 7m7s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 7m22s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 9m9s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 13m55s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m41s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 19m6s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m2s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 26s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m9s
2026-10-12 12:47:59 +00:00
CI Bot 769590a525 fix(seed): 补全 marketing_purpose/target_customer 两类默认seed数据(共52条)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m51s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 15s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 43s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Successful in 6m3s
AI Code Review / AI Code Review (pull_request) Successful in 6m41s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 2m37s
- marketing_purpose 20 条(对应前端 PURPOSES 枚举:获客引流/口播带货/对话短剧等)
- target_customer 8 条(对应前端 TARGET_CUSTOMERS 枚举:小镇青年/Z世代/精致妈妈等)
- seed总数:persona×12 + structure×5 + language×7 + marketing_purpose×20 + target_customer×8 = 52条
- 每条guide_text按该分类的创作指导风格撰写,前端选对应选项时精确命中DB返回专业hint
- 代码内关键词模糊兜底逻辑继续保留,未命中DB的自定义输入仍可正常工作
2026-10-12 20:37:45 +08:00
CI Bot 3df88c48fd fix(seed): 修复 viral_video_param_guides seed 脚本 engine 获取逻辑(_session_mod.engine 不存在导致 AttributeError)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m29s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m47s
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m28s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
- 删除对 packages.adapters.sqlalchemy_impl.session.engine 的引用(session 模块只有 build_engine 工厂,无全局 engine)
- 改为与 seed_viral_video_prompts.py 一致:优先 DATABASE_URL 环境变量,fallback 到 get_shared_settings().database_url
- driver 统一为 postgresql+psycopg2://(与其他 seed 脚本一致)
- 不影响线上运行(代码内置 _DEFAULT_* 兜底),生产部署时必须用此修复版 seed
2026-10-12 20:33:56 +08:00
xiaoxia 6ab953ae2a Merge pull request 'refactor(viral-video): PR3 后端P2 统一异常+清理' (#2281) from refactor/viral-video-backend-p2-errors-and-cleanup into develop
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m7s
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m28s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m32s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 23s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 32s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m26s
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m24s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 3m10s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m23s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m47s
CI/CD Pipeline / Staging E2E Tests (push) Successful in 2m20s
CI/CD Pipeline / Validate - Style (push) Successful in 6m11s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m24s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m13s
AI Code Review / AI Code Review (pull_request) Successful in 7m0s
CI/CD Pipeline / Integration Tests (push) Failing after 8m18s
CI/CD Pipeline / Unit Tests (push) Successful in 13m29s
CI/CD Pipeline / Validate - Security (push) Successful in 19m22s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
2026-10-12 20:22:48 +08:00
xiaoxia 73294c6440 Merge pull request 'refactor(web/viral-video): PR6 前端P2 CSS瘦身+打磨' (#2282) from refactor/viral-video-frontend-p2-css-and-polish into develop
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 5s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 7s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 9s
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m13s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m25s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m57s
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m23s
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 / Retag skipped Staging Web Image (pull_request) Has been skipped
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 / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + 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 / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 5s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 7m6s
2026-10-12 20:10:55 +08:00
xiaoxia c96b6de053 test(viral-video): fix mock leakage in p2_errors tests (monkeypatch restore)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m38s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 56s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m57s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m11s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 5m23s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 5m45s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 4m49s
AI Code Review / AI Code Review (pull_request) Successful in 6m36s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 11m11s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 15m11s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 15m5s
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m21s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 3m53s
2026-10-12 20:04:36 +08:00
xiaoxia 02660b063c Merge pull request 'ci(ditto): DITTO_EMOTION_TIMEOUT secret 注入部署渲染链路' (#2280) from fix/ditto-timeout-ci-config into develop
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 55s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 50s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m27s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 42s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m3s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m19s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 3m50s
CI/CD Pipeline / Integration Tests (push) Successful in 4m53s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m10s
AI Code Review / AI Code Review (pull_request) Successful in 7m4s
CI/CD Pipeline / Validate - Style (push) Successful in 7m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m45s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m45s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 4m23s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m53s
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Build Production API 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 / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
2026-10-12 20:01:33 +08:00
xiaoxia fd977991bd style(web): prettier --write 5 files (CI lint)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 12s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m33s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m12s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 6m54s
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m28s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m31s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m53s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 6m19s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 10m49s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 11m25s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 16m8s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 45s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 3m27s
2026-10-12 19:38:48 +08:00
CI Bot ce9da8cbfe style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 9s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 13s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m16s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 21s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m18s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 6m37s
AI Code Review / AI Code Review (pull_request) Successful in 6m44s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 6m57s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 6m41s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m43s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 14m39s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 16m57s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m10s
2026-10-12 11:38:03 +00:00
agent e9128449ef refactor(viral-video): PR3 P2 errors+cleanup (统一异常/兜底文案/Seedance拆分/常量/param_guides DB)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m3s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 15s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m2s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 43s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 6m56s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
- 新建 packages/domain/viral_video/ 包(原 viral_video.py 拆为 entities.py + __init__.py 重导出)
- 新增 errors.py: ViralVideoError 基类 + Config/Upstream/Compliance/Resource/Credits/Timeout/LLM 7 个子类
- 新增 stages.py: STAGE_* / PROGRESS_* 阶段/进度常量集中维护
- worker viral_video.py 主路径 RuntimeError/ValueError → ViralVideo* 异常;
  顶层 except 拆 ViralVideoError 分支,用 user_message 做前端展示不打堆栈
- _fallback_script 去掉8条硬编码营销套话,改为"来看看{brand}家的{name}"等诚实短文案
- _step_video_analysis ImportError 不再返回占位假风格 dict,返回 None 下游跳过
- _assemble_seedance_prompt 拆分:抽取 _SEEDANCE_* / _WAN_* 常量 + _build_style_section + _build_character_section,主函数减 70+ 行,输出不变
- prompt_loader.py 4处静默 except 改为首次 warning(_warned set + 5min 冷却去重)
- 新建 packages/application/viral_video/param_guides.py: DB 读 viral_video_param_guides,30s TTL 缓存
- worker 5个 hint 函数(persona/structure/marketing_purpose/target_customer/language)改为 DB 优先 + _DEFAULT_* 代码兜底
- 新增迁移 109_add_viral_video_param_guides.py(5种 guide_type, UNIQUE(type,key))
- 新增 scripts/seed_viral_video_param_guides.py 幂等灌入默认值
- 新增 tests/unit/test_viral_video_p2_errors.py 6 个测试,修复既有测试引用 _PERSONA_STYLE_GUIDE → _DEFAULT_PERSONA_HINTS

测试:pytest tests/unit/ -k viral → 254 passed, 2 skipped
验收:worker/API import ok;alembic heads → 109 (head)
2026-10-12 19:25:48 +08:00
CI Bot 90ec76ce92 refactor(web/viral-video): PR6 P2清理 - CSS瘦身/inlie style迁移/抖音空功能删除/短剧提示条迁移/GENDER_LABEL修复/console清理
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m12s
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m23s
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m18s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m23s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
- GENDER_LABEL: 修复"女声·女声"/"男声·男声"重复拼接bug,改为"女声"/"男声"
- douyin空功能清理: 删除handleDouyinUrl空函数、抖音链接识别按钮/输入框UI、douyinInput state、VoicePanel中'douyin'分支、RefVideo.douyinUrl字段
- 短剧提示条: 从ViralVideoPage底部(grid外)迁移到Step2CopyPanel内部就地显示(选短剧类营销目的时显示),inline style抽为.vv-shortdrama-hint class,文案改为'💡 短剧模式已选中,对手戏角色将自动分配配角音色'
- copyError持久显示: 合规审核失败错误条从step3列移到Step2CopyPanel文案区持久显示(原只toast 3秒+step3列显示,不在文案区就近)
- CSS清理: 删除未使用class约25个(v1残留/旧结构),CSS 2274→2157行(-117行)
- inline style迁移CSS: speaker标签(.vv-sb-speaker-tag-main/guest)、spinner、voice空态、loading图标、margin/font-size helpers等静态多属性inline style抽为class
- console清理: 删除2处console.warn调试日志
- 不动API契约/三列等高布局/CSS Module,复用ErrorRetryBar/VVSpinner组件

tsc --noEmit: 0 error
2026-10-12 19:14:34 +08:00
67 changed files with 4756 additions and 1333 deletions
@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
"""viral_video_param_guides 表:运营可配置的人设/结构/营销目的/目标客群/语言指导文案(PR3 P2-h)。
Revision ID: 109_add_viral_video_param_guides
Revises: 108_storyboard_v4_multivoice
Create Date: 2026-10-12
表用途:
- 替代 worker 中 ~130 行硬编码 if/elif mapping;
- 5 类 guide_type: persona / structure / marketing_purpose / target_customer / language;
- 启动时由 seed_viral_video_param_guides.py 灌入代码默认值,运营可在后台改,30s TTL 缓存自动热加载。
"""
from alembic import op
revision = "109_add_viral_video_param_guides"
down_revision = "108_storyboard_v4_multivoice"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute("""
CREATE TABLE IF NOT EXISTS viral_video_param_guides (
id SERIAL PRIMARY KEY,
guide_type VARCHAR(32) NOT NULL,
guide_key VARCHAR(64) NOT NULL,
guide_text TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE (guide_type, guide_key)
)
""")
op.execute("""
CREATE INDEX IF NOT EXISTS ix_viral_video_param_guides_type_key
ON viral_video_param_guides (guide_type, guide_key)
""")
def downgrade() -> None:
op.execute("DROP INDEX IF EXISTS ix_viral_video_param_guides_type_key")
op.execute("DROP TABLE IF EXISTS viral_video_param_guides")
@@ -0,0 +1,23 @@
"""add tone column to viral_video_jobs
Revision ID: 110
Revises: 109_add_viral_video_param_guides
Create Date: 2026-10-14
"""
from alembic import op
# revision identifiers, used by Alembic.
revision = "110"
down_revision = "109_add_viral_video_param_guides"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute("ALTER TABLE viral_video_jobs ADD COLUMN IF NOT EXISTS tone VARCHAR(50) NOT NULL DEFAULT ''")
def downgrade() -> None:
op.execute("ALTER TABLE viral_video_jobs DROP COLUMN IF EXISTS tone")
+58 -4
View File
@@ -18,6 +18,7 @@ from app.dependencies import get_db_session
from app.schemas.ai_avatar_render import (
AiAvatarRenderJobResponse,
CreateAiAvatarRenderRequest,
FinalizeRenderRequest,
FinalizeRenderResponse,
SmartCoverResponse,
)
@@ -26,7 +27,7 @@ from app.services.ai_avatar_render_service import (
AiAvatarRenderError,
AiAvatarRenderService,
)
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Body, Depends, HTTPException, Query
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
@@ -267,13 +268,16 @@ def generate_render_smart_cover(
@router.post("/{job_id}/finalize", response_model=FinalizeRenderResponse)
def finalize_render_job(
job_id: str,
body: FinalizeRenderRequest = Body(default=FinalizeRenderRequest()),
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""用户完成封面选择后,将视频正式保存到成片库.
- 必须等渲染任务 completed 后才可调用
- 如果已通过 smart-cover/custom-cover 设置了封面,会自动带上
- body.cover_url:用户选定的封面公网 URL(本地上传需前端先走 OSS 直传拿公网 URL,blob:/data: URL 无效);
为空时优先使用 smart-cover 已写入的 output_cover_url,仍为空则兜底自动抽一帧
- body.video_title:成片库展示标题;为空用默认 AI数字人_{job_id[:8]}
- 返回成片库视频ID
- 幂等:已 finalize 的任务重复调用会返回 existing 记录
"""
@@ -285,6 +289,9 @@ def finalize_render_job(
raise HTTPException(status_code=404, detail="渲染任务不存在")
if job.status != "completed":
raise HTTPException(status_code=400, detail="请先完成视频生成")
video_url = (job.output_video_url or "").strip()
if not video_url:
raise HTTPException(status_code=400, detail="渲染成片视频 URL 为空,无法入库")
# 幂等检查(通过 generation_task_id=job_id 识别,finalize_job 内部也做了一次,这里提前返回简化)
from packages.adapters.sqlalchemy_impl.models import GeneratedVideoModel
@@ -304,11 +311,58 @@ def finalize_render_job(
status="already_finalized",
)
# 封面 URL 解析:按优先级 body.cover_url → job.output_cover_url(smart-cover)→ 兜底自动抽帧,
# 每一级都要校验是公网 HTTP(S) URL(拒绝 blob:/data: 等后端无法访问的 URL)
def _is_valid_public_url(u: str) -> bool:
return bool(u) and u.startswith(("http://", "https://"))
cover_url = ""
for candidate, source in [
((body.cover_url or "").strip(), "body"),
((job.output_cover_url or "").strip(), "job"),
]:
if _is_valid_public_url(candidate):
cover_url = candidate
break
if candidate:
logger.warning(
"finalize 收到无效封面URL(非HTTP,跳过该来源): job_id=%s source=%s url=%s",
job_id,
source,
candidate[:80],
)
# 兜底:body 和 job 都没有可用封面时,后端主动从成片抽一帧
if not cover_url:
try:
fallback_url = (generate_smart_cover(video_url, job_id=job_id, max_frames=5) or "").strip()
except Exception as exc:
logger.warning("finalize 兜底抽帧失败(不阻断入库): job_id=%s err=%s", job_id, exc)
fallback_url = ""
if _is_valid_public_url(fallback_url):
cover_url = fallback_url
try:
job.cover_config = {
**(job.cover_config if isinstance(job.cover_config, dict) else {}),
"mode": "auto_frame",
"url": cover_url,
}
job.output_cover_url = cover_url
db.commit()
except Exception as exc:
logger.warning("finalize 兜底封面写回DB失败(不阻断入库): job_id=%s err=%s", job_id, exc)
elif fallback_url:
logger.warning(
"finalize 兜底抽帧返回了非HTTP URL(忽略): job_id=%s url=%s", job_id, fallback_url[:80]
)
video_title = (body.video_title or "").strip() or None
try:
video = svc.finalize_job(job_id, current_user.user.id)
video = svc.finalize_job(job_id, current_user.user.id, cover_url=cover_url or None, video_title=video_title)
return FinalizeRenderResponse(
video_id=video.id,
cover_url=video.thumbnail_url or job.output_cover_url or "",
cover_url=video.thumbnail_url or cover_url or "",
status="success",
)
except AiAvatarRenderError as exc:
+6 -104
View File
@@ -2,12 +2,10 @@
v1.6 三步分步流水线端点(单次 Seedance 出片版):
POST /api/v1/viral-video/analyze-images 阶段1:创建任务 + 仅做图片/视频分析,暂停在 image_analyzed
POST /api/v1/viral-video/{job_id}/generate-copy 阶段2:用户填完参数后跑意图+文案+分镜+审核,暂停在 copy_generated
POST /api/v1/viral-video/{job_id}/confirm-copy 阶段3:用户确认/编辑文案后跑渲染,直到完成
POST /api/v1/viral-video/{job_id}/generate-copy 阶段2:用户填完参数后文案+分镜一步生成+审核,暂停在 copy_generated
POST /api/v1/viral-video/{job_id}/confirm-copy 阶段3:用户确认/编辑文案后跑渲染(TTS+Seedance+上传),直到完成
旧端点(兼容保留,旧前端/一键生成模式):
POST /api/v1/viral-video/generate 一键入队,前半段跑到 wait_user_confirm
POST /api/v1/viral-video/{job_id}/confirm-intent 旧的意图确认后继续渲染
意图分析已并入脚本生成一步完成,不再有独立的 intent_parsing 阶段或 wait_user_confirm 状态。
通用:
GET /api/v1/viral-video/{job_id} 查询任务状态(含 image_analysis/copy_result 编导脚本)
@@ -30,8 +28,6 @@ from app.schemas.viral_video import (
AnalyzeStyleRequest,
AnalyzeStyleResponse,
ConfirmCopyRequest,
ConfirmIntentRequest,
CreateViralVideoRequest,
CreditsFormulaBreakdown,
EstimateCreditsRequest,
EstimateCreditsResponse,
@@ -75,7 +71,7 @@ def _build_copy_result(job) -> dict | None:
"""v1.6: 返回编导分镜脚本 CopyResult 结构(给前端/Seedance 使用)。
- 若 job.copy_result 已持久化(v1.6 worker 生成),直接返回(补 final_copy 兜底)。
- 否则从老字段(generated_copy_text=口播, storyboard=分镜列表, intent_result)拼装兼容结构。
- 否则从老字段(generated_copy_text=口播, storyboard=分镜列表)拼装兼容结构。
"""
cr = getattr(job, "copy_result", None)
if isinstance(cr, dict) and cr:
@@ -89,12 +85,9 @@ def _build_copy_result(job) -> dict | None:
# 兼容 v1.5 老数据:storyboard 是老格式 [{order,type,description,text,duration,...}]
copy_text = getattr(job, "generated_copy_text", "") or ""
sb = getattr(job, "storyboard", None) or []
intent = getattr(job, "intent_result", None) or {}
if not copy_text and not sb:
return None
title = ""
if isinstance(intent, dict):
title = intent.get("suggested_title") or intent.get("intent", "") or ""
shots = []
for seg in sb:
if isinstance(seg, dict):
@@ -134,6 +127,7 @@ def _to_response(job) -> ViralVideoJobResponse:
persona_id=job.persona_id,
viral_structure=job.viral_structure,
marketing_purpose=job.marketing_purpose,
tone=getattr(job, "tone", "") or "",
bgm_preference=job.bgm_preference,
duration=job.duration or 15,
user_copy_text=job.user_copy_text,
@@ -155,7 +149,6 @@ def _to_response(job) -> ViralVideoJobResponse:
language=getattr(job, "language", "zh-CN") or "zh-CN",
video_ratio=getattr(job, "video_ratio", "9:16") or "9:16",
video_model=getattr(job, "video_model", "") or "",
intent_result=job.intent_result,
result_video_url=job.result_video_url,
pre_trusted_images=getattr(job, "pre_trusted_images", None) or None,
video_resolution=getattr(job, "video_resolution", "720p") or "720p",
@@ -331,57 +324,6 @@ def _settle_viral_video_credits(
# ── Endpoints ────────────────────────────────────────────────────────────
@router.post("/generate", response_model=ViralVideoJobResponse)
def create_viral_video(
request: CreateViralVideoRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
session: Session = Depends(get_db_session),
) -> ViralVideoJobResponse:
"""创建爆款视频任务,入队 Celery 编排器。"""
from packages.domain.viral_video import ViralVideoJob
repo = _get_job_repo(session)
# 创建领域实体
job = ViralVideoJob(
user_id=authenticated_user.user.id,
images=list(request.images),
industry=request.industry,
target_customer=request.target_customer,
persona_id=request.persona_id,
viral_structure=request.viral_structure,
marketing_purpose=request.marketing_purpose,
bgm_preference=request.bgm_preference,
duration=request.duration or 15,
user_copy_text=request.user_copy_text,
fusion_level=request.fusion_level,
reference_audio_path=request.reference_audio_path,
reference_video_url=request.reference_video_url,
style_strength=request.style_strength,
style_template_id=request.style_template_id,
voice_id=getattr(request, "voice_id", "") or "",
voice_source=getattr(request, "voice_source", "") or "",
video_ratio=getattr(request, "video_ratio", "9:16") or "9:16",
video_model=getattr(request, "video_model", "") or "",
video_resolution=getattr(request, "video_resolution", "720p") or "720p",
copy_result=None,
)
# 持久化
repo.save(job)
# 入队 Celery 任务
try:
celery_app.send_task("worker.run_viral_video_pipeline", args=[job.id])
logger.info("[爆款视频] 任务已入队: job_id=%s user_id=%s", job.id, job.user_id)
except Exception as e:
logger.error("[爆款视频] 入队失败: %s", e, exc_info=True)
job.mark_failed(f"任务入队失败: {e}")
repo.update(job)
return _to_response(job)
@router.post("/analyze-images", response_model=ViralVideoJobResponse)
def analyze_images(
request: AnalyzeImagesRequest,
@@ -463,6 +405,7 @@ def generate_copy(
job.persona_id = request.persona_id or job.persona_id
job.viral_structure = request.viral_structure or job.viral_structure
job.marketing_purpose = request.marketing_purpose or job.marketing_purpose
job.tone = getattr(request, "tone", "") or job.tone
job.bgm_preference = request.bgm_preference or job.bgm_preference
if request.duration:
job.duration = max(5, min(30, int(request.duration)))
@@ -766,7 +709,6 @@ def retry_viral_video_job(
job.phase_message = ""
job.heartbeat_at = None
# 重试时清空上次产物(参考 resume_from_image_analyzed 逻辑)
job.intent_result = None
job.copy_result = None
job.storyboard = None
job.generated_copy_text = ""
@@ -794,43 +736,6 @@ def retry_viral_video_job(
return _to_response(job)
@router.post("/{job_id}/confirm-intent", response_model=ViralVideoJobResponse)
def confirm_intent(
job_id: str,
request: ConfirmIntentRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
session: Session = Depends(get_db_session),
) -> ViralVideoJobResponse:
"""用户确认/修改 AI 生成的意图文案,恢复流水线。"""
repo = _get_job_repo(session)
job = repo.get(job_id)
if job is None:
raise HTTPException(status_code=404, detail="任务不存在")
if job.user_id != authenticated_user.user.id:
raise HTTPException(status_code=403, detail="无权操作此任务")
if job.status != ViralVideoStatus.WAIT_USER_CONFIRM:
raise HTTPException(status_code=409, detail="任务当前不在等待确认状态")
# 更新文案
if request.confirmed_copy:
job.user_copy_text = request.confirmed_copy
# 恢复流水线
job.resume_from_confirm()
repo.update(job)
# 从断点恢复 Celery 任务
try:
celery_app.send_task("worker.resume_viral_video_pipeline", args=[job.id])
logger.info("[爆款视频] 意图确认,恢复流水线: job_id=%s", job.id)
except Exception as e:
logger.error("[爆款视频] 恢复流水线失败: %s", e, exc_info=True)
job.mark_failed(f"恢复流水线失败: {e}")
repo.update(job)
return _to_response(job)
@router.post("/{job_id}/analyze-style", response_model=AnalyzeStyleResponse)
def analyze_style(
job_id: str,
@@ -1097,7 +1002,6 @@ _STATUS_STAGE = {
"running": "",
"image_analyzed": "image_analysis",
"copy_generated": "review",
"wait_user_confirm": "intent_parsing",
"completed": "uploading",
"failed": "",
"cancelled": "",
@@ -1108,7 +1012,6 @@ _STATUS_PROGRESS = {
"running": 5.0,
"image_analyzed": 15.0,
"copy_generated": 70.0,
"wait_user_confirm": 35.0,
"completed": 100.0,
"failed": 0.0,
"cancelled": 0.0,
@@ -1119,7 +1022,6 @@ _STATUS_MESSAGE = {
"running": "任务执行中",
"image_analyzed": "图片分析完成,等待填写营销参数",
"copy_generated": "文案与分镜已生成,等待确认文案",
"wait_user_confirm": "等待用户确认意图文案",
"completed": "视频生成完成",
"failed": "任务失败",
"cancelled": "任务已取消",
+9
View File
@@ -124,6 +124,15 @@ class SmartCoverResponse(BaseModel):
message: str = Field("", description="失败原因(如有)")
class FinalizeRenderRequest(BaseModel):
"""finalize 入库请求体(所有字段可选,缺省时用渲染任务自身状态)."""
cover_url: Optional[str] = Field(
None, description="用户选定的封面公网 URL(必须是 OSS/HTTP(S) 公网地址,blob:/data: URL 无效)"
)
video_title: Optional[str] = Field(None, description="成片库展示标题;为空时用默认 AI数字人_{job_id[:8]}")
class FinalizeRenderResponse(BaseModel):
"""封面选好后点「完成」,正式入库成片库的响应."""
+3 -50
View File
@@ -13,7 +13,6 @@ VALID_STYLE_STRENGTHS = ("light", "medium", "strict")
VALID_STAGES = (
"image_analysis",
"video_analysis",
"intent_parsing",
"script_generation",
"review",
"tts",
@@ -37,6 +36,7 @@ class ShotScript(BaseModel):
action_details: str = Field(default="", description="人物动作、表情、物品操作细节")
audio_bgm: str = Field(default="", description="环境音+BGM提示")
transition: str = Field(default="硬切", description="转场方式:硬切/淡入淡出/叠化")
voiceover: str = Field(default="", description="该镜头口播台词(纯对白,不含画面描述)")
reference_image_index: int | None = Field(
default=None, description="参考图片索引(0-based,对应上传的第几张产品图)"
)
@@ -68,47 +68,6 @@ class CopyResult(BaseModel):
# -- Request Schemas --
class CreateViralVideoRequest(BaseModel):
"""旧接口:一键创建(保留兼容)。"""
images: list[str] = Field(..., min_length=1, max_length=20)
industry: str = ""
target_customer: str = ""
persona_id: str = ""
viral_structure: str = ""
marketing_purpose: str = ""
bgm_preference: str = ""
duration: int = Field(default=15, ge=5, le=30, description="视频时长(秒),5-30")
user_copy_text: str = ""
fusion_level: str = "ai_polish"
reference_audio_path: str = ""
reference_video_url: str = ""
style_strength: str = "medium"
style_template_id: str = ""
voice_id: str = ""
voice_source: str = ""
language: str = "zh-CN"
video_ratio: str = "9:16"
video_model: str = ""
video_resolution: str = "720p"
@field_validator("fusion_level")
@classmethod
def _v_fl(cls, v: str) -> str:
if v == "full_ai":
return "ai_full"
if v not in VALID_FUSION_LEVELS:
raise ValueError(f"fusion_level must be one of {VALID_FUSION_LEVELS}")
return v
@field_validator("style_strength")
@classmethod
def _v_ss(cls, v: str) -> str:
if v not in VALID_STYLE_STRENGTHS:
raise ValueError(f"style_strength must be one of {VALID_STYLE_STRENGTHS}")
return v
class AnalyzeImagesRequest(BaseModel):
"""v1.5+ 阶段1:创建任务 + 图片/视频分析。"""
@@ -133,6 +92,7 @@ class GenerateCopyRequest(BaseModel):
persona_id: str = ""
viral_structure: str = ""
marketing_purpose: str = ""
tone: str = ""
bgm_preference: str = ""
duration: int = Field(default=15, ge=5, le=30)
user_copy_text: str = ""
@@ -179,13 +139,6 @@ class ConfirmCopyRequest(BaseModel):
language: str | None = Field(default=None, description="用户选定的语言(confirm时可选)")
class ConfirmIntentRequest(BaseModel):
"""旧 confirm-intent(兼容)。"""
confirmed_copy: str = ""
adjustments: str = ""
class AnalyzeStyleRequest(BaseModel):
reference_video_url: str = Field(..., description="参考视频 URL")
style_template_id: str = ""
@@ -205,6 +158,7 @@ class ViralVideoJobResponse(BaseModel):
persona_id: str = ""
viral_structure: str = ""
marketing_purpose: str = ""
tone: str = ""
bgm_preference: str = ""
duration: int = 15
user_copy_text: str = ""
@@ -231,7 +185,6 @@ class ViralVideoJobResponse(BaseModel):
language: str = "zh-CN"
video_ratio: str = "9:16"
video_model: str = ""
intent_result: dict | None = None
result_video_url: str = ""
pre_trusted_images: list[str] | None = None
video_resolution: str = "720p"
@@ -420,19 +420,25 @@ class AiAvatarRenderService:
logger.exception("渲染任务异常 [%s]", job_id)
raise
def _persist_to_library(self, job: AiAvatarRenderJob, cover_url: Optional[str] = None):
def _persist_to_library(
self,
job: AiAvatarRenderJob,
cover_url: Optional[str] = None,
video_title: Optional[str] = None,
):
"""将渲染结果写入成片库,返回 GeneratedVideo 领域对象.
Args:
job: 渲染任务(必须 status=completed 且 output_video_url 非空)
cover_url: 可选的封面 URL 覆盖(finalize 时传入即优先使用,否则取 job.output_cover_url)
video_title: 可选的成片标题覆盖;为空用默认 AI数字人_{job.id[:8]}
"""
from packages.adapters.sqlalchemy_impl.generated_video_repository import (
SQLAlchemyGeneratedVideoRepository,
)
from packages.domain.generated_video import GeneratedVideo
clip_name = f"AI数字人_{job.id[:8]}"
clip_name = (video_title or "").strip() or f"AI数字人_{job.id[:8]}"
# AI数字人入口是独立页面,前端可能不传 project_id(无项目概念),
# 兜底为 "ai_avatar" 避免 DB 非空约束/查询问题;generation_task_id 用 render_job_id 便于反查。
clip_project_id = (job.project_id or "").strip() or "ai_avatar"
@@ -456,11 +462,18 @@ class AiAvatarRenderService:
logger.info("[数字人渲染] 成片已入库: clip_id=%s render_job=%s", clip.id, job.id)
return clip
def finalize_job(self, job_id: str, user_id: str, cover_url: Optional[str] = None):
def finalize_job(
self,
job_id: str,
user_id: str,
cover_url: Optional[str] = None,
video_title: Optional[str] = None,
):
"""用户在前端点「完成」后调用:将已 completed 的渲染任务正式入库到成片库.
- 必须 status=completed 才可调用
- cover_url 若传入则优先使用并回写 job.output_cover_url;否则使用 job.output_cover_url(smart-cover/custom-cover 已写入)
- video_title 若传入则作为成片库展示标题;否则使用默认 AI数字人_{job_id[:8]}
- 幂等:已入库则返回已存在的 GeneratedVideo
"""
from packages.adapters.sqlalchemy_impl.generated_video_repository import (
@@ -499,7 +512,7 @@ class AiAvatarRenderService:
job.updated_at = datetime.now(UTC)
self.db.commit()
return self._persist_to_library(job, cover_url=cover_url)
return self._persist_to_library(job, cover_url=cover_url, video_title=video_title)
def _download_video(self, url: str) -> str:
"""下载视频到临时文件."""
+6
View File
@@ -623,6 +623,12 @@ class LipsyncService:
# 预合成模式:script_text 可空(因为 timings 已自带句子文本),但仍建议传
if not isinstance(sentence_timings, list) or len(sentence_timings) == 0:
raise MediaKitError("预合成模式 sentence_timings 不能为空", code="InvalidInput")
# 文案为空时从 sentence_timings 拼接兜底(Ditto 情绪分析依赖 script_text;
# 前端 #1875 起预合成 payload 不带 script_text,导致 job.script_text 落库为空)
if not (script_text or "").strip():
script_text = "".join(
str(item.get("text") or "") for item in sentence_timings if isinstance(item, dict)
).strip()
# 0.5 lip_sync 计费预扣(全局 points 开关 + 功能开关均开才扣)
prepaid_credits = 0.0
+47 -7
View File
@@ -4,17 +4,57 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 标题字体(#2001 / #font-selection 修复):Google Fonts CDN 引入中文字体,保证优设标题黑/抖音美好体/阿里普惠体等fallback可用 -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&family=ZCOOL+KuaiLe&family=ZCOOL+XiaoWei&family=ZCOOL+QingKe+HuangYou&family=Ma+Shan+Zheng&family=Long+Cang&family=Liu+Jian+Mao+Cao&family=Zhi+Mang+Xing&display=swap"
rel="stylesheet"
/>
<!-- 标题字体(#2001 / #font-selection 修复):Google Fonts 中文字体完全延迟加载
关键设计:所有 Google Fonts 子资源(CSS + 字体文件)必须在 window load 之后才发起请求。
- 原因:fonts.googleapis.com/gstatic.com 在境内访问间歇性挂起,若在关键路径内
(<link rel=stylesheet>/preload/preconnect 等任何会被计入文档子资源完成的形式),
会拖死 window.load 事件,导致 Playwright page.goto 默认 waitUntil:'load' 90s 超时,
同时真实用户首屏也可能因等字体而白屏。
- 实现:head 里完全不出现 Google 域的 <link>,首屏用系统字体兜底
(font-family fallback 链:Noto Sans SC → PingFang SC → Microsoft YaHei → sans-serif);
页面 load 后通过 requestIdleCallback 动态注入 <link rel=stylesheet>,字体异步加载生效。
- noscript 降级:JS 禁用时仍然阻塞加载(可接受,这类用户极少)。
- 参考:运维 2026-10-14 E2E 失败根因分析,#2300 preload/media=print 方案不治本,
preload 与 media=print 样式表仍计入子资源完成,会阻塞 load 事件。 -->
<title>小虾 SaaS - 自动化视频剪辑平台</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<!-- Google Fonts 延迟注入:必须在 main.tsx 之后,用独立 <script>(非 module)在页面解析
完成时注册 load 回调;window.load 触发后再 requestIdleCallback 插 <link>,
确保字体请求不进入首屏关键路径,也不阻塞 Playwright load 等待 -->
<script>
;(function () {
var FONT_HREF =
"https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&family=ZCOOL+KuaiLe&family=ZCOOL+XiaoWei&family=ZCOOL+QingKe+HuangYou&family=Ma+Shan+Zheng&family=Long+Cang&family=Liu+Jian+Mao+Cao&family=Zhi+Mang+Xing&display=swap"
function inject() {
if (document.getElementById("__gfonts_link")) return
var l = document.createElement("link")
l.id = "__gfonts_link"
l.rel = "stylesheet"
l.href = FONT_HREF
l.setAttribute("data-lazy", "1")
document.head.appendChild(l)
}
function schedule() {
if ("requestIdleCallback" in window) {
requestIdleCallback(inject, { timeout: 2000 })
} else {
setTimeout(inject, 500)
}
}
if (document.readyState === "complete") {
schedule()
} else {
window.addEventListener("load", schedule, { once: true })
}
})()
</script>
<noscript
><link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&family=Noto+Serif+SC:wght@400;700;900&family=ZCOOL+KuaiLe&family=ZCOOL+XiaoWei&family=ZCOOL+QingKe+HuangYou&family=Ma+Shan+Zheng&family=Long+Cang&family=Liu+Jian+Mao+Cao&family=Zhi+Mang+Xing&display=swap"
/></noscript>
</body>
</html>
-16
View File
@@ -1,6 +1,5 @@
import apiClient from "@/api/client"
import type {
GenerateViralVideoRequest,
HistoryResponse,
StyleTemplate,
ViralVideoJob,
@@ -13,26 +12,11 @@ import type {
ViralVideoModelsResponse,
} from "./types"
/** 创建爆款视频任务 */
export function generateViralVideo(payload: GenerateViralVideoRequest) {
return apiClient.post<ViralVideoJob>("/viral-video/generate", payload).then((r) => r.data)
}
/** 查询单个任务 */
export function getViralVideoJob(id: string) {
return apiClient.get<ViralVideoJob>(`/viral-video/${id}`).then((r) => r.data)
}
/** 用户确认/修改 AI 理解的意图后继续 */
export function confirmViralVideoIntent(
id: string,
payload: { confirmed_copy?: string; edits?: Record<string, unknown> },
) {
return apiClient
.post<ViralVideoJob>(`/viral-video/${id}/confirm-intent`, payload)
.then((r) => r.data)
}
/** 重试失败任务 */
export function retryViralVideo(id: string) {
return apiClient.post<ViralVideoJob>(`/viral-video/${id}/retry`).then((r) => r.data)
+3 -54
View File
@@ -21,23 +21,15 @@ export const VALID_RATIOS = ["9:16", "16:9", "1:1"] as const
export type VideoRatio = (typeof VALID_RATIOS)[number]
export type ViralVideoStatus =
| "pending"
| "running"
| "wait_user_confirm"
| "image_analyzed"
| "copy_generated"
| "completed"
| "failed"
| "cancelled"
"pending" | "running" | "image_analyzed" | "copy_generated" | "completed" | "failed" | "cancelled"
/**
* v1.6 后端流水线阶段。单次 Seedance 出片版:
* image_analysis → video_analysis(可选) → intent_parsing → script_generation → review → tts → rendering → uploading
* image_analysis → video_analysis(可选) → script_generation(含意图理解) → review → tts → rendering → uploading
*/
export type ViralVideoStage =
| "image_analysis"
| "video_analysis"
| "intent_parsing"
| "script_generation"
| "review"
| "tts"
@@ -47,7 +39,7 @@ export type ViralVideoStage =
/** 图片+视频分析阶段:属于「分析图片」按钮的范围 */
const IMAGE_ANALYSIS_STAGES = new Set<ViralVideoStage>(["image_analysis", "video_analysis"])
/** 编导脚本阶段:属于「生成文案」按钮的范围 */
const COPY_STAGES = new Set<ViralVideoStage>(["intent_parsing", "script_generation", "review"])
const COPY_STAGES = new Set<ViralVideoStage>(["script_generation", "review"])
/** 视频生成阶段:属于「开始生成视频」按钮的范围(v1.6: TTS+单次Seedance+上传) */
const VIDEO_STAGES = new Set<ViralVideoStage>(["tts", "rendering", "uploading"])
@@ -161,22 +153,6 @@ export interface StyleTemplate {
tags?: string[]
}
export interface IntentResult {
intent?: string
key_messages?: string[]
tone?: string
target_emotion?: string
call_to_action?: string
suggested_title?: string
/** v1.5 旧字段兼容 */
product?: string
selling_points?: string[]
target_audience?: string
structure?: string
duration?: number
suggested_copy?: string
}
export interface ViralVideoJob {
id: string
status: ViralVideoStatus
@@ -194,8 +170,6 @@ export interface ViralVideoJob {
voice_mode?: "global" | "per_video"
voice_source?: "preset" | "library" | "clone" | "upload" | "my_voice"
bgm_preference?: string
intent_result?: IntentResult
intent_text?: string
/** v1.6 编导分镜脚本(核心产物) */
copy_result?: CopyResult
/** 向后兼容:= copy_result.shots */
@@ -225,31 +199,6 @@ export interface ViralVideoJob {
updated_at?: string
}
export interface GenerateViralVideoRequest {
images: string[]
reference_video_url?: string
douyin_url?: string
style_strength?: StyleStrength
style_template_id?: string
user_copy_text?: string
fusion_level?: FusionLevel
voice_id?: string
voice_source?: "preset" | "library" | "clone" | "upload" | "my_voice"
bgm_preference?: string
industry?: string
target_customer?: string
language?: string
persona_id?: string
viral_structure?: string
marketing_purpose?: string
/** 视频时长(5-30秒,默认15) */
duration?: number
video_model?: string
video_ratio?: string
/** 三步拆分:step 控制后端执行到哪一步暂停 */
step?: "analyze" | "generate_copy" | "generate_video"
}
export interface HistoryResponse {
items: ViralVideoJob[]
total: number
+35 -4
View File
@@ -28,6 +28,7 @@ import {
generateRenderSmartCover,
finalizeRenderJob,
} from "./api/aiAvatar"
import { uploadAssetDirect } from "@/api/assets"
import { getOrCreateDefaultProject } from "@/api/projects"
import type { RenderJob, SentenceTiming } from "./types"
import { buildTitleConfigPayload, buildCoverConfigPayload } from "./utils/contract"
@@ -254,6 +255,8 @@ const AiAvatarPage: React.FC = () => {
audio_url: state.ttsPreview.audioUrl!,
audio_duration: state.ttsPreview.duration,
sentence_timings: state.ttsPreview.sentenceTimings,
// 预合成也必须带文案:后端落库 job.script_text,Ditto 情绪分析依赖它
script_text: state.scriptText,
enable_video_loop: true,
}
} else {
@@ -531,7 +534,7 @@ const AiAvatarPage: React.FC = () => {
setShowCoverModal(false)
}, [])
/* ── 自定义上传封面(本地预览,不单独上传;点完成时一起入库) ── */
/* ── 自定义上传封面:本地预览 + 保存 File 引用,点完成时直传 OSS 再 finalize ── */
const handleUploadCover = useCallback(
(file: File) => {
const url = URL.createObjectURL(file)
@@ -540,13 +543,15 @@ const AiAvatarPage: React.FC = () => {
mode: "upload",
upload_url: url,
thumbnail_url: url,
smart_cover_url: null,
upload_file: file,
}))
setSelectedCoverUrl(url)
},
[state],
)
/* ── 点「完成」:调用 finalize 入库成片库,成功后跳转到成片库 ── */
/* ── 点「完成」:本地上传封面先直传 OSS,再调 finalize 入库成片库 ── */
const handleFinalize = useCallback(async () => {
if (!currentRenderJob?.id) {
message.error("渲染任务不存在")
@@ -556,9 +561,35 @@ const AiAvatarPage: React.FC = () => {
message.warning("请先完成视频生成")
return
}
const { coverConfig } = state
const hasSmartCover = !!coverConfig.smart_cover_url
const hasLocalUpload = coverConfig.mode === "upload" && !!coverConfig.upload_file
setFinalizeLoading(true)
try {
const res = await finalizeRenderJob(currentRenderJob.id)
// 本地上传封面:先走 OSS 直传拿到公网 URL(智能封面已由 /smart-cover 后端上传 OSS,无需重复传;
// 两种都没选则不传 cover_url,后端 finalize 兜底自动抽帧)
let finalCoverUrl: string | undefined
if (hasSmartCover) {
finalCoverUrl = coverConfig.smart_cover_url!
} else if (hasLocalUpload && coverConfig.upload_file) {
const hide = message.loading("正在上传封面...", 0)
try {
const result = await uploadAssetDirect({
file: coverConfig.upload_file,
kind: "image",
})
finalCoverUrl = result.url || undefined
if (!finalCoverUrl) {
message.warning("封面上传未返回URL,将使用视频首帧作为封面")
}
} finally {
hide()
}
}
const res = await finalizeRenderJob(
currentRenderJob.id,
finalCoverUrl ? { cover_url: finalCoverUrl } : undefined,
)
if (res.data?.status === "success" || res.data?.status === "already_finalized") {
message.success("已保存到成片库")
navigate("/app/products")
@@ -574,7 +605,7 @@ const AiAvatarPage: React.FC = () => {
} finally {
setFinalizeLoading(false)
}
}, [currentRenderJob, navigate])
}, [currentRenderJob, navigate, state])
/* ── 配置汇总 ── */
const coverStatus: "not_ready" | "pending" | "selected" = (() => {
+6 -2
View File
@@ -119,8 +119,12 @@ export const generateRenderSmartCover = async (
return response.data
}
/* ── 封面选定后点「完成」正式入库(POST /ai-avatar/render/{job_id}/finalize) ── */
export const finalizeRenderJob = (renderId: string) =>
/* ── 封面选定后点「完成」正式入库(POST /ai-avatar/render/{job_id}/finalize)
* 后端契约:body 支持可选 cover_url(OSS 公网 URL,智能封面已经由后端/smart-cover 写入则不传)
*/
export const finalizeRenderJob = (renderId: string, payload?: { cover_url?: string }) =>
apiClient.post<{ video_id: string; cover_url: string; status: string }>(
`/ai-avatar/render/${renderId}/finalize`,
payload ?? {},
{ timeout: 120_000 }, // 后端兜底抽帧/写入成片库可能耗时较长
)
@@ -73,6 +73,8 @@ const ModalCoverSelect: React.FC<ModalCoverSelectProps> = ({
mode: "auto_frame",
thumbnail_url: coverUrl,
smart_cover_url: coverUrl,
upload_url: null,
upload_file: null,
})
onCoverSelected(coverUrl)
message.success("智能封面已生成")
@@ -113,6 +115,7 @@ const ModalCoverSelect: React.FC<ModalCoverSelectProps> = ({
mode: "upload",
upload_url: url,
thumbnail_url: url,
smart_cover_url: null,
})
onCoverSelected(url)
}
+3
View File
@@ -162,6 +162,8 @@ export interface AiAvatarCoverConfig {
thumbnail_url: string | null
/** 智能封面(MediaKit 选帧)返回的 OSS 非临时 URL(#1822) */
smart_cover_url: string | null
/** 本地上传封面时保存的原始 File 对象,finalize 时先直传 OSS 再带公网 URL */
upload_file?: File | null
}
/* ── 渲染任务 ── */
@@ -215,4 +217,5 @@ export const DEFAULT_COVER_CONFIG: AiAvatarCoverConfig = {
upload_url: null,
thumbnail_url: null,
smart_cover_url: null,
upload_file: null,
}
@@ -108,7 +108,7 @@ export const FALLBACK_VIDEO_MODELS: ViralVideoModel[] = [
display_name: "Wan 3.0 — 通义万相全能",
supports_audio: true,
supported_resolutions: ["480p", "720p", "1080p"],
max_duration: 15,
max_duration: 30,
},
]
export const RESOLUTION_ORDER = ["480p", "720p", "1080p", "4k"]
+91 -185
View File
@@ -138,27 +138,6 @@
border-radius: 12px;
overflow: hidden;
}
.vv-section-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
cursor: pointer;
user-select: none;
border-bottom: 1px solid #f0f0f5;
gap: 8px;
}
.vv-section.collapsed .vv-section-head {
border-bottom: none;
}
.vv-section-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 600;
color: #1f2937;
}
.vv-step-badge {
width: 22px;
height: 22px;
@@ -310,11 +289,6 @@
/* antd Select dropdown light */
.vv-page .ant-select-dropdown,
.vv-select-dropdown {
background: #fff !important;
border-radius: 8px !important;
padding: 4px !important;
}
.vv-page .ant-select-item {
color: #1f2937 !important;
border-radius: 6px !important;
@@ -655,10 +629,6 @@
margin-left: auto;
}
.vv-douyin-input {
margin-bottom: 8px;
}
.vv-ref-video-box {
margin-top: 8px;
}
@@ -669,44 +639,6 @@
max-height: 200px;
}
.vv-audio-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.vv-audio-cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
padding: 14px 8px;
background: #fff;
border: 1px solid #e0e0e8;
border-radius: 8px;
color: #6b7280;
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
}
.vv-audio-cell:hover:not(:disabled) {
border-color: #7c3aed;
color: #7c3aed;
}
.vv-audio-cell.selected,
.vv-audio-cell.active {
background: #f5f0ff;
border-color: #7c3aed;
color: #7c3aed;
}
.vv-audio-cell:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.vv-audio-ic {
font-size: 20px;
}
.vv-selected-audio {
display: flex;
align-items: center;
@@ -857,12 +789,6 @@
color: #6b7280;
flex-shrink: 0;
}
.vv-recog-v {
color: #1f2937;
}
.vv-recog-meta {
color: #9ca3af;
}
/* progress mini */
.vv-progress-mini-inline {
@@ -1002,11 +928,6 @@
.vv-form-row {
margin-bottom: 10px;
}
.vv-form-hint {
font-size: 12px;
color: #9ca3af;
line-height: 1.4;
}
@media (max-width: 500px) {
.vv-form-grid {
grid-template-columns: 1fr;
@@ -1026,29 +947,6 @@
min-height: 260px;
overflow: hidden;
}
.vv-copy-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.vv-copy-title {
font-size: 14px;
font-weight: 600;
color: #1f2937;
}
.vv-copy-tag {
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
font-weight: 400;
}
.vv-copy-tag-preview {
background: #fef3c7;
color: #b45309;
border: 1px solid #fcd34d;
margin-left: auto;
}
.vv-copy-loading {
display: flex;
@@ -1080,15 +978,6 @@
color: #6b7280;
}
.vv-copy-locked {
background: #fafafe;
border-color: #d8cafc;
}
.vv-copy-sub {
font-size: 12px;
color: #6b7280;
}
/* ── Storyboard (linear doc style) ── */
.vv-storyboard {
display: flex;
@@ -1789,84 +1678,10 @@
}
/* intent (keyword chips) - if used */
.vv-intent {
margin-top: 10px;
}
.vv-intent-title {
font-size: 12px;
color: #6b7280;
margin-bottom: 6px;
}
.vv-intent-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.vv-chip {
padding: 4px 10px;
background: #fff;
border: 1px solid #e0e0e8;
border-radius: 14px;
font-size: 12px;
color: #374151;
cursor: pointer;
}
.vv-chip.picked {
background: #f5f0ff;
color: #7c3aed;
border-color: #7c3aed;
}
/* misc helpers */
.vv-k {
color: #6b7280;
}
.vv-v {
color: #1f2937;
}
.vv-hint-yellow {
background: #fef3c7;
border: 1px solid #fcd34d;
color: #b45309;
font-size: 12px;
padding: 8px;
border-radius: 6px;
line-height: 1.6;
margin-top: 8px;
}
.vv-hint-gray {
color: #9ca3af;
font-size: 12px;
}
/* Segmented tabs (legacy) */
.vv-seg-tabs {
display: inline-flex;
background: #f5f6fa;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 3px;
gap: 2px;
}
.vv-seg-tab {
padding: 6px 14px;
border-radius: 6px;
background: #fff;
border: none;
color: #6b7280;
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
}
.vv-seg-tab:hover {
color: #374151;
background: #fff;
}
.vv-seg-tab.active {
background: #f3f0ff;
color: #7c3aed;
font-weight: 500;
}
/* ── v1.6 我的音色(默认主路径) ── */
.vv-voice-section {
@@ -2272,3 +2087,94 @@
.vv-file-list {
max-height: none;
}
/* ── P2 新增 utility / 组件类 ── */
/* 短剧模式提示条(放在 Step2 区域就地显示) */
.vv-shortdrama-hint {
padding: 8px 12px;
margin-bottom: 8px;
background-color: #fff7e6;
border: 1px solid #ffd591;
border-radius: 4px;
font-size: 13px;
color: #d46b08;
}
/* speaker 标签(主角 / 配角) */
.vv-sb-speaker-tag {
display: inline-block;
padding: 2px 8px;
margin: 0 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
vertical-align: middle;
}
.vv-sb-speaker-tag-main {
background-color: #e6f7ff;
color: #1890ff;
}
.vv-sb-speaker-tag-guest {
background-color: #fff7e6;
color: #fa8c16;
}
.vv-sb-speaker-main {
color: #1890ff;
}
.vv-sb-speaker-guest {
color: #fa8c16;
}
/* 间距 / 排版 helpers */
.vv-mt-4 {
margin-top: 4px;
}
.vv-mt-6 {
margin-top: 6px;
}
.vv-mt-8 {
margin-top: 8px;
}
.vv-mb-6 {
margin-bottom: 6px;
}
.vv-mr-6 {
margin-right: 6px;
}
.vv-fs-12 {
font-size: 12px;
}
.vv-fs-13 {
font-size: 13px;
}
.vv-fs-22 {
font-size: 22px;
}
.vv-p-12 {
padding: 12px;
}
.vv-text-center {
text-align: center;
}
.vv-flex-1 {
flex: 1;
}
.vv-w-170 {
width: 170px;
flex: 0 0 auto;
}
.vv-inline-spinner {
margin-right: 6px;
display: inline-block;
vertical-align: middle;
}
.vv-progress-bar-sm {
margin: 6px 0 4px;
}
.vv-loading-ic {
font-size: 36px;
color: #7c3aed;
}
@@ -38,7 +38,6 @@ function emptyTask(id: string, title: string): TabTask {
title,
images: [],
refVideo: null,
douyinInput: "",
voicePanel: null,
refAudio: null,
fusionLevel: "ai_full",
@@ -176,7 +175,6 @@ const ViralVideoPage: React.FC = () => {
moveImg,
clearImages,
handleLocalRefVideo,
handleDouyinUrl,
removeRefVideo,
handleVoiceFile,
onCloneSuccess,
@@ -208,8 +206,8 @@ const ViralVideoPage: React.FC = () => {
try {
const job = await getViralVideoJob(ev.job_id)
onPollUpdate(job)
} catch (e) {
console.warn("[viral-video] WS终态主动拉取失败,等待HTTP轮询兜底", e)
} catch {
// WS终态拉取失败,等待HTTP轮询兜底
}
},
})
@@ -413,7 +411,6 @@ const ViralVideoPage: React.FC = () => {
<Step1UploadPanel
images={task.images}
refVideo={task.refVideo}
douyinInput={task.douyinInput}
voicePanel={task.voicePanel}
refAudio={task.refAudio}
voiceTipsOpen={task.voiceTipsOpen}
@@ -435,11 +432,6 @@ const ViralVideoPage: React.FC = () => {
onImagesSelected={appendImagesFromFiles}
onAnalyze={handleAnalyzeImages}
onPickLocalVideo={() => videoInputRef.current?.click()}
onToggleDouyinPanel={() =>
setTask({ voicePanel: task.voicePanel === "douyin" ? null : "douyin" })
}
onDouyinInputChange={(v) => setTask({ douyinInput: v })}
onDouyinSubmit={handleDouyinUrl}
onVideoSelected={handleLocalRefVideo}
onRemoveRefVideo={removeRefVideo}
onStyleStrengthChange={(v) => setTask({ styleStrength: v })}
@@ -473,6 +465,7 @@ const ViralVideoPage: React.FC = () => {
viralStructure={task.viralStructure}
marketingPurpose={task.marketingPurpose}
duration={task.duration}
copyError={task.copyError}
onFusionLevelChange={(v) => setTask({ fusionLevel: v })}
onUserCopyChange={(v) => setTask({ userCopy: v })}
onParamChange={(patch) => setTask(patch)}
@@ -529,31 +522,9 @@ const ViralVideoPage: React.FC = () => {
onRetry={handleRetry}
/>
)}
{/* REVIEW 合规失败就地显示 */}
{task.uiStep === "step2_copy_ready" && task.copyError && (
<ErrorRetryBar label="文案审核未通过" message={task.copyError} />
)}
</div>
</div>
{/* 短剧提示条 */}
{task.marketingPurpose && task.marketingPurpose.includes("短剧") && (
<div
style={{
padding: "8px 12px",
marginBottom: 8,
backgroundColor: "#fff7e6",
border: "1px solid #ffd591",
borderRadius: 4,
fontSize: 13,
color: "#d46b08",
}}
>
💡 短剧模式将自动为对手戏角色分配配角音色
</div>
)}
<CloneModal
open={cloneModalOpen}
onClose={() => setCloneModalOpen(false)}
@@ -152,8 +152,7 @@ export default function AssetPickerModal({
<div className="vv-modal-body">
<div className="vv-asset-search">
<select
className="vv-input"
style={{ width: 170, flex: "0 0 auto" }}
className="vv-input vv-w-170"
value={libraryId}
onChange={(e) => setLibraryId(e.target.value)}
disabled={loadingLibs || libraries.length === 0}
@@ -201,8 +200,8 @@ export default function AssetPickerModal({
</div>
{kind === "voice" ? (
<>
<div style={{ marginTop: 8, fontSize: 13 }}>暂无配音素材</div>
<div style={{ marginTop: 4, fontSize: 12, color: "#9ca3af" }}>
<div className="vv-mt-8 vv-fs-13">暂无配音素材</div>
<div className="vv-muted vv-mt-4 vv-fs-12">
请先在「配音/我的音色」中上传音频文件,或在素材库管理中添加
</div>
</>
@@ -106,10 +106,10 @@ const CATEGORY_LABELS: Record<string, string> = {
const GENDER_LABEL = (v: PresetVoice) => {
if (v.gender_label) return v.gender_label
const g = v.gender
if (g === "female") return "女声·女声"
if (g === "male") return "男声·男声"
if (g === "child") return "童声·童声"
return "性别未标注·其他"
if (g === "female") return "女声"
if (g === "male") return "男声"
if (g === "child") return "童声"
return "其他"
}
const AVATAR_BG = (gender?: string) => {
@@ -303,7 +303,7 @@ const PresetVoicePickerModal: React.FC<Props> = ({
{v.avatar_url ? (
<img src={v.avatar_url} alt={v.name} />
) : (
<UserOutlined style={{ fontSize: 22 }} />
<UserOutlined className="vv-fs-22" />
)}
</div>
<div className="vv-voice-card-name" title={v.name}>
@@ -5,9 +5,7 @@ import {
ArrowUpOutlined,
ArrowDownOutlined,
CloseOutlined,
LinkOutlined,
} from "@ant-design/icons"
import { Input } from "antd"
import { VVSpinner } from "./VVSpinner"
import { renderMarkdown } from "../utils/renderMarkdown"
import { CheckCircleFilled } from "@ant-design/icons"
@@ -21,7 +19,6 @@ import type { AssetItem } from "@/api/assets"
export interface Step1UploadPanelProps {
images: UploadedImage[]
refVideo: RefVideo
douyinInput: string
voicePanel: VoicePanel
refAudio: RefAudio
voiceTipsOpen: boolean
@@ -45,9 +42,6 @@ export interface Step1UploadPanelProps {
onAnalyze: () => void
// callbacks - ref video
onPickLocalVideo: () => void
onToggleDouyinPanel: () => void
onDouyinInputChange: (v: string) => void
onDouyinSubmit: () => void
onVideoSelected: (file: File) => void
onRemoveRefVideo: () => void
onStyleStrengthChange: (v: StyleStrength) => void
@@ -94,10 +88,7 @@ function RecognitionBlock({
return (
<div className="vv-recog">
<div className="vv-recog-title">
<VVSpinner
color="#7c3aed"
style={{ marginRight: 6, display: "inline-block", verticalAlign: "middle" }}
/>
<VVSpinner color="#7c3aed" className="vv-inline-spinner" />
识别描述汇览
</div>
<div className="vv-muted">AI 正在识别画面…</div>
@@ -240,9 +231,6 @@ export const Step1UploadPanel: React.FC<Step1UploadPanelProps> = (p) => {
<button className="vv-action-btn" onClick={p.onPickLocalVideo}>
<UploadOutlined /> 本地上传视频
</button>
<button className="vv-action-btn" onClick={p.onToggleDouyinPanel}>
<LinkOutlined /> 抖音链接识别
</button>
</div>
<input
ref={p.videoInputRef}
@@ -254,24 +242,6 @@ export const Step1UploadPanel: React.FC<Step1UploadPanelProps> = (p) => {
e.target.value = ""
}}
/>
{p.voicePanel === "douyin" && !p.refVideo && (
<div className="vv-douyin-input">
<Input
placeholder="粘贴抖音分享链接,例:https://v.douyin.com/xxxxx/"
value={p.douyinInput}
onChange={(e) => p.onDouyinInputChange(e.target.value)}
onPressEnter={p.onDouyinSubmit}
suffix={
<button className="vv-link-btn" onClick={p.onDouyinSubmit}>
识别
</button>
}
/>
<div className="vv-muted" style={{ marginTop: 4, fontSize: 11 }}>
粘贴后自动解析下载参考视频(敬请期待)
</div>
</div>
)}
{p.refVideo && (
<div className="vv-ref-video-box">
<video
@@ -279,10 +249,10 @@ export const Step1UploadPanel: React.FC<Step1UploadPanelProps> = (p) => {
controls
className="vv-ref-video-el"
/>
<div className="vv-file-info" style={{ marginTop: 6 }}>
<div className="vv-file-info vv-mt-6">
<div className="vv-file-name">{p.refVideo.name}</div>
{p.refVideo.uploading && (
<div className="vv-progress-bar" style={{ margin: "6px 0 4px" }}>
<div className="vv-progress-bar vv-progress-bar-sm">
<div className="vv-progress-fill" style={{ width: `${p.refVideo.pct}%` }} />
</div>
)}
@@ -292,9 +262,7 @@ export const Step1UploadPanel: React.FC<Step1UploadPanelProps> = (p) => {
</button>
{p.refVideo.ossUrl && (
<div className="vv-mt-8">
<div className="vv-label" style={{ marginBottom: 6 }}>
风格借鉴强度
</div>
<div className="vv-label vv-mb-6">风格借鉴强度</div>
<div className="vv-pill-row">
{STYLE_STRENGTHS.map((s) => (
<button
@@ -4,6 +4,7 @@ import { Select } from "antd"
import { FUSION_LEVELS } from "@/api/viral-video/types"
import type { FusionLevel } from "@/api/viral-video/types"
import { VVSpinner } from "./VVSpinner"
import { ErrorRetryBar } from "./ErrorRetryBar"
import { StoryboardCard } from "./StoryboardCard"
import type { SbDoc, StoryboardEditor } from "../hooks/useStoryboardEditor"
import type { UploadedImage, UIStep, ViralVideoJob } from "../types"
@@ -52,6 +53,7 @@ export interface Step2CopyPanelProps {
images: UploadedImage[]
locked: boolean
job: ViralVideoJob | null
copyError?: string
}
export const Step2CopyPanel: React.FC<Step2CopyPanelProps> = (p) => {
@@ -181,6 +183,16 @@ export const Step2CopyPanel: React.FC<Step2CopyPanelProps> = (p) => {
</div>
</div>
{/* 短剧模式提示 */}
{p.marketingPurpose && p.marketingPurpose.includes("短剧") && (
<div className="vv-shortdrama-hint">
💡 短剧模式已选中,对手戏角色将自动分配配角音色
</div>
)}
{/* 文案审核错误(持久显示) */}
{p.copyError && <ErrorRetryBar label="文案审核未通过" message={p.copyError} />}
{/* 分镜结果区 / 流式预览 */}
<StoryboardCard
uiStep={p.uiStep}
@@ -154,7 +154,7 @@ export const Step3VideoPanel: React.FC<Step3VideoPanelProps> = (p) => {
) : isGenerating ? (
<div className="vv-preview-placeholder">
<div className="ph-icon">
<LoadingOutlined style={{ fontSize: 36, color: "#7c3aed" }} />
<LoadingOutlined className="vv-loading-ic" />
</div>
<div className="ph-txt">
AI 正在生成视频中…
@@ -162,9 +162,7 @@ export const Step3VideoPanel: React.FC<Step3VideoPanelProps> = (p) => {
预计 1-3 分钟,请勿关闭页面
</div>
{getJobPhaseMessage(p.job) && (
<div className="vv-muted" style={{ marginTop: 6, fontSize: 12 }}>
{getJobPhaseMessage(p.job)}
</div>
<div className="vv-muted vv-mt-6 vv-fs-12">{getJobPhaseMessage(p.job)}</div>
)}
</div>
) : (
@@ -39,7 +39,7 @@ function renderVoiceoverWithSpeakers(text: string) {
const isMain = MAIN_SPEAKERS.includes(speaker)
elements.push(
<span key={i}>
<strong style={{ color: isMain ? "#1890ff" : "#fa8c16" }}>{speaker}</strong>
<strong className={isMain ? "vv-sb-speaker-main" : "vv-sb-speaker-guest"}>{speaker}</strong>
<span>{segText}</span>
</span>,
)
@@ -79,7 +79,7 @@ export const StoryboardCard: React.FC<StoryboardCardProps> = ({
<div className="vv-copy-box vv-copy-loading">
<VVSpinner />
<span className="vv-copy-loading-text">{stageMsg}</span>
<span className="vv-copy-loading-hint">预计 10-30 秒,请稍候</span>
<span className="vv-copy-loading-hint">预计 5-15 秒,请稍候</span>
</div>
)
}
@@ -111,7 +111,7 @@ export const StoryboardCard: React.FC<StoryboardCardProps> = ({
if (uiStep === "step2_copy_ready" || uiStep === "step3_generating" || uiStep === "step3_done") {
return (
<div className="vv-copy-box vv-copy-loading">
<span style={{ color: "#9ca3af" }}>暂无分镜数据</span>
<span className="vv-muted">暂无分镜数据</span>
</div>
)
}
@@ -51,18 +51,7 @@ export const StoryboardShotItem: React.FC<StoryboardShotItemProps> = ({
{/* 说话人标签(有 speaker 时展示,纯展示不加编辑) */}
{shot.speaker && (
<span
className="vv-sb-speaker-tag"
style={{
display: "inline-block",
padding: "2px 8px",
marginRight: 8,
marginLeft: 8,
borderRadius: 4,
fontSize: 12,
fontWeight: 500,
backgroundColor: isMainSpeaker ? "#e6f7ff" : "#fff7e6",
color: isMainSpeaker ? "#1890ff" : "#fa8c16",
}}
className={`vv-sb-speaker-tag ${isMainSpeaker ? "vv-sb-speaker-tag-main" : "vv-sb-speaker-tag-guest"}`}
>
{shot.speaker}
</span>
@@ -143,8 +143,8 @@ export const VoicePickerSection: React.FC<VoicePickerSectionProps> = (p) => {
/>
{p.refAudio && (
<div className="vv-selected-audio">
<span style={{ marginRight: 6 }}>🔊</span>
<span style={{ flex: 1 }}>
<span className="vv-mr-6">🔊</span>
<span className="vv-flex-1">
已选:
{p.refAudio.source === "my_voice"
? `我的音色 · ${p.refAudio.name}`
@@ -168,9 +168,7 @@ export const VoicePickerSection: React.FC<VoicePickerSectionProps> = (p) => {
</button>
</div>
{p.voiceAssets.length === 0 ? (
<div className="vv-muted" style={{ padding: 12, textAlign: "center" }}>
暂无已上传的配音文件
</div>
<div className="vv-muted vv-text-center vv-p-12">暂无已上传的配音文件</div>
) : (
<div className="vv-voice-list">
{p.voiceAssets.map((v) => (
@@ -58,7 +58,6 @@ function emptyTask(id: string, title: string): TabTask {
title,
images: [],
refVideo: null,
douyinInput: "",
voicePanel: null,
refAudio: null,
fusionLevel: "ai_full",
@@ -27,7 +27,6 @@ export interface MediaActions {
removeImg: (localId: string) => void
moveImg: (idx: number, dir: -1 | 1) => void
handleLocalRefVideo: (file: File) => void
handleDouyinUrl: () => void
removeRefVideo: () => void
handleVoiceFile: (file: File) => void
onCloneSuccess: (v: VoiceClone | { id?: string; name?: string; voice_id?: string | null }) => void
@@ -221,20 +220,6 @@ export function useViralVideoMedia(opts: UseViralVideoMediaOptions): MediaAction
[setTask, uploadFile],
)
const handleDouyinUrl = () => {
const url = task.douyinInput.trim()
if (!url) {
message.warning("请粘贴抖音分享链接")
return
}
if (!/(douyin\.com|iesdouyin\.com)/.test(url)) {
message.warning("请输入有效的抖音链接")
return
}
message.info("抖音链接识别功能即将上线,当前请使用本地上传参考视频")
setTask({ douyinInput: "" })
}
const removeRefVideo = () => {
setTask((t) => {
if (t.refVideo?.url.startsWith("blob:")) URL.revokeObjectURL(t.refVideo.url)
@@ -350,7 +335,6 @@ export function useViralVideoMedia(opts: UseViralVideoMediaOptions): MediaAction
removeImg,
moveImg,
handleLocalRefVideo,
handleDouyinUrl,
removeRefVideo,
handleVoiceFile,
onCloneSuccess,
@@ -61,7 +61,7 @@ export function useViralVideoPolling(
failCountRef.current += 1
if (stoppedRef.current) return
if (failCountRef.current >= 20) {
console.warn("[viral-video] 轮询连续失败 20 次,停止轮询,请刷新页面重试")
// 轮询连续失败 20 次,停止轮询
stop()
return
}
+2 -4
View File
@@ -33,14 +33,13 @@ export type UploadedImage = {
}
export type RefVideo = {
source: "local" | "douyin"
source: "local"
url: string
name: string
uploading: boolean
pct: number
ossUrl?: string
file?: File
douyinUrl?: string
} | null
export type RefAudio = {
@@ -52,7 +51,7 @@ export type RefAudio = {
ttsVoiceId?: string
} | null
export type VoicePanel = "my_voices" | "preset" | "upload" | "library" | "record" | "douyin" | null
export type VoicePanel = "my_voices" | "preset" | "upload" | "library" | "record" | null
// ── 分镜脚本数据模型(新后端 copy_result 结构) ──
export interface StoryboardShot {
@@ -82,7 +81,6 @@ export type TabTask = {
// 素材
images: UploadedImage[]
refVideo: RefVideo
douyinInput: string
voicePanel: VoicePanel
refAudio: RefAudio
// STEP2 参数
@@ -42,9 +42,6 @@ export function applyPollUpdate(t: TabTask, job: ViralVideoJob): TabTask {
nextStep = "step2_copy_ready"
copyError = undefined
break
case "wait_user_confirm":
nextStep = t.uiStep === "step3_generating" ? t.uiStep : "step2_copy_ready"
break
case "running":
if (isVideoStage(stage)) nextStep = "step3_generating"
else if (isCopyStage(stage)) nextStep = "step2_generating"
+1 -21
View File
@@ -1,8 +1,6 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
import {
generateViralVideo,
getViralVideoJob,
confirmViralVideoIntent,
retryViralVideo,
getViralVideoHistory,
getViralStyleTemplates,
@@ -76,10 +74,10 @@ describe("viral-video constants & stage helpers", () => {
})
it("isCopyStage", () => {
expect(isCopyStage("intent_parsing")).toBe(true)
expect(isCopyStage("script_generation")).toBe(true)
expect(isCopyStage("review")).toBe(true)
expect(isCopyStage("tts")).toBe(false)
expect(isCopyStage("image_analysis")).toBe(false)
})
it("isAnalysisStage is union", () => {
@@ -101,14 +99,6 @@ describe("viral-video API wrappers", () => {
mockPost.mockResolvedValue({ data: {} })
})
it("generateViralVideo", async () => {
mockPost.mockResolvedValue({ data: { id: "j1" } })
const r = generateViralVideo({ images: ["img1"] } as never)
vi.runAllTimersAsync()
expect(await r).toEqual({ id: "j1" })
expect(mockPost).toHaveBeenCalledWith("/viral-video/generate", { images: ["img1"] })
})
it("getViralVideoJob", async () => {
mockGet.mockResolvedValue({ data: { id: "j2" } })
const r = getViralVideoJob("j2")
@@ -117,16 +107,6 @@ describe("viral-video API wrappers", () => {
expect(mockGet).toHaveBeenCalledWith("/viral-video/j2")
})
it("confirmViralVideoIntent", async () => {
mockPost.mockResolvedValue({ data: { id: "j3" } })
const r = confirmViralVideoIntent("j3", { confirmed_copy: "hi" })
vi.runAllTimersAsync()
await r
expect(mockPost).toHaveBeenCalledWith("/viral-video/j3/confirm-intent", {
confirmed_copy: "hi",
})
})
it("retryViralVideo", async () => {
mockPost.mockResolvedValue({ data: { id: "j4" } })
await retryViralVideo("j4")
+2
View File
@@ -29,6 +29,8 @@ export default defineConfig({
"src/pages/assets/AssetLibrary.tsx",
"src/pages/voice-materials/VoiceMaterialLibrary.tsx",
"src/pages/viral-video/ViralVideoPage.tsx",
"src/pages/ai-avatar/AiAvatarPage.tsx",
"src/pages/ai-avatar/components/**",
],
// CI 覆盖率门禁(Phase 4 后提升,逐步逼近目标)
// 当前实际:行 ~62% / 分支 ~61% / 函数 ~25%
File diff suppressed because it is too large Load Diff
@@ -948,6 +948,7 @@ class ViralVideoJobModel(Base):
persona_id = Column(String(36), nullable=False, default="")
viral_structure = Column(String(50), nullable=False, default="")
marketing_purpose = Column(String(100), nullable=False, default="")
tone = Column(String(50), nullable=False, default="")
bgm_preference = Column(String(50), nullable=False, default="")
duration = Column(Integer, nullable=False, default=30)
user_copy_text = Column(Text, nullable=False, default="")
@@ -38,6 +38,7 @@ def _to_domain(model: ViralVideoJobModel) -> ViralVideoJob:
persona_id=model.persona_id or "",
viral_structure=model.viral_structure or "",
marketing_purpose=model.marketing_purpose or "",
tone=getattr(model, "tone", "") or "",
bgm_preference=model.bgm_preference or "",
duration=model.duration or 15,
user_copy_text=model.user_copy_text or "",
@@ -56,7 +57,6 @@ def _to_domain(model: ViralVideoJobModel) -> ViralVideoJob:
current_stage=getattr(model, "current_stage", "") or "",
phase_message=getattr(model, "phase_message", "") or "",
heartbeat_at=getattr(model, "heartbeat_at", None),
intent_result=dict(model.intent_result) if model.intent_result else None,
image_analysis=dict(model.image_analysis) if getattr(model, "image_analysis", None) else None,
storyboard=list(model.storyboard) if getattr(model, "storyboard", None) else None,
generated_copy_text=getattr(model, "generated_copy_text", "") or "",
@@ -92,6 +92,7 @@ class SQLAlchemyViralVideoJobRepository:
persona_id=job.persona_id,
viral_structure=job.viral_structure,
marketing_purpose=job.marketing_purpose,
tone=getattr(job, "tone", "") or "",
bgm_preference=job.bgm_preference,
duration=job.duration,
user_copy_text=job.user_copy_text,
@@ -110,7 +111,6 @@ class SQLAlchemyViralVideoJobRepository:
current_stage=job.current_stage or "",
phase_message=job.phase_message or "",
heartbeat_at=job.heartbeat_at,
intent_result=job.intent_result,
image_analysis=job.image_analysis,
storyboard=job.storyboard,
generated_copy_text=job.generated_copy_text,
@@ -139,7 +139,6 @@ class SQLAlchemyViralVideoJobRepository:
model.current_stage = job.current_stage or ""
model.phase_message = job.phase_message or ""
model.heartbeat_at = job.heartbeat_at
model.intent_result = job.intent_result
model.image_analysis = job.image_analysis
model.storyboard = job.storyboard
model.generated_copy_text = job.generated_copy_text or ""
@@ -162,6 +161,7 @@ class SQLAlchemyViralVideoJobRepository:
model.persona_id = job.persona_id
model.viral_structure = job.viral_structure
model.marketing_purpose = job.marketing_purpose
model.tone = getattr(job, "tone", "") or ""
model.bgm_preference = job.bgm_preference
model.duration = job.duration
model.fusion_level = job.fusion_level
@@ -199,9 +199,7 @@ class SQLAlchemyViralVideoJobRepository:
self.session.query(ViralVideoJobModel)
.filter(
ViralVideoJobModel.user_id == user_id,
ViralVideoJobModel.status.in_(
["pending", "running", "wait_user_confirm", "image_analyzed", "copy_generated"]
),
ViralVideoJobModel.status.in_(["pending", "running", "image_analyzed", "copy_generated"]),
)
.count()
)
+21 -2
View File
@@ -4,7 +4,8 @@
1. 分句:先按 。!?; 句末标点切分;整段无句末标点且超长时按逗号等语义停顿二次切分
2. 调 DoubaoClient.chat_completion 分析每句表情(emo: 0-7 八种全部开放, intensity: 0-1)
3. 结果 LRU 缓存(文案 hash → 情绪列表)
4. LLM 失败/超时/格式错 → 返回空列表(降级中性表情,不阻塞生成)
4. LLM 失败/超时/格式错 → analyze 层预分句全中性兜底;build_timeline 识别全兜底特征后
返回空串不传 emo_timeline(避免压制 GPU 端自身表情逻辑),不阻塞生成
5. TTS 完成后按字数比例或 sentence_timings 对齐成秒级 timeline
"""
@@ -31,6 +32,11 @@ EMO_SURPRISE = 6
EMO_CONTEMPT = 7
ALLOWED_EMOS = set(range(8)) # 8 种表情全部开放(0-7);超出范围的非法值仍会被降级为中性
# 传给 Ditto 的 emo_timeline 强度下限:GPU 端表情混合公式修复后,intensity>=0.3
# 目标情绪才能压过中性;LLM 常给陈述句打 0.1-0.2,信号太弱、表情效果一眼假。
# 构造 timeline entry 时统一保底到该值(不影响 analyze 层 LLM 原始输出与日志排查)
MIN_TIMELINE_INTENSITY = 0.35
# ── 分句正则 ─────────────────────────────────────────────────────
_SENT_SPLIT_RE = re.compile(r"(?<=[。!?;!?;])\s*")
# 二次切分:逗号/分号/顿号/冒号等语义停顿(lookbehind 保留标点在前句末尾)
@@ -82,7 +88,8 @@ class EmotionTimelineEntry:
self.start = round(start, 2)
self.end = round(end, 2)
self.emo = emo
self.intensity = round(intensity, 2)
# 强度保底:低于 MIN_TIMELINE_INTENSITY 的信号在 GPU 混合公式中压不过中性
self.intensity = round(max(intensity, MIN_TIMELINE_INTENSITY), 2)
def to_dict(self) -> dict[str, Any]:
return {
@@ -361,6 +368,18 @@ class DittoEmotionService:
segments = self.analyze(text)
if not segments:
return ""
# LLM 失败/异常时 _cached_analyze 会兜底返回"全中性(emo=4)+低强度(<=0.1)"的
# 预分句结果;把这种"明确的全中性指令"传给 GPU 会压制 Ditto 端
# use_script_emo/emo_global 的自身处理,比不传更糟糕。识别全兜底特征后
# 返回空串(payload 不带 emo_timeline),交由 GPU 端自行决定表情。
# 注意:LLM 真实分析即使全中性,intensity 一般 >0.1,不会被误伤。
if all(s.emo == EMO_NEUTRAL and s.intensity <= 0.1 for s in segments):
logger.info(
"[ditto_emotion] 分析结果全为中性低强度(疑似 LLM 兜底),"
"emo_timeline 置空交由 GPU 端处理: text=%.80s",
text,
)
return ""
if sentence_timings:
entries = align_timeline_by_timings(segments, sentence_timings, audio_duration)
else:
@@ -7,11 +7,11 @@ from __future__ import annotations
# 时长规格:key=duration(秒),value=对应约束
DURATION_SPEC: dict[int, dict[str, int]] = {
5: {"min_chars": 12, "max_chars": 20, "min_shots": 1, "max_shots": 2},
10: {"min_chars": 25, "max_chars": 40, "min_shots": 2, "max_shots": 3},
15: {"min_chars": 38, "max_chars": 60, "min_shots": 3, "max_shots": 4},
20: {"min_chars": 50, "max_chars": 80, "min_shots": 4, "max_shots": 5},
30: {"min_chars": 75, "max_chars": 120, "min_shots": 6, "max_shots": 8},
5: {"min_chars": 12, "max_chars": 25, "min_shots": 1, "max_shots": 2},
10: {"min_chars": 25, "max_chars": 50, "min_shots": 2, "max_shots": 3},
15: {"min_chars": 38, "max_chars": 75, "min_shots": 3, "max_shots": 4},
20: {"min_chars": 50, "max_chars": 100, "min_shots": 4, "max_shots": 5},
30: {"min_chars": 75, "max_chars": 150, "min_shots": 6, "max_shots": 8},
}
@@ -38,8 +38,8 @@ def build_duration_constraint_text(dur: int) -> str:
spec = get_duration_spec(dur)
return (
f"视频时长{dur}秒。口播总字数严格控制在{spec['min_chars']}~{spec['max_chars']}字之间"
f"(中文正常口播语速约2.5~4字/秒,{dur}秒={dur * 4}字为硬上限,宁可少写也不要多写,"
f"超长会导致TTS配音超出视频时长而失败)。"
f"(中文口播语速约3~5字/秒,{dur}秒={spec['max_chars']}字为硬上限,必须严格遵守,"
f"超长会被截断导致语义不完整)。"
f"分镜镜头数{spec['min_shots']}~{spec['max_shots']}个,每个镜头的time_range首尾相接,"
f"不得跳秒或重叠,每个镜头时长≥2秒。"
)
@@ -0,0 +1,115 @@
"""爆款视频参数指导(persona/viral_structure/marketing_purpose/target_customer/language)。
PR3 P2(h): 把原来 worker 里 130 行 if/elif 硬编码 mapping 迁到 DB 表
`viral_video_param_guides`,提供 30 秒 TTL 缓存读取。DB miss 时返回 None,
由调用方兜底到代码默认值(保证 worker 冷启动/DB 不可用时不阻断流程)。
"""
from __future__ import annotations
import threading
import time
from typing import Optional
import sqlalchemy as sa
from packages.adapters.sqlalchemy_impl import session as _session_mod
CACHE_TTL_SECONDS = 30.0
VALID_GUIDE_TYPES = frozenset({"persona", "structure", "marketing_purpose", "target_customer", "language"})
_lock = threading.Lock()
# key = f"{guide_type}:{guide_key}", value = (expire_monotonic_ts, text|None)
_cache: dict[str, tuple[float, Optional[str]]] = {}
_lazy_session = None
def _get_session():
"""优先复用 worker 的 SessionLocal;否则懒建(API 场景)。"""
global _lazy_session
if _session_mod.SessionLocal is not None:
return _session_mod.SessionLocal()
if _lazy_session is not None:
return _lazy_session()
try:
from packages.config import get_shared_settings
url = str(get_shared_settings().database_url)
except Exception: # noqa: BLE001
return None
if not url:
return None
url = url.replace("postgresql+asyncpg://", "postgresql+psycopg://")
if url.startswith("postgresql://") and "psycopg" not in url:
url = url.replace("postgresql://", "postgresql+psycopg://", 1)
engine = sa.create_engine(url, pool_pre_ping=True, pool_size=2, max_overflow=2)
from sqlalchemy.orm import sessionmaker
_lazy_session = sessionmaker(bind=engine)
return _lazy_session()
def _cache_key(guide_type: str, guide_key: str) -> str:
return f"{guide_type}:{guide_key}"
def _load_from_db(guide_type: str, guide_key: str) -> Optional[str]:
session = None
try:
session = _get_session()
if session is None:
return None
sql = sa.text("""
SELECT guide_text
FROM viral_video_param_guides
WHERE guide_type = :gt AND guide_key = :gk
""")
row = session.execute(sql, {"gt": guide_type, "gk": guide_key}).first()
if row is None:
return None
return row[0] if row[0] is not None else None
except Exception: # noqa: BLE001 - 表不存在/DB 不可用静默返回 None
return None
finally:
if session is not None:
try:
session.close()
except Exception: # noqa: BLE001
pass
def get_param_guide(guide_type: str, guide_key: str, *, force_refresh: bool = False) -> Optional[str]:
"""读参数指导文本,30 秒 TTL;DB 无数据返回 None,调用方自行兜底。"""
if guide_type not in VALID_GUIDE_TYPES:
return None
gk = (guide_key or "").strip()
if not gk:
return None
key = _cache_key(guide_type, gk)
now = time.monotonic()
with _lock:
if not force_refresh:
cached = _cache.get(key)
if cached and now < cached[0]:
return cached[1]
text = _load_from_db(guide_type, gk)
with _lock:
_cache[key] = (now + CACHE_TTL_SECONDS, text)
return text
def invalidate() -> None:
"""清空缓存(测试用)。"""
with _lock:
_cache.clear()
__all__ = [
"CACHE_TTL_SECONDS",
"VALID_GUIDE_TYPES",
"get_param_guide",
"invalidate",
]
+152 -20
View File
@@ -1,10 +1,12 @@
"""Prompt 模板加载器:从 viral_video_prompt_templates 读模板,30 秒 TTL 热加载。
DB 不可用或没有数据时自动回落到 prompts.DEFAULT_TEMPLATES,保证流程不阻断。
DB 不可用或没有数据时自动回落到 prompts.DEFAULT_TEMPLATES,保证流程不阻断;
首次回落会打 warning(PR3 P2(f)),避免静默掩盖配置问题,又不刷屏。
"""
from __future__ import annotations
import logging
import threading
import time
from dataclasses import dataclass
@@ -15,10 +17,34 @@ import sqlalchemy as sa
from packages.adapters.sqlalchemy_impl import session as _session_mod
from packages.application.viral_video.prompts import DEFAULT_TEMPLATES
logger = logging.getLogger(__name__)
CACHE_TTL_SECONDS = 30.0
_VALID_TYPES = {"image_analysis", "storyboard", "review"}
# PR3 P2(f): 回落 warning 去重 — key -> 上次打 warning 的 monotonic 时间戳,
# 同一 key 在 _WARN_COOLDOWN_SECONDS 内不再重复告警。
_WARN_COOLDOWN_SECONDS = 300.0 # 5 分钟
_warned: dict[str, float] = {}
_warn_lock = threading.Lock()
def _warn_once(key: str, context: str, detail: BaseException | str | None = None) -> None:
"""冷却告警通用方法:5 分钟内同 key 只打一次,避免日志刷屏。
- key: 告警分组键(去重用)
- context: 本条告警的简短场景描述(直接拼进日志,不要再写死 'fallback to default' 字样)
- detail: 具体异常或描述
"""
now = time.monotonic()
with _warn_lock:
last = _warned.get(key, 0.0)
if now - last < _WARN_COOLDOWN_SECONDS:
return
_warned[key] = now
logger.warning("viral_video prompt %s: %s", context, detail)
@dataclass
class PromptTemplate:
@@ -64,12 +90,15 @@ def _get_session():
from packages.config import get_shared_settings
url = str(get_shared_settings().database_url)
except Exception: # noqa: BLE001
except Exception as e: # noqa: BLE001
_warn_once("session_config", "db config failed (will fallback)", e)
return None
if not url:
_warn_once("session_url", "db_url empty (will fallback)", "database_url empty")
return None
url = url.replace("postgresql+asyncpg://", "postgresql+psycopg://")
url = url.replace("postgresql://", "postgresql+psycopg://") if url.startswith("postgresql://") else url
if url.startswith("postgresql://") and "psycopg" not in url:
url = url.replace("postgresql://", "postgresql+psycopg://", 1)
engine = sa.create_engine(url, pool_pre_ping=True, pool_size=2, max_overflow=2)
from sqlalchemy.orm import sessionmaker
@@ -104,14 +133,15 @@ def _load_from_db(prompt_type: str) -> Optional[PromptTemplate]:
example_output=row[5] or "",
is_active=bool(row[6]),
)
except Exception: # noqa: BLE001 - 表不存在/DB 不可用时静默回落
except Exception as e: # noqa: BLE001 - 表不存在/DB 不可用时静默回落
_warn_once(f"load_db:{prompt_type}", f"db load failed for {prompt_type} (will fallback to code default)", e)
return None
finally:
if session is not None:
try:
session.close()
except Exception: # noqa: BLE001
pass
except Exception as e: # noqa: BLE001
_warn_once(f"session_close:{prompt_type}", f"session close failed for {prompt_type}", e)
def get_template(prompt_type: str, *, force_refresh: bool = False) -> Optional[PromptTemplate]:
@@ -126,6 +156,12 @@ def get_template(prompt_type: str, *, force_refresh: bool = False) -> Optional[P
return cached[1]
template = _load_from_db(prompt_type) or _fallback(prompt_type)
if template is None:
_warn_once(
f"no_template:{prompt_type}",
f"NO TEMPLATE for {prompt_type} (neither db nor code defaults)",
"no template in db or code defaults",
)
if template is not None:
with _lock:
_cache[prompt_type] = (now, template)
@@ -136,25 +172,121 @@ def invalidate() -> None:
"""清空缓存(测试用)。"""
with _lock:
_cache.clear()
class _SafeDict(dict):
def __missing__(self, key: str) -> str:
return "{" + key + "}"
with _warn_lock:
_warned.clear()
def _safe_format(text: str, kwargs: dict) -> str:
try:
return text.format_map(_SafeDict(kwargs))
except Exception: # noqa: BLE001
return text
"""安全格式化模板:任何占位符异常(缺key/属性访问失败/格式符非法/JSON花括号样例)
都原样保留,不抛异常、不静默回落默认模板,只打一条warning告警。
不用 str.format_map(_SafeDict):_SafeDict 只能防 KeyError,无法防 AttributeError
(如 {duration.1格式写全6项参数} 访问 str.1格式... 会抛 AttributeError),
也无法防非法 format_spec(如 JSON 样例 {"key":"val"} 里冒号被当格式符)。
"""
import string as _string
def render_user_prompt(template: PromptTemplate, **kwargs) -> str:
"""填充 user_prompt_template 占位符,缺键原样保留不报错。"""
return _safe_format(template.user_prompt_template, kwargs)
out: list[str] = []
i, n = 0, len(text)
errors: list[str] = []
while i < n:
c = text[i]
if c == "{":
if i + 1 < n and text[i + 1] == "{":
out.append("{")
i += 2
continue
j = i + 1
depth = 1
while j < n and depth > 0:
if text[j] == "{":
depth += 1
elif text[j] == "}":
depth -= 1
j += 1
if depth == 0:
inner = text[i + 1 : j - 1]
try:
val = _string.Formatter().vformat("{" + inner + "}", (), kwargs)
out.append(val)
except Exception as e: # noqa: BLE001
# 非法占位符原样保留,避免模板里的样例JSON/说明文本炸format
errors.append(f"{inner!r}: {type(e).__name__}")
out.append(text[i:j])
i = j
continue
if c == "}":
if i + 1 < n and text[i + 1] == "}":
out.append("}")
i += 2
continue
out.append(c)
i += 1
if errors:
# 模板里花括号炸format分两种:
# 1. 配置bug(如v5事故{duration.1格式写全6项参数}非法属性访问)→ 需要排查
# 2. 样例文本花括号(如{大远景/远景/全景}给LLM看的景别选项、JSON示例、XML说明)→ 原样保留属正常
# 注意:本函数只原样保留,不会让template回落到DEFAULT_TEMPLATES;
# 真正的回落到代码默认模板只在DB查询失败/无激活模板时发生(见_load_from_db/get_template),
# 那里会打独立ERROR日志并带job_id(见viral_video.py中DEFAULT_TEMPLATES检测块)。
# #2310: 降级为WARNING,因为大部分是DB模板中的样例花括号正常保留,不是bug;
# 仅在错误数异常多(>10)或包含明显属性访问错误(.xxx/[xxx])时提级到WARNING加关注
_has_real_bug = any("." in e or "[" in e for e in errors[:5])
_log_fn = logger.warning if _has_real_bug else logger.debug
_log_fn(
"[viral][prompt] 模板格式化有%d个花括号原样保留(示例:%s)——"
"样例文本(景别选项/JSON示例)可忽略;若应为变量请检查占位符拼写",
len(errors),
errors[0],
)
_warn_once(
"safe_format",
f"format-keep({len(errors)}placeholders)",
f"{len(errors)}个占位符原样保留(首条{errors[0]}),未回落默认模板",
)
return "".join(out)
def render_system_prompt(template: PromptTemplate, **kwargs) -> str:
"""copy_fusion 等 system_prompt 含运行时变量时填充。"""
return _safe_format(template.system_prompt, kwargs)
"""system_prompt 含运行时变量时填充;example_output 存在时作为 few-shot 样例追加到末尾。"""
body = _safe_format(template.system_prompt, kwargs)
example = (template.example_output or "").strip()
if not example:
return body
# 追加一个 assistant 角色样例消息由调用方拼成多轮;system 里只能带规则,example 统一由 user 侧追加
return body
def render_user_prompt(template: PromptTemplate, **kwargs) -> str:
r"""填充 user_prompt_template 占位符;example_output 存在时作为 few-shot 样例追加到 user 消息末尾,
配合 \_build_example_exchange 拼多轮 messages,LLM 收到完整「示例输入→示例输出」对齐格式。"""
body = _safe_format(template.user_prompt_template, kwargs)
example = (template.example_output or "").strip()
if not example:
return body
return body
def build_messages(template: PromptTemplate, **kwargs) -> list[dict]:
"""构造传给 LLM 的 messages 列表:system + (user, assistant[example]) + user。
- 第 1 条 system:规则
- 如果 template.example_output 非空,追加一轮 few-shot:user 给示例 user_prompt 渲染结果,assistant 给示例输出
- 最后 user:当前任务真实 user_prompt
这样 LLM 既能看到规则,又能看到一个完整「输入→输出」样例,格式遵从度大幅提升。
"""
sys_body = render_system_prompt(template, **kwargs)
user_body = render_user_prompt(template, **kwargs)
example = (template.example_output or "").strip()
messages: list[dict] = [{"role": "system", "content": sys_body}]
if example:
# 用一个通用「请参考以下示例输出格式」作为 few-shot user 前缀
messages.append(
{
"role": "user",
"content": "以下是一个符合规范的完整示例输出,请严格参照其格式、字段、风格来生成:",
}
)
messages.append({"role": "assistant", "content": example})
messages.append({"role": "user", "content": user_body})
return messages
+13 -5
View File
@@ -113,7 +113,9 @@ _STORYBOARD_SYSTEM = (
<clip index="1">
<time_range>0-3秒</time_range>
<voiceover>这一镜的口播台词</voiceover>
<visual>具体、有画面感的镜头描述(主体/动作/镜头运动/景别/光线)</visual>
<shot>景别、机位高度、焦段、光圈、运镜(6项摄影参数,简短,如『中近景平视,50mm f/1.8,缓慢推镜』)</shot>
<visual>画面主体、场景、光线、氛围(只写画面内容,不含景别/运镜参数,如『暖光木色门店,店员微笑递茶,背景虚化』)</visual>
<action_details>人物动作、表情、物品操作细节(真人出镜镜头必填,纯产品/空镜填空线『-』)</action_details>
<reference_image_index>0</reference_image_index>
</clip>
</clips>
@@ -126,7 +128,7 @@ _STORYBOARD_SYSTEM = (
## 写作要求
1. 口播台词:像真人面对镜头说话,短句、口语化、有停顿有情绪,开头 3 秒给出钩子;不要书面腔,不要机械报参数。
2. 画面描述:写清"观众会看到什么",有动作、有镜头运动、有景别和光线,具体可拍;不堆砌形容词,不写无法实现的画面。
2. <shot> 只写6项摄影参数(景别/机位高度/焦段/光圈/运镜),简短不超过20字;<visual> 写观众会看到的画面主体/场景/光线/氛围;<action_details> 写人物动作/表情/物品操作细节(真人出镜必填)。三字段各司其职,不要混写。
3. copy_display_markdown:直接展示给最终用户的文案,用 Markdown 写成自然、流畅、有感染力的成片文案,可用小标题与短句组织;不要做字段列表,不要出现"镜头一/台词:"这类制作说明。
4. 内容必须来自图片观察与用户给出的信息,不编造卖点、不夸大、不使用绝对化用语和虚假承诺。
5. reference_image_index 填本镜参考图片序号(从 0 开始),没有合适参考图填 -1。
@@ -167,19 +169,25 @@ _STORYBOARD_EXAMPLE = """<script>
<clip index="1">
<time_range>0-4秒</time_range>
<voiceover>生完娃,是不是连照镜子的勇气都没了?</voiceover>
<visual>中近景,暖光下一位妈妈略显疲惫地看向镜中,镜头缓缓推近</visual>
<shot>中近景平视,50mm f/2.0,缓慢推镜</shot>
<visual>暖光浴室镜前,妈妈略显疲惫地看向镜中自己,镜灯打在侧脸</visual>
<action_details>妈妈手扶洗手台,微微低头再抬眼看镜,眼眶微红</action_details>
<reference_image_index>0</reference_image_index>
</clip>
<clip index="2">
<time_range>4-10秒</time_range>
<voiceover>推开门,暖光和一杯热茶先接住了你。</voiceover>
<visual>中景,门店前台暖光木色装修,店员微笑递上茶水,镜头平移扫过环境</visual>
<shot>中景平视,35mm f/2.8,镜头缓慢平移</shot>
<visual>门店前台暖光木色装修,茶水冒热气,背景柔和虚化</visual>
<action_details>店员微笑双手递上茶杯,妈妈伸手接过,指尖轻触杯沿</action_details>
<reference_image_index>1</reference_image_index>
</clip>
<clip index="3">
<time_range>10-15秒</time_range>
<voiceover>来御众堂躺一会儿,整个人都松了。</voiceover>
<visual>特写,妈妈闭眼放松做护理,暖光打在侧脸,最后镜头拉远露出品牌LOGO</visual>
<shot>近景缓慢拉远至中景,85mm f/1.8,匀速后拉</shot>
<visual>护理床暖光打在妈妈侧脸,最后画面露出御众堂品牌LOGO</visual>
<action_details>妈妈闭眼放松,嘴角微微上扬,肩膀缓缓下沉</action_details>
<reference_image_index>2</reference_image_index>
</clip>
</clips>
+4 -9
View File
@@ -17,9 +17,8 @@ from typing import Optional
from packages.application.viral_video import xml_parser as xp
from packages.application.viral_video.prompt_loader import (
build_messages,
get_template,
render_system_prompt,
render_user_prompt,
)
from packages.application.viral_video.prompts import COMPLIANCE_BLACKLIST
from packages.application.viral_video.schemas import ReviewIssue, ReviewResult
@@ -119,16 +118,12 @@ class Reviewer:
if not self.client or not getattr(self.client, "is_available", True):
return None
template = get_template("review")
system = render_system_prompt(template)
user = render_user_prompt(template, voiceover_script=voiceover_script)
messages = build_messages(template, voiceover_script=voiceover_script)
raw = self.client.chat_completion(
[
{"role": "system", "content": system},
{"role": "user", "content": user},
],
messages,
temperature=0.2,
max_tokens=1024,
timeout=25,
timeout=15, # #2307: 25s过长,超时降级放行
)
if not raw:
return None
@@ -37,26 +37,6 @@ class ImageAnalysis(BaseModel):
raw: str = ""
class CoreMessage(BaseModel):
text: str
must_keep: bool = False
confidence: float = 0.0
class PersonalBrand(BaseModel):
text: str
category: str = "brand"
class IntentResult(BaseModel):
intent_summary: str = ""
core_messages: list[CoreMessage] = Field(default_factory=list)
personal_brands: list[PersonalBrand] = Field(default_factory=list)
emotion_tone: str = ""
missing_info: list[str] = Field(default_factory=list)
raw: str = ""
class BodyPoint(BaseModel):
text: str
elaboration: str = ""
+2 -2
View File
@@ -202,9 +202,9 @@ class SharedSettings(BaseSettings):
default=120,
validation_alias=AliasChoices("DITTO_REQUEST_TIMEOUT", "ditto_request_timeout"),
)
# Ditto 句间过渡帧数(平滑表情/口型切换)
# Ditto 句间过渡帧数(平滑表情/口型切换);默认 6 与 GPU 端 clamp 最优值对齐(表情切换更灵敏)
ditto_blend_frames: int = Field(
default=12,
default=6,
validation_alias=AliasChoices("DITTO_BLEND_FRAMES", "ditto_blend_frames"),
)
+14 -1
View File
@@ -21,7 +21,8 @@ class PresetVoice:
description: 音色描述
gender: 性别(male/female)
language: 语言代码(如 zh-CN)
preview_url: 预览音频 URL(可选)
preview_url: 预览音频 URL(可选,前端试听用,短文本)
sample_audio_url: 音色样本音频 URL(#2311,reference_audio用,8秒固定文案,部署预生成)
tags: 标签列表
"""
@@ -31,6 +32,9 @@ class PresetVoice:
gender: str
language: str = "zh-CN"
preview_url: str = ""
sample_audio_url: str = (
"" # #2311: 用于reference_audio的8秒静态音色样本公网URL(部署时由seed_preset_voice_samples.py预生成)
)
tags: list[str] | None = None
def to_dict(self) -> dict:
@@ -42,6 +46,7 @@ class PresetVoice:
"gender": self.gender,
"language": self.language,
"preview_url": self.preview_url,
"sample_audio_url": self.sample_audio_url,
"tags": self.tags or [],
}
@@ -56,6 +61,7 @@ PRESET_VOICES: list[PresetVoice] = [
gender="female",
language="zh-CN",
preview_url="http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/prod/cosyvoice-v3-flash/20260802/1320180660510733/a8fb29a2-7d44-9f16-9dab-f17fa806cf3d.mp3?Expires=1785716123&OSSAccessKeyId=LTAI5tGzqbGcEmE58b221XQy&Signature=c74kFX%2BcDpmuBny2dPBkvak01oY%3D",
sample_audio_url="https://xiaoxia-autocut.oss-cn-hangzhou.aliyuncs.com/static/voice-samples/preset/longxiaochun_v3_sample.mp3",
tags=["知性", "女声", "助手"],
),
PresetVoice(
@@ -65,6 +71,7 @@ PRESET_VOICES: list[PresetVoice] = [
gender="female",
language="zh-CN",
preview_url="http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/prod/cosyvoice-v3-flash/20260802/1320180660510733/d065a6da-de0d-9ad2-b64a-00aaf9aa3dea.mp3?Expires=1785716203&OSSAccessKeyId=LTAI5tGzqbGcEmE58b221XQy&Signature=9hRh7xbmS8eDdiBDDFUatvad1jg%3D",
sample_audio_url="https://xiaoxia-autocut.oss-cn-hangzhou.aliyuncs.com/static/voice-samples/preset/longxiaoxia_v3_sample.mp3",
tags=["沉稳", "女声", "播报"],
),
PresetVoice(
@@ -74,6 +81,7 @@ PRESET_VOICES: list[PresetVoice] = [
gender="male",
language="zh-CN",
preview_url="http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/prod/cosyvoice-v3-flash/20260802/1320180660510733/b139d925-4669-979d-89db-2dc16945b577.mp3?Expires=1785730686&OSSAccessKeyId=LTAI5tGzqbGcEmE58b221XQy&Signature=X2p%2FA7Dm%2BTNfeFilW%2FQO9VoYPLQ%3D",
sample_audio_url="https://xiaoxia-autocut.oss-cn-hangzhou.aliyuncs.com/static/voice-samples/preset/longsanshu_v3_sample.mp3",
tags=["沉稳", "男声", "有声书"],
),
PresetVoice(
@@ -83,6 +91,7 @@ PRESET_VOICES: list[PresetVoice] = [
gender="female",
language="zh-CN",
preview_url="http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/prod/cosyvoice-v3-flash/20260802/1320180660510733/1ea86db3-4dc9-927e-9b76-c83d89f6788c.mp3?Expires=1785716207&OSSAccessKeyId=LTAI5tGzqbGcEmE58b221XQy&Signature=Vg0ckgSgPzgNQ%2FNdeJzrE%3D",
sample_audio_url="https://xiaoxia-autocut.oss-cn-hangzhou.aliyuncs.com/static/voice-samples/preset/longyue_v3_sample.mp3",
tags=["温暖", "女声", "广告"],
),
PresetVoice(
@@ -92,6 +101,7 @@ PRESET_VOICES: list[PresetVoice] = [
gender="male",
language="zh-CN",
preview_url="http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/prod/cosyvoice-v3-flash/20260802/1320180660510733/6b47a5ce-ae21-995a-ac8b-5e55ed04ec45.mp3?Expires=1785716209&OSSAccessKeyId=LTAI5tGzqbGcEmE58b221XQy&Signature=zm6WGmthpwTjf8PzgNQ%2FNdeJzrE%3D",
sample_audio_url="https://xiaoxia-autocut.oss-cn-hangzhou.aliyuncs.com/static/voice-samples/preset/longshu_v3_sample.mp3",
tags=["沉稳", "男声", "教育"],
),
PresetVoice(
@@ -101,6 +111,7 @@ PRESET_VOICES: list[PresetVoice] = [
gender="female",
language="zh-CN",
preview_url="http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/prod/cosyvoice-v3-flash/20260802/1320180660510733/13bf9f6a-72a9-949c-a896-ec3e6b46338b.mp3?Expires=1785730692&OSSAccessKeyId=LTAI5tGzqbGcEmE58b221XQy&Signature=pxrP2rYmOumc4dw9TD8TZf%2B27gk%3D",
sample_audio_url="https://xiaoxia-autocut.oss-cn-hangzhou.aliyuncs.com/static/voice-samples/preset/longyingjing_v3_sample.mp3",
tags=["冷静", "女声", "纪录片"],
),
PresetVoice(
@@ -110,6 +121,7 @@ PRESET_VOICES: list[PresetVoice] = [
gender="male",
language="zh-CN",
preview_url="http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/prod/cosyvoice-v3-flash/20260802/1320180660510733/5e69f0c1-45fa-92ca-ae5c-38272f231efb.mp3?Expires=1785730692&OSSAccessKeyId=LTAI5tGzqbGcEmE58b221XQy&Signature=uREIiIUFrN7CpJ7avXKWCo04A6I%3D",
sample_audio_url="https://xiaoxia-autocut.oss-cn-hangzhou.aliyuncs.com/static/voice-samples/preset/longshuo_v3_sample.mp3",
tags=["干练", "男声", "科技"],
),
PresetVoice(
@@ -119,6 +131,7 @@ PRESET_VOICES: list[PresetVoice] = [
gender="female",
language="zh-CN",
preview_url="http://dashscope-result-bj.oss-cn-beijing.aliyuncs.com/prod/cosyvoice-v3-flash/20260802/1320180660510733/a38e4c05-25bf-9640-92d6-0b4a14555cdb.mp3?Expires=1785716215&OSSAccessKeyId=LTAI5tGzqbGcEmE58b221XQy&Signature=WtbsqqfR4A6OhpL%2B%2Fe0lh7YBMWM%3D",
sample_audio_url="https://xiaoxia-autocut.oss-cn-hangzhou.aliyuncs.com/static/voice-samples/preset/longtian_v3_sample.mp3",
tags=["活泼", "女声", "短视频"],
),
]
+86
View File
@@ -0,0 +1,86 @@
"""爆款视频领域层包(PR3 P2:从单文件转为包,保持原有对外符号不变)。"""
from packages.domain.viral_video.entities import (
STAGE_LABELS,
FusionLevel,
PromptType,
StyleStrength,
ViralVideoJob,
ViralVideoStage,
ViralVideoStatus,
)
from packages.domain.viral_video.errors import (
ViralVideoComplianceError,
ViralVideoConfigError,
ViralVideoCreditsError,
ViralVideoError,
ViralVideoLLMError,
ViralVideoResourceError,
ViralVideoTimeoutError,
ViralVideoUpstreamError,
)
from packages.domain.viral_video.stages import (
PROGRESS_COPY_DONE,
PROGRESS_COPY_START,
PROGRESS_COPY_STREAMING,
PROGRESS_DONE,
PROGRESS_IMAGE_DONE,
PROGRESS_REVIEW,
PROGRESS_START,
PROGRESS_TRUST,
PROGRESS_TTS,
PROGRESS_UPLOAD_DONE,
PROGRESS_VIDEO_DONE,
PROGRESS_VIDEO_START,
STAGE_COPY_GENERATION,
STAGE_IMAGE_ANALYSIS,
STAGE_PHASE_MESSAGES,
STAGE_REVIEW,
STAGE_TRUST_CHAIN,
STAGE_TTS,
STAGE_UPLOADING,
STAGE_VIDEO_ANALYSIS,
STAGE_VIDEO_GENERATION,
)
__all__ = [
# entities
"FusionLevel",
"PromptType",
"STAGE_LABELS",
"StyleStrength",
"ViralVideoJob",
"ViralVideoStage",
"ViralVideoStatus",
# errors
"ViralVideoError",
"ViralVideoConfigError",
"ViralVideoUpstreamError",
"ViralVideoComplianceError",
"ViralVideoResourceError",
"ViralVideoCreditsError",
"ViralVideoTimeoutError",
"ViralVideoLLMError",
# stages
"STAGE_IMAGE_ANALYSIS",
"STAGE_VIDEO_ANALYSIS",
"STAGE_COPY_GENERATION",
"STAGE_REVIEW",
"STAGE_TRUST_CHAIN",
"STAGE_TTS",
"STAGE_VIDEO_GENERATION",
"STAGE_UPLOADING",
"STAGE_PHASE_MESSAGES",
"PROGRESS_START",
"PROGRESS_IMAGE_DONE",
"PROGRESS_COPY_START",
"PROGRESS_COPY_STREAMING",
"PROGRESS_COPY_DONE",
"PROGRESS_REVIEW",
"PROGRESS_TRUST",
"PROGRESS_TTS",
"PROGRESS_VIDEO_START",
"PROGRESS_VIDEO_DONE",
"PROGRESS_UPLOAD_DONE",
"PROGRESS_DONE",
]
@@ -3,9 +3,10 @@
v1.6 重大简化:Seedance 2.5 单次最长30秒,单次调用直接出片,不再分段/拼接/ffmpeg concat。
状态机(三步分步):
pending -> running -> image_analyzed -> running -> copy_generated -> running -> completed
wait_user_confirm -> running -> completed (旧路径兼容)
任意阶段 fail; 任意非终态 cancel.
failed -> pending (retry 重置后重跑)。
意图解析(intent_parsing) 已并入脚本生成一步完成,不再单独作为阶段/状态存在。
"""
from __future__ import annotations
@@ -31,7 +32,6 @@ class ViralVideoStatus(StrEnum):
RUNNING = "running"
IMAGE_ANALYZED = "image_analyzed"
COPY_GENERATED = "copy_generated"
WAIT_USER_CONFIRM = "wait_user_confirm"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
@@ -40,7 +40,6 @@ class ViralVideoStatus(StrEnum):
class ViralVideoStage(StrEnum):
IMAGE_ANALYSIS = "image_analysis"
VIDEO_ANALYSIS = "video_analysis"
INTENT_PARSING = "intent_parsing"
SCRIPT_GENERATION = "script_generation" # v1.6: 编导分镜脚本(融合原 copy_fusion+storyboard+review)
REVIEW = "review"
TTS = "tts"
@@ -62,7 +61,6 @@ class StyleStrength(StrEnum):
class PromptType(StrEnum):
IMAGE_ANALYSIS = "image_analysis"
INTENT_PARSING = "intent_parsing"
SCRIPT_GENERATION = "script_generation"
REVIEW = "review"
VIDEO_STYLE_INTEGRATION = "video_style_integration"
@@ -72,7 +70,6 @@ class PromptType(StrEnum):
STAGE_LABELS = {
ViralVideoStage.IMAGE_ANALYSIS: "图片分析",
ViralVideoStage.VIDEO_ANALYSIS: "视频风格分析",
ViralVideoStage.INTENT_PARSING: "意图解析",
ViralVideoStage.SCRIPT_GENERATION: "编导脚本生成",
ViralVideoStage.REVIEW: "合规审核",
ViralVideoStage.TTS: "AI 配音",
@@ -95,6 +92,7 @@ class ViralVideoJob:
persona_id: str = ""
viral_structure: str = ""
marketing_purpose: str = ""
tone: str = ""
bgm_preference: str = ""
duration: int = 15 # v1.6: 默认15秒,上限30秒(Seedance 2.5 单次最大30s)
user_copy_text: str = ""
@@ -112,7 +110,6 @@ class ViralVideoJob:
video_model: str = ""
# v1.4+ 产物
image_analysis: dict | None = None
intent_result: dict | None = None
generated_copy_text: str = "" # v1.6: 存 voiceover_script(纯口播对白),字段名兼容
storyboard: list | None = None # v1.6: 存 copy_result.shots,字段名兼容
copy_result: dict | None = None # v1.6: 完整编导脚本结构
@@ -141,7 +138,6 @@ class ViralVideoJob:
ViralVideoStatus.PENDING,
ViralVideoStatus.IMAGE_ANALYZED,
ViralVideoStatus.COPY_GENERATED,
ViralVideoStatus.WAIT_USER_CONFIRM,
ViralVideoStatus.RUNNING,
):
raise ValueError(f"Cannot transition from {self.status} to running")
@@ -184,17 +180,10 @@ class ViralVideoJob:
self.storyboard = list(shots) if isinstance(shots, list) else []
self.updated_at = datetime.now(timezone.utc)
def mark_wait_user_confirm(self, intent_result: dict) -> None:
if self.status != ViralVideoStatus.RUNNING:
raise ValueError(f"Cannot transition from {self.status} to wait_user_confirm")
self.status = ViralVideoStatus.WAIT_USER_CONFIRM
self.intent_result = intent_result
self.updated_at = datetime.now(timezone.utc)
def resume_from_image_analyzed(self, **kwargs) -> None:
"""阶段2入口:允许从 IMAGE_ANALYZED/PENDING 首次进入,也允许从 COPY_GENERATED/COMPLETED/FAILED 重新生成文案。
重新生成时清空上一轮文案产物(copy_result/intent_result/storyboard/generated_copy_text),
重新生成时清空上一轮文案产物(copy_result/storyboard/generated_copy_text),
并重置 completed_at/result_video_url/error_msg,确保前端轮询能看到新的阶段2进度。
"""
_allowed = (
@@ -216,7 +205,6 @@ class ViralVideoJob:
setattr(self, k, v)
if _is_regen:
# 清空上一轮文案/视频产物,避免前端拿到旧数据
self.intent_result = None
self.copy_result = None
self.storyboard = None
self.generated_copy_text = ""
@@ -252,15 +240,6 @@ class ViralVideoJob:
self.completed_at = None
self.credits_cost = 0.0
def resume_from_confirm(self) -> None:
if self.status != ViralVideoStatus.WAIT_USER_CONFIRM:
raise ValueError(f"Cannot resume from {self.status}")
_now = datetime.now(timezone.utc)
self.started_at = _now
self.heartbeat_at = _now
self.status = ViralVideoStatus.RUNNING
self.updated_at = _now
def mark_completed(self, video_url: str) -> None:
self.status = ViralVideoStatus.COMPLETED
self.result_video_url = video_url
+167
View File
@@ -0,0 +1,167 @@
"""爆款视频领域统一异常体系(PR3 P2)。
所有爆款视频相关业务异常均继承 ViralVideoError,保证:
1. 上层可统一捕获,按 code/user_message 组装前端响应;
2. 不暴露内部栈/traceback;
3. http_status 供 API 层映射 HTTP 状态码。
"""
from __future__ import annotations
class ViralVideoError(Exception):
"""爆款视频业务异常基类。"""
code: str = "viral_video_error"
user_message: str = "视频生成失败,请稍后重试"
detail: str | None = None
http_status: int = 500
def __init__(
self,
user_message: str | None = None,
detail: str | None = None,
code: str | None = None,
http_status: int | None = None,
) -> None:
if user_message is not None:
self.user_message = user_message
self.detail = detail
if code is not None:
self.code = code
if http_status is not None:
self.http_status = http_status
super().__init__(self.user_message)
def __str__(self) -> str: # pragma: no cover
return f"[{self.code}] {self.user_message}" + (f" :: {self.detail}" if self.detail else "")
class ViralVideoConfigError(ViralVideoError):
"""配置/初始化错误:缺模型、缺 API Key、依赖未就绪等。"""
code = "config_error"
http_status = 500
def __init__(
self,
user_message: str = "服务配置异常,请稍后重试",
detail: str | None = None,
) -> None:
super().__init__(user_message=user_message, detail=detail)
class ViralVideoUpstreamError(ViralVideoError):
"""上游模型/云服务错误(火山 Seedance、阿里 Wan、OSS 等)。"""
code = "upstream_error"
http_status = 502
def __init__(
self,
provider: str,
upstream_code: str = "unknown",
user_message: str = "视频生成服务暂时不可用,请稍后重试",
detail: str | None = None,
status_code: int | None = None,
) -> None:
self.provider = provider
self.upstream_code = upstream_code
self.status_code = status_code
super().__init__(user_message=user_message, detail=detail)
class ViralVideoComplianceError(ViralVideoError):
"""合规审核不通过(用户文案命中敏感/违规内容)。"""
code = "compliance_rejected"
http_status = 400
def __init__(
self,
user_message: str = "文案未通过合规审核,请修改后重试",
issues: list | None = None,
detail: str | None = None,
) -> None:
self.issues: list = list(issues) if issues else []
super().__init__(user_message=user_message, detail=detail)
class ViralVideoResourceError(ViralVideoError):
"""资源不存在/失效:OSS URL 过期、图片找不到、返回空文件等。"""
code = "resource_error"
http_status = 502
def __init__(
self,
resource_type: str = "file",
resource_id: str = "",
user_message: str = "素材失效,请重新上传后重试",
detail: str | None = None,
) -> None:
self.resource_type = resource_type
self.resource_id = resource_id
super().__init__(user_message=user_message, detail=detail)
class ViralVideoCreditsError(ViralVideoError):
"""积分不足/结算失败。"""
code = "credits_insufficient"
http_status = 402
def __init__(
self,
user_message: str = "积分不足,请充值后重试",
detail: str | None = None,
) -> None:
super().__init__(user_message=user_message, detail=detail)
class ViralVideoTimeoutError(ViralVideoError):
"""某阶段执行超时(LLM/渲染/上传)。"""
code = "timeout"
http_status = 504
def __init__(
self,
stage: str,
timeout_s: float = 0.0,
user_message: str = "处理超时,请稍后重试",
detail: str | None = None,
) -> None:
self.stage = stage
self.timeout_s = timeout_s
super().__init__(user_message=user_message, detail=detail)
class ViralVideoLLMError(ViralVideoError):
"""LLM 调用失败/重试耗尽/解析失败。"""
code = "llm_error"
http_status = 502
def __init__(
self,
stage: str = "script_generation",
model: str = "",
user_message: str = "文案生成失败,请稍后重试",
detail: str | None = None,
) -> None:
self.stage = stage
self.model = model
super().__init__(user_message=user_message, detail=detail)
__all__ = [
"ViralVideoError",
"ViralVideoConfigError",
"ViralVideoUpstreamError",
"ViralVideoComplianceError",
"ViralVideoResourceError",
"ViralVideoCreditsError",
"ViralVideoTimeoutError",
"ViralVideoLLMError",
]
+68
View File
@@ -0,0 +1,68 @@
"""爆款视频阶段与进度点常量(PR3 P2)。
worker/API 中硬编码的阶段字符串与进度数值集中到此文件统一维护;
注意:Celery task name / HTTP path / WS event name 属对外契约,不在此列。
"""
from __future__ import annotations
# ── 阶段标识(对齐 ViralVideoStage 但更贴合 v1.6 实际链路) ──────────────
STAGE_IMAGE_ANALYSIS: str = "image_analysis"
STAGE_VIDEO_ANALYSIS: str = "video_analysis"
STAGE_COPY_GENERATION: str = "script_generation" # v1.6 文案=编导分镜
STAGE_REVIEW: str = "review"
STAGE_TRUST_CHAIN: str = "trust_chain" # #2172 信任链预热
STAGE_TTS: str = "tts"
STAGE_VIDEO_GENERATION: str = "rendering"
STAGE_UPLOADING: str = "uploading"
# ── 阶段中文提示 ────────────────────────────────────────────────────────
STAGE_PHASE_MESSAGES: dict[str, str] = {
STAGE_IMAGE_ANALYSIS: "正在分析图片内容...",
STAGE_VIDEO_ANALYSIS: "正在分析参考视频风格...",
STAGE_COPY_GENERATION: "正在生成文案与分镜...",
STAGE_REVIEW: "正在进行出片前合规审核...",
STAGE_TRUST_CHAIN: "正在AI化人像参考...",
STAGE_TTS: "正在合成AI配音...",
STAGE_VIDEO_GENERATION: "正在生成视频(约1-3分钟)...",
STAGE_UPLOADING: "正在上传成片...",
}
# ── 关键进度点(0-100)────────────────────────────────────────────────
PROGRESS_START: float = 0.0
PROGRESS_IMAGE_DONE: float = 10.0 # 图片分析完成
PROGRESS_COPY_START: float = 15.0 # 进入文案生成
PROGRESS_COPY_STREAMING: float = 30.0 # 文案流式输出中
PROGRESS_COPY_DONE: float = 45.0 # 文案生成完成
PROGRESS_REVIEW: float = 50.0 # 合规审核
PROGRESS_TRUST: float = 60.0 # 信任链 AI 化
PROGRESS_TTS: float = 65.0 # TTS 合成
PROGRESS_VIDEO_START: float = 70.0 # 开始视频生成
PROGRESS_VIDEO_DONE: float = 95.0 # 视频生成完成
PROGRESS_UPLOAD_DONE: float = 98.0 # 上传完成
PROGRESS_DONE: float = 100.0 # 全流程完成
__all__ = [
"STAGE_IMAGE_ANALYSIS",
"STAGE_VIDEO_ANALYSIS",
"STAGE_COPY_GENERATION",
"STAGE_REVIEW",
"STAGE_TRUST_CHAIN",
"STAGE_TTS",
"STAGE_VIDEO_GENERATION",
"STAGE_UPLOADING",
"STAGE_PHASE_MESSAGES",
"PROGRESS_START",
"PROGRESS_IMAGE_DONE",
"PROGRESS_COPY_START",
"PROGRESS_COPY_STREAMING",
"PROGRESS_COPY_DONE",
"PROGRESS_REVIEW",
"PROGRESS_TRUST",
"PROGRESS_TTS",
"PROGRESS_VIDEO_START",
"PROGRESS_VIDEO_DONE",
"PROGRESS_UPLOAD_DONE",
"PROGRESS_DONE",
]
+35 -14
View File
@@ -286,6 +286,11 @@ class DoubaoClient:
if not self.is_available:
return None
# #2304 追加修复:每次调用必须重置 finish_reason 标记,避免跨调用泄漏导致误判截断(Bug:
# attempt1 收到 finish=length 后 last_finish_reason='length',attempt2 开头 chunk 处理后
# 立即读到这个陈旧值误判截断、丢弃好内容)。
self.last_finish_reason = ""
url = f"{self.base_url}/chat/completions"
headers = {
"Authorization": f"Bearer {self.api_key}",
@@ -302,6 +307,11 @@ class DoubaoClient:
if self.extra_params:
payload.update(self.extra_params)
if kwargs:
# #2303 追加修复:豆包 ark 端点不认 enable_thinking 布尔参数,
# 必须用 thinking={"type":"disabled"} 才会真正关闭 reasoning(A/B实测TTFT从100s+→2s)。
# 调用方传 enable_thinking=False 时,统一翻译为标准字段。
if kwargs.pop("enable_thinking", None) is False:
payload["thinking"] = {"type": "disabled"}
payload.update(kwargs)
last_error: Optional[Exception] = None
@@ -405,10 +415,21 @@ class DoubaoClient:
if self.extra_params:
payload.update(self.extra_params)
if kwargs:
# 同同步路径:enable_thinking=False 翻译为 thinking={"type":"disabled"}
_thinking_disabled = kwargs.pop("enable_thinking", None) is False
payload.update(kwargs)
if _thinking_disabled:
payload["thinking"] = {"type": "disabled"}
# 每次调用 reset finish_reason 标记(同 chat_completion,避免跨 attempt 泄漏误判截断)
self.last_finish_reason = ""
_t0 = time.time()
for attempt in range(self.max_retries + 1):
# #2303 追加修复:流式路径 SDK 内重试对 read timeout 翻倍耗时(单 attempt 外层120s +
# SDK 重试120s*2 ≈ 360-420s),把两个 attempt 总预算吃光。外层 _step_script_generation
# 已实现 fast→fast→pro 三级attempt兜底,这里禁 SDK 内重试,超时/错误直接抛给外层。
_stream_max_retries = 0
for attempt in range(_stream_max_retries + 1):
try:
_req_timeout = httpx.Timeout(connect=10.0, read=120.0, write=30.0, pool=10.0)
if timeout:
@@ -439,6 +460,8 @@ class DoubaoClient:
continue
delta = choices[0].get("delta") or {}
content_piece = delta.get("content") or ""
# reasoning_content 由豆包 seed-2-1-pro 思考模式返回,
# 对模板填充式生成无用(会占满 deadline),调用方可传 enable_thinking=False 关闭
if content_piece:
yield content_piece
finish_reason = choices[0].get("finish_reason")
@@ -453,19 +476,13 @@ class DoubaoClient:
)
return
except Exception as e:
if attempt < self.max_retries:
wait = 0.5 * (2**attempt)
logger.warning(
"豆包流式API失败,%.1fs后重试 (%d/%d, elapsed=%.1fs): %s",
wait,
attempt + 1,
self.max_retries + 1,
time.time() - _t0,
e,
)
time.sleep(wait)
continue
logger.error("豆包流式API最终失败: elapsed=%.1fs err=%s", time.time() - _t0, e)
# 流式路径禁 SDK 内重试(外层 attempt 机制兜底,避免单 attempt 被 SDK 重试拖到 300s+)
err_msg = str(e).lower()
is_timeout = any(k in err_msg for k in ("timeout", "timed out", "deadline", "超时", "read timeout"))
if is_timeout:
logger.warning("[doubao] 流式 read timeout elapsed=%.1fs,交外层attempt处理", time.time() - _t0)
else:
logger.error("豆包流式API失败: elapsed=%.1fs err=%s", time.time() - _t0, e)
return
def vision_completion(
@@ -536,8 +553,12 @@ class DoubaoClient:
if self.extra_params:
payload.update(self.extra_params)
if kwargs:
if kwargs.pop("enable_thinking", None) is False:
payload["thinking"] = {"type": "disabled"}
payload.update(kwargs)
self.last_finish_reason = "" # reset,避免跨调用泄漏
req_timeout = self._resolve_timeout(timeout or self.timeout)
last_error: Optional[Exception] = None
_t0 = time.time()
+116
View File
@@ -0,0 +1,116 @@
# 爆款视频模块全面审计报告
**审计日期**: 2026-10-14
**审计分支**: fix/viral-script-dual-format-parser
**审计人**: CodeAct Agent
**基准 commit**: 5e5a9b6 (#2294)
---
## 一、Parser 改造说明
### 改造内容
将 `_script_from_xml`(仅支持纯 XML)改造为 `_parse_script`,支持三种 LLM 输出格式:
| 格式 | 描述 | 解析路径 |
|------|------|----------|
| A: 纯 XML | `<script>...</script>` 包裹(v3/v4) | `_parse_script_xml` |
| B: 双段式 | 中文 markdown 说明 + 末尾 `<script>` XML 块 | `_parse_script_xml`(提取最后一个 script 块) |
| C: 纯中文 markdown | 无 `<script>` 标签,按"镜头1/镜头2..."分段 | `_parse_script_markdown` |
| (兼容) 无包裹 v3 XML | 含 `<clips>/<voiceover_script>` 但无 `<script>` 包裹 | `_parse_script_xml`(整个 raw 当 XML body) |
### 解析策略
1. 正则 `r'<script[^>]*>(.*?)</script>'` (DOTALL) 提取所有 `<script>` 块,取最后一个
2. 有 XML 块 → 走 XML 解析(`_parse_script_xml`)
3. 无 `<script>` 但含 v3 结构标签(`<clips>/<voiceover_script>/<copy_display_markdown>`)→ 整个 raw 走 XML 解析(兼容旧格式)
4. 都不匹配 → 走中文 markdown 兜底解析(`_parse_script_markdown`)
5. 都解析不出 → 返回 None
### 代码变更
- `_script_from_xml` → `_parse_script`(入口函数)
- 新增 `_parse_script_xml`(XML 路径解析,从原函数抽出)
- 新增 `_parse_script_markdown`(中文 markdown 兜底解析)
- 模块级常量:`_MD_SHOT_SPLIT_RE`, `_MD_TIME_RE`, `_MD_FIELD_RE` 等正则
---
## 二、Seedance voiceover bug 修复说明
### 问题
`_assemble_seedance_prompt`(~line 1990/2010)循环 shots 时读 `s.get("voiceover", "")`,但原 `_script_from_xml` 输出的 shot dict 没有 `voiceover` 字段——口播被拼进了 `scene_and_dialogue = f"{visual}|{voice}"`,导致 Seedance/Wan prompt 中口播为空。
### 修复
1. `_parse_script_xml`: 每个 shot dict 显式设置 `"voiceover": voice or ""`
2. `_parse_script_markdown`: 同样输出 `"voiceover": voice` 字段
3. `_fallback_script`: shot dict 加 `"voiceover": voiceover`(就是那一句 voiceover)
4. `_assemble_seedance_prompt` 的 Wan 和 Seedance 两个分支都已正确读 `s.get("voiceover")`,无需改动
### 验证
- 新增测试 `TestShotVoiceoverField`(3 个用例)确认 XML/Markdown/Fallback 三条路径的 shot 都有 `voiceover`
- 新增测试 `test_assemble_seedance_uses_shot_voiceover` 确认 Seedance prompt 能取到口播
---
## 三、冗余代码清理情况
| 项目 | 状态 | 说明 |
|------|------|------|
| `_script_from_xml` → `_parse_script` 重命名 | ✅ 完成 | 调用点(line ~1484)已同步更新 |
| `_safe_json_loads` | ⚠️ 保留 | 已不在主流程使用(JSON fallback 路径已删),但 2 个单测引用它,暂保留 |
| `_validate_copy_result` docstring | ✅ 更新 | 引用从 `_script_from_xml` 改为 `_parse_script` |
| `_step_script_generation` docstring | ✅ 更新 | 反映双格式解析支持 |
| reviewer.py voiceover_script XML 标签 | ✅ 确认无需改 | `<voiceover_script>` 仅存在于 reviewer 自己构造的 messages 里,跟 storyboard 输出格式无关 |
| JSON fallback 残留 | ✅ 无残留 | 主流程无 JSON 解析路径,`_safe_json_loads` 仅测试引用 |
---
## 四、全面审计结果
### 审计清单 9 项
| # | 审计项 | 验证结果 | 发现问题 | 修复情况 |
|---|--------|----------|----------|----------|
| 1 | **用户配置参数链路** | ✅ 基本完整 | `tone` 字段未在 API schema (GenerateCopyRequest) 中定义,但 worker 用 `getattr(job, "tone", "") or "亲切自然"` 优雅降级,不影响功能 | 记录为 minor issue,不影响本次 PR |
| 2 | **图片分析→文案→review→渲染全流程** | ✅ 链路完整 | `_step_image_analysis` → `_step_script_generation` → `_step_review` → `_step_tts` → `_step_render` → `_step_upload`,每步有错误处理和降级 | 无需修复 |
| 3 | **三视图机制** | ✅ 链路完整 | `pre_trusted_images` → `_build_character_section` → Seedream 三视图 → reference_images 通道。代码注释明确标注"当前为空实现,仅保留扩展点" | 无需修复 |
| 4 | **few-shot example_output** | ✅ 正确使用 | `prompt_loader.build_messages` 在 storyboard 和 reviewer 两个场景都正确注入 few-shot(system→user示例→assistant示例→user任务) | 无需修复 |
| 5 | **字数/时长约束** | ✅ 链路正确 | `constants.DURATION_SPEC` → `build_duration_constraint_text` → `{duration_constraint_text}` 占位符注入 → `_validate_copy_result` 校验 → `_truncate_voiceover` 截断 | 无需修复 |
| 6 | **重试/超时机制** | ✅ 链路正确 | deadline 300s + fast@0.8 → fast@0.6 → pro@0.7 三档重试 + 流式外层 deadline + next() 超时 + 口播超长自动截断(`_truncate_voiceover`)| 无需修复 |
| 7 | **fallback 脚本** | ✅ brand==name 去重生效 | `_fallback_script` 中 `_brand_str == _name_str or _brand_str in _name_str or _name_str in _brand_str` 三重判断确保去重(#2294 修复) | 本次新增 `voiceover` 字段到 shot dict |
| 8 | **TTS/口型驱动链路** | ✅ 链路完整 | `voiceover_script` → `_step_tts` (CosyVoice) → 音频 URL → `_step_render` (Seedance) → 视频 → Ditto/MuseTalk 口型。voiceover 字段修复后链路更完整 | voiceover bug 修复(本次 PR) |
| 9 | **review 审核阶段** | ✅ 逻辑正确 | `reviewer.py` 的 `_llm_review_inner` 用 `build_messages(template, voiceover_script=...)` 送审,XML 解析 `<passed>/<issues>`,审核失败合并本地规则后返回;LLM 不可用时降级放行(带 warning issue)| 无需修复 |
### 发现的 Minor Issues(不阻塞本次 PR)
1. **`tone` 参数未暴露到 API**: `GenerateCopyRequest` schema 没有 `tone` 字段,但 worker 用 `getattr` 优雅降级到默认值 "亲切自然"。前端如需自定义语气,需后续版本补 schema 字段。
2. **`_safe_json_loads` 死代码**: 主流程已不使用(JSON fallback 在 PR1 已删),但 2 个单测 (`test_safe_json_loads_*`) 引用它。建议下个 PR 清理。
3. **API ShotScript schema 缺 `voiceover` 字段**: `apps/api/app/schemas/viral_video.py` 的 `ShotScript` 模型没有 `voiceover` 字段定义。前端目前从 `scene_and_dialogue` 拆口播,后续可考虑暴露 `voiceover` 字段给前端。
---
## 五、测试覆盖情况
### 新增测试文件: `tests/unit/test_viral_video_dual_parser.py`
| 测试类 | 用例数 | 覆盖范围 |
|--------|--------|----------|
| `TestDualFormatParsing` | 2 | 双段式(中文+XML)解析 + voiceover 字段 |
| `TestPureXmlParsing` | 2 | 纯 XML 兼容性 + voiceover 字段 |
| `TestPureMarkdownParsing` | 9 | 纯中文 markdown 解析(shots/theme/voiceover/time_range/audio/transition/reference/拼接/中文数字) |
| `TestShotVoiceoverField` | 3 | XML/Fallback/Seedance 三条路径的 voiceover 字段 |
| `TestParameterPassing` | 3 | marketing_purpose/target_audience/fusion_level 参数传递 |
| `TestMarkdownParseFailures` | 4 | 解析失败返回 None(纯文本/空字符串/None/无口播) |
**合计**: 23 个新增用例,全部通过。
### 回归测试
- 全量 viral 相关测试: **269 passed, 2 skipped**
- 覆盖了 test_viral_video.py / test_viral_video_p0.py / test_viral_video_p2_errors.py / test_viral_video_prompt_system.py / test_viral_video_routes.py / test_viral_video_v8_bugs.py / test_viral_video_wiring.py / test_viral_video_ws.py / test_viral_video_models.py
---
## 六、PR 信息
- **分支**: `fix/viral-script-dual-format-parser`
- **Base**: `develop`
- **PR**: 待创建
+204
View File
@@ -0,0 +1,204 @@
#!/usr/bin/env python3
"""#2311 部署脚本:为所有预置音色生成 8 秒静态音色样本并上传 OSS。
用途:
部署 release 时执行,为 8 个 CosyVoice 预置音色各合成一段 8s 固定样本 mp3,
上传到 OSS 永久路径 static/voice-samples/preset/{voice_id}_sample.mp3(公共读),
并回填 packages/domain/preset_voices.py 中 PresetVoice.sample_audio_url 字段。
出片流程通过 PresetVoice.sample_audio_url 直接读取,零 TTS、零冷启动。
用法(在 API/Worker 容器内执行):
cd /path/to/project && python scripts/seed_preset_voice_samples.py
可选参数:
--dry-run 只打印要做的操作,不实际上传/回填
--force 强制重新生成(即使 OSS 已有文件也覆盖)
--no-public 上传后不设公共读 ACL(默认公共读)
"""
from __future__ import annotations
import argparse
import logging
import os
import re
import sys
import tempfile
import urllib.request
from pathlib import Path
import oss2 # type: ignore
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
# 固定 8 秒样本文案(覆盖中文四声、开合齐撮、常见声母韵母)
VOICE_SAMPLE_SCRIPT_ZH = "你好,很高兴认识你。今天天气真不错,我们一起来看看吧。"
VOICE_SAMPLE_SCRIPT_EN = "Hi there, nice to meet you. The weather is great today, let's take a look together."
# OSS 路径前缀
OSS_PREFIX = "static/voice-samples/preset/"
def _project_root() -> Path:
"""定位项目根目录(兼容容器/软链/不同 cwd 场景)。"""
script_dir = Path(__file__).resolve().parent
candidate = script_dir.parent
if (candidate / "packages" / "domain" / "preset_voices.py").exists():
return candidate
cur = Path.cwd().resolve()
for _ in range(8):
if (cur / "packages" / "domain" / "preset_voices.py").exists():
return cur
if cur.parent == cur:
break
cur = cur.parent
raise RuntimeError("无法定位项目根目录,请在项目根目录或 scripts/ 下执行本脚本")
PROJECT_ROOT = _project_root()
PRESET_VOICES_PY = PROJECT_ROOT / "packages" / "domain" / "preset_voices.py"
sys.path.insert(0, str(PROJECT_ROOT))
def _get_cosyvoice_service():
from packages.application.cosyvoice_service import CosyVoiceService
return CosyVoiceService()
def _get_oss_bucket():
endpoint = os.environ.get("OSS_ENDPOINT", "")
ak = os.environ.get("OSS_ACCESS_KEY_ID", "")
sk = os.environ.get("OSS_ACCESS_KEY_SECRET", "")
bucket_name = os.environ.get("OSS_BUCKET_NAME", "")
if not (endpoint and ak and sk and bucket_name):
try:
from packages.shared.config import get_shared_settings
s = get_shared_settings()
endpoint = endpoint or getattr(s, "oss_endpoint", "")
ak = ak or getattr(s, "oss_access_key_id", "")
sk = sk or getattr(s, "oss_access_key_secret", "")
bucket_name = bucket_name or getattr(s, "oss_bucket_name", "")
except Exception as e:
logger.warning("从settings读取OSS配置失败: %s", e)
if not (endpoint and ak and sk and bucket_name):
logger.error("缺少 OSS 凭证,请设置 OSS_ENDPOINT/OSS_ACCESS_KEY_ID/OSS_ACCESS_KEY_SECRET/OSS_BUCKET_NAME")
sys.exit(3)
ep_host = endpoint.replace("https://", "").replace("http://", "").rstrip("/")
auth = oss2.Auth(ak, sk)
bucket = oss2.Bucket(auth, f"https://{ep_host}", bucket_name)
public_base = f"https://{bucket_name}.{ep_host}"
return bucket, public_base
def main() -> int:
ap = argparse.ArgumentParser(description="#2311 预生成预置音色静态样本")
ap.add_argument("--dry-run", action="store_true")
ap.add_argument("--force", action="store_true")
ap.add_argument("--no-public", action="store_true")
args = ap.parse_args()
from packages.domain.preset_voices import PRESET_VOICES
logger.info("项目根目录: %s", PROJECT_ROOT)
logger.info("预置音色总数: %d", len(PRESET_VOICES))
cosy = _get_cosyvoice_service()
bucket, public_base = _get_oss_bucket()
voice_to_url: dict[str, str] = {}
tmpdir = Path(tempfile.mkdtemp(prefix="preset_voice_sample_"))
for voice in PRESET_VOICES:
voice_id = voice.voice_id
lang = voice.language or "zh-CN"
script = VOICE_SAMPLE_SCRIPT_EN if lang.lower().startswith("en") else VOICE_SAMPLE_SCRIPT_ZH
oss_key = f"{OSS_PREFIX}{voice_id}_sample.mp3"
public_url = f"{public_base}/{oss_key}"
exists = False
try:
exists = bucket.object_exists(oss_key)
except Exception as e:
logger.warning("[%s] 检查OSS存在失败: %s", voice_id, e)
if exists and not args.force:
logger.info("[%s] OSS已有样本,跳过: %s", voice_id, public_url)
voice_to_url[voice_id] = public_url
continue
if args.dry_run:
logger.info("[DRY][%s] 将合成「%s...」→ oss://%s", voice_id, script[:20], oss_key)
voice_to_url[voice_id] = public_url
continue
logger.info("[%s] 合成中...", voice_id)
try:
result = cosy.synthesize_speech(text=script, voice_id=voice_id, format="mp3")
except Exception as e:
logger.error("[%s] TTS合成失败: %s", voice_id, e, exc_info=True)
continue
local_mp3 = tmpdir / f"{voice_id}_sample.mp3"
try:
urllib.request.urlretrieve(result.audio_url, str(local_mp3))
size = local_mp3.stat().st_size
if size < 4096:
logger.error("[%s] 文件太小(%d),跳过", voice_id, size)
continue
logger.info("[%s] 下载完成: %d bytes", voice_id, size)
except Exception as e:
logger.error("[%s] 下载合成音频失败: %s", voice_id, e, exc_info=True)
continue
logger.info("[%s] 上传 oss://%s", voice_id, oss_key)
try:
headers = {"Content-Type": "audio/mpeg"}
bucket.put_object_from_file(oss_key, str(local_mp3), headers=headers)
if not args.no_public:
bucket.put_object_acl(oss_key, oss2.OBJECT_ACL_PUBLIC_READ)
logger.info("[%s] 上传完成 → %s", voice_id, public_url)
voice_to_url[voice_id] = public_url
except Exception as e:
logger.error("[%s] OSS上传失败: %s", voice_id, e, exc_info=True)
continue
if not voice_to_url:
logger.warning("没有成功生成任何样本")
return 1
if args.dry_run:
logger.info("[DRY] 将回填 %d 个 sample_audio_url 到 %s", len(voice_to_url), PRESET_VOICES_PY)
for vid, url in voice_to_url.items():
logger.info(" %s → %s", vid, url)
return 0
text = PRESET_VOICES_PY.read_text(encoding="utf-8")
orig = text
for voice_id, url in voice_to_url.items():
block_pat = re.compile(
r'(PresetVoice\([^)]*?voice_id="' + re.escape(voice_id) + r'"[^)]*?sample_audio_url=)"[^"]*"',
re.DOTALL,
)
new_text, n = block_pat.subn(rf'\1"{url}"', text, count=1)
if n == 0:
logger.warning("[%s] 回填sample_audio_url失败(正则未匹配),请手动添加", voice_id)
continue
text = new_text
if text != orig:
PRESET_VOICES_PY.write_text(text, encoding="utf-8")
logger.info("✅ 已回填 %d 条 sample_audio_url 到 %s", len(voice_to_url), PRESET_VOICES_PY)
logger.info("下一步:git diff packages/domain/preset_voices.py 确认后 commit & push")
else:
logger.info("preset_voices.py 未变化(可能全部已回填过)")
return 0
if __name__ == "__main__":
sys.exit(main())
+247
View File
@@ -0,0 +1,247 @@
"""Seed viral_video_param_guides 表(PR3 P2-h)。
将 worker 中硬编码的人设/结构/语言默认值灌入 DB,保证部署后查询有默认返回;
运营可后续在后台修改,30s TTL 缓存自动生效。
用法:
cd /root/work/xiaoxia-saas
PYTHONPATH=. python scripts/seed_viral_video_param_guides.py
幂等:用 ON CONFLICT (guide_type, guide_key) DO UPDATE 保证重复执行安全。
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from sqlalchemy import create_engine, text # noqa: E402
SEED_DATA: list[tuple[str, str, str]] = [
# ── persona:人设风格 ──────────────────────────────────────────────
("persona", "宝妈", "温柔贴心,多从带娃和家庭实用角度分享,语气亲切有共鸣"),
("persona", "店主", "热情实在,像当面招呼客人,突出靠谱和实在优惠"),
("persona", "专业顾问", "专业可信,讲清原理和效果,用事实打消顾虑"),
("persona", "年轻达人", "活泼有网感,节奏轻快,金句和梗自然不尬"),
("persona", "老板型IP", "以老板第一人称出镜,真诚接地气,像招呼街坊邻居一样分享,突出创业初心和靠谱"),
("persona", "知识博主", "条理清晰、数据说话,干货密度高,语气专业但不枯燥"),
("persona", "生活美学", "画面感强,注重氛围和质感描述,语速偏慢,文字有诗意"),
("persona", "健身教练", "energetic、鼓励式口吻,强调动作要领和效果变化"),
("persona", "美妆达人", "细腻讲质地和妆效,像闺蜜安利,语气亲切有感染力"),
("persona", "美食博主", "色香味描述丰富,口语化带馋感,节奏轻快"),
("persona", "穿搭博主", "讲搭配逻辑和场景适配,时尚但不高冷,像朋友建议"),
("persona", "育儿师", "科学育儿角度,温柔坚定,给具体可操作的建议"),
# ── structure:爆款结构 ────────────────────────────────────────────
(
"structure",
"反差破局+亮明观点+还原现状",
"开头3秒用反差/痛点钩子抓注意力,中段亮出核心卖点或观点,结尾还原真实到店/使用场景引导行动",
),
(
"structure",
"痛点切入+方案展示+效果对比",
"开头直击用户痛点场景,中间展示产品/服务解决方案,结尾用前后对比强化效果",
),
(
"structure",
"故事引入+产品种草+行动引导",
"用一个真实小故事/案例引入,自然过渡到产品种草,结尾明确引导用户下一步行动",
),
(
"structure",
"场景展示+价值输出+信任背书",
"开头展示使用场景让用户代入,中间输出核心价值主张,结尾用客户评价/数据等信任背书收尾",
),
("structure", "悬念开场+层层递进+高潮转化", "开头制造悬念引发好奇,内容层层推进保持张力,高潮处给出转化钩子"),
# ── language:语言提示 ─────────────────────────────────────────────
("language", "zh-cn", "使用标准普通话,口语化表达"),
("language", "zh-tw", "使用台湾腔中文,语气温柔亲切"),
("language", "zh-hk", "使用粤语风格中文表达"),
("language", "en-us", "使用美式英语,自然口语化"),
("language", "en-gb", "使用英式英语"),
("language", "ja-jp", "使用日语,自然口语化"),
("language", "ko-kr", "使用韩语,亲切自然"),
# ── marketing_purpose:营销目的(前端枚举 + 常见关键词,20项)─────────
(
"marketing_purpose",
"获客引流",
"【口播获客】第一人称对镜头发声,突出到店/加粉福利和行动引导,节奏紧凑有钩子,结尾明确CTA",
),
(
"marketing_purpose",
"账号涨粉",
"【涨粉内容】开头有强反差/悬念钩子抓注意力,内容有信息密度和共鸣点,结尾引导关注不生硬",
),
(
"marketing_purpose",
"活动通知",
"【活动通知】开头直接说活动主题和时间,中段讲福利/优惠力度,结尾强调截止时间和参与方式,信息清晰不绕弯",
),
(
"marketing_purpose",
"口播带货",
"【口播带货】第一人称对镜头说话,像真人出镜推荐好物,有真实使用体验感,突出卖点和使用效果,结尾有购买引导",
),
(
"marketing_purpose",
"促销转化",
"【促销转化】强调限时/限量/专属优惠,制造紧迫感,价格/优惠信息清晰突出,有明确购买路径",
),
(
"marketing_purpose",
"场景种草",
"【场景种草】在真实使用场景中展示产品/服务,自然带出卖点,让用户产生代入感和'我也想试试'的欲望",
),
(
"marketing_purpose",
"痛点解决",
"【痛点解决】开头直击目标用户的具体痛点场景,中段给出解决方案和使用演示,结尾强调效果对比",
),
(
"marketing_purpose",
"功能演示",
"【功能演示】以产品功能展示为主,操作步骤清晰可见,讲清楚'怎么用'和'用了有什么效果',少废话多演示",
),
(
"marketing_purpose",
"门店发现",
"【门店探店】店主或探店博主出镜,真实展示门店环境、招牌产品/服务、价格和体验感,结尾有到店引导",
),
(
"marketing_purpose",
"到店实录",
"【到店实录】真实记录到店体验全过程,镜头跟着主走进店、看环境、体验服务、尝产品,节奏自然真实",
),
(
"marketing_purpose",
"招牌体验",
"【招牌体验】聚焦门店/品牌的招牌产品/服务,细节展示特色卖点,突出'为什么这是招牌',有记忆点",
),
(
"marketing_purpose",
"同城团购",
"【同城团购】突出本地/同城/到店属性,讲清楚团购优惠内容、使用规则、门店位置,结尾引导下单",
),
(
"marketing_purpose",
"品牌主张",
"【品牌主张】传递品牌理念和价值观,有态度有情感共鸣,画面质感高级,少硬广多故事感",
),
(
"marketing_purpose",
"品牌故事",
"【品牌故事】用叙事方式讲品牌的创立初心/工艺传承/用户故事,有情感起伏,建立品牌信任",
),
(
"marketing_purpose",
"生活方式",
"【生活方式】营造向往的生活场景和氛围,产品/服务自然融入生活片段,调性好看有质感,引发'想过这样的生活'的共鸣",
),
(
"marketing_purpose",
"创意概念",
"【创意概念】画面有创意和视觉冲击力,概念新颖不套路,适合做品牌传播内容,让人愿意转发分享",
),
(
"marketing_purpose",
"对话短剧",
"【短剧模式】必须双/多角色对话格式,台词用'角色:xxx'格式,镜头在角色间切换,有剧情冲突或反转,禁止第三人称旁白解说",
),
(
"marketing_purpose",
"反转短剧",
"【反转短剧】前半段建立常规预期,后半段出人意料反转,对话推动剧情,节奏紧凑有爽感,结尾反转干脆利落",
),
(
"marketing_purpose",
"悬念短剧",
"【悬念短剧】开头制造悬念或谜题,剧情层层推进吊足胃口,结尾揭晓答案,角色对话推进,不要旁白解说",
),
(
"marketing_purpose",
"情绪短片",
"【情绪短片】用画面、音乐和简短台词传递情绪(治愈/感动/激励/共鸣),节奏偏慢有氛围感,文字克制留白",
),
# ── target_customer:目标客群(前端枚举 8项)─────────────────────
(
"target_customer",
"小镇青年",
"【目标小镇青年】台词接地气有网感,贴近下沉市场生活场景,讲实在优惠和真实体验,节奏轻快不端着",
),
(
"target_customer",
"小镇中老年",
"【目标小镇中老年】台词慢节奏、吐字清晰、信息直白,强调实惠耐用和健康养生,少用网络梗",
),
(
"target_customer",
"Z世代",
"【目标Z世代】台词活泼有梗、节奏快,可用流行语和弹幕感表达,突出个性、有趣、社交话题性",
),
(
"target_customer",
"精致妈妈",
"【目标精致妈妈】台词温柔贴心,从育儿/家庭/品质生活角度切入,强调安全、省心、性价比,有真实带娃/家居场景共鸣",
),
(
"target_customer",
"新锐白领",
"【目标新锐白领】台词简洁干练,突出效率、品质、格调,讲职场/都市生活场景,少废话直接说价值",
),
(
"target_customer",
"资深中产",
"【目标资深中产】台词专业有深度,强调品质、品位、身份感,调性高级克制,不浮夸不喊麦",
),
(
"target_customer",
"都市蓝领",
"【目标都市蓝领】台词实在接地气,强调性价比、耐用、实惠,讲打工/租房/日常刚需场景,贴近生活",
),
(
"target_customer",
"都市银发",
"【目标都市银发】台词清晰慢节奏,强调健康、实惠、方便操作,避免复杂术语和网络用语,信息直白",
),
]
def _get_engine():
"""获取 DB engine。优先 DATABASE_URL 环境变量;否则走应用配置(与 seed_viral_video_prompts.py 一致)。"""
import os
database_url = os.environ.get("DATABASE_URL")
if database_url:
url = database_url
else:
from packages.config import get_shared_settings
url = str(get_shared_settings().database_url)
# asyncpg → psycopg2(脚本里不能用 async driver)
url = url.replace("postgresql+asyncpg://", "postgresql+psycopg2://")
if url.startswith("postgresql://") and "psycopg" not in url:
url = url.replace("postgresql://", "postgresql+psycopg2://", 1)
return create_engine(url, pool_pre_ping=True)
def main() -> None:
engine = _get_engine()
insert_sql = text("""
INSERT INTO viral_video_param_guides (guide_type, guide_key, guide_text)
VALUES (:gt, :gk, :gtxt)
ON CONFLICT (guide_type, guide_key)
DO UPDATE SET guide_text = EXCLUDED.guide_text, updated_at = NOW()
""")
count = 0
with engine.begin() as conn:
for gt, gk, gtxt in SEED_DATA:
conn.execute(insert_sql, {"gt": gt, "gk": gk, "gtxt": gtxt})
count += 1
print(f"[seed_viral_video_param_guides] upserted {count} rows")
if __name__ == "__main__":
main()
+293
View File
@@ -449,3 +449,296 @@ class TestRenderSmartCoverRoute:
assert result.cover_url == ""
# 失败时不写 cover_config / 不 commit
mock_db.commit.assert_not_called()
class TestFinalizeRoute:
"""POST /renders/{job_id}/finalize — 封面选好后正式入库成片库."""
def _make_existing_video(self, video_id="video-1", cover_url="https://oss/existing-cover.jpg"):
v = MagicMock()
v.id = video_id
v.thumbnail_url = cover_url
return v
def _make_finalized_video(self, video_id="video-new", thumbnail_url="https://oss/final-cover.jpg"):
v = MagicMock()
v.id = video_id
v.thumbnail_url = thumbnail_url
return v
def test_finalize_job_not_found_returns_404(self):
"""任务不存在 → 404."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
from fastapi import HTTPException
mock_service = MagicMock()
mock_service.get_render_job.return_value = None
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest()
with patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service):
with pytest.raises(HTTPException) as exc_info:
finalize_render_job(job_id="render-missing", body=body, current_user=mock_user, db=mock_db)
assert exc_info.value.status_code == 404
assert "不存在" in exc_info.value.detail
def test_finalize_job_not_completed_returns_400(self):
"""任务未 completed(如 processing)→ 400."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
from fastapi import HTTPException
mock_service = MagicMock()
mock_job = _make_mock_render_job(status="processing", output_video_url="https://oss/v.mp4")
mock_service.get_render_job.return_value = mock_job
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest()
with patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service):
with pytest.raises(HTTPException) as exc_info:
finalize_render_job(job_id="render-1", body=body, current_user=mock_user, db=mock_db)
assert exc_info.value.status_code == 400
assert "先完成视频生成" in exc_info.value.detail
def test_finalize_empty_video_url_returns_400(self):
"""已 completed 但 output_video_url 为空 → 400."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
from fastapi import HTTPException
mock_service = MagicMock()
mock_job = _make_mock_render_job(status="completed", output_video_url=" ")
mock_service.get_render_job.return_value = mock_job
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest()
with patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service):
with pytest.raises(HTTPException) as exc_info:
finalize_render_job(job_id="render-1", body=body, current_user=mock_user, db=mock_db)
assert exc_info.value.status_code == 400
assert "URL 为空" in exc_info.value.detail
def test_finalize_idempotent_returns_existing(self):
"""已 finalize 过 → 幂等返回 existing 记录,不重复入库."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
mock_service = MagicMock()
mock_job = _make_mock_render_job(status="completed", output_video_url="https://oss/v.mp4")
mock_service.get_render_job.return_value = mock_job
existing = self._make_existing_video()
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = existing
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest()
with patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service):
result = finalize_render_job(job_id="render-1", body=body, current_user=mock_user, db=mock_db)
assert result.status == "already_finalized"
assert result.video_id == "video-1"
assert result.cover_url == "https://oss/existing-cover.jpg"
mock_service.finalize_job.assert_not_called()
def test_finalize_empty_body_uses_smart_cover_url(self):
"""空 body 且 job.output_cover_url 已由 smart-cover 写入 → 直接用,不抽帧."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
mock_service = MagicMock()
mock_job = _make_mock_render_job(
status="completed",
output_video_url="https://oss/v.mp4",
output_cover_url="https://oss/smart-cover.jpg",
)
mock_service.get_render_job.return_value = mock_job
finalized = self._make_finalized_video(thumbnail_url="https://oss/smart-cover.jpg")
mock_service.finalize_job.return_value = finalized
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest()
with (
patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service),
patch("app.api.routes.ai_avatar_render.generate_smart_cover") as mock_gen,
):
result = finalize_render_job(job_id="render-1", body=body, current_user=mock_user, db=mock_db)
mock_gen.assert_not_called()
mock_service.finalize_job.assert_called_once()
call_kwargs = mock_service.finalize_job.call_args.kwargs
assert call_kwargs["cover_url"] == "https://oss/smart-cover.jpg"
assert call_kwargs.get("video_title") is None
assert result.status == "success"
assert result.cover_url == "https://oss/smart-cover.jpg"
def test_finalize_body_cover_url_takes_priority(self):
"""body.cover_url 优先于 job.output_cover_url."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
mock_service = MagicMock()
mock_job = _make_mock_render_job(
status="completed",
output_video_url="https://oss/v.mp4",
output_cover_url="https://oss/smart-cover.jpg",
)
mock_service.get_render_job.return_value = mock_job
finalized = self._make_finalized_video(thumbnail_url="https://oss/custom-cover.jpg")
mock_service.finalize_job.return_value = finalized
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest(cover_url="https://oss/custom-cover.jpg")
with (
patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service),
patch("app.api.routes.ai_avatar_render.generate_smart_cover") as mock_gen,
):
result = finalize_render_job(job_id="render-1", body=body, current_user=mock_user, db=mock_db)
mock_gen.assert_not_called()
call_kwargs = mock_service.finalize_job.call_args.kwargs
assert call_kwargs["cover_url"] == "https://oss/custom-cover.jpg"
assert result.cover_url == "https://oss/custom-cover.jpg"
def test_finalize_blob_url_is_ignored(self):
"""body 传 blob:/data: 等非 HTTP URL → 忽略,走已有 cover/兜底."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
mock_service = MagicMock()
mock_job = _make_mock_render_job(
status="completed",
output_video_url="https://oss/v.mp4",
output_cover_url="https://oss/smart-cover.jpg",
)
mock_service.get_render_job.return_value = mock_job
finalized = self._make_finalized_video(thumbnail_url="https://oss/smart-cover.jpg")
mock_service.finalize_job.return_value = finalized
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest(cover_url="blob:http://localhost/xxx")
with (
patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service),
patch("app.api.routes.ai_avatar_render.generate_smart_cover") as mock_gen,
):
result = finalize_render_job(job_id="render-1", body=body, current_user=mock_user, db=mock_db)
mock_gen.assert_not_called()
call_kwargs = mock_service.finalize_job.call_args.kwargs
# blob URL 被忽略,fallback 到 job.output_cover_url
assert call_kwargs["cover_url"] == "https://oss/smart-cover.jpg"
assert result.cover_url == "https://oss/smart-cover.jpg"
def test_finalize_no_cover_fallback_to_smart_cover(self):
"""无 body.cover_url 且 job.output_cover_url 为空 → 兜底自动抽帧."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
mock_service = MagicMock()
mock_job = _make_mock_render_job(
status="completed",
output_video_url="https://oss/v.mp4",
output_cover_url="",
)
mock_job.cover_config = {}
mock_service.get_render_job.return_value = mock_job
finalized = self._make_finalized_video(thumbnail_url="https://oss/fallback-cover.jpg")
mock_service.finalize_job.return_value = finalized
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest()
with (
patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service),
patch(
"app.api.routes.ai_avatar_render.generate_smart_cover",
return_value="https://oss/fallback-cover.jpg",
) as mock_gen,
):
result = finalize_render_job(job_id="render-1", body=body, current_user=mock_user, db=mock_db)
mock_gen.assert_called_once_with("https://oss/v.mp4", job_id="render-1", max_frames=5)
call_kwargs = mock_service.finalize_job.call_args.kwargs
assert call_kwargs["cover_url"] == "https://oss/fallback-cover.jpg"
# 兜底成功应回写 job.cover_config 和 output_cover_url
assert mock_job.output_cover_url == "https://oss/fallback-cover.jpg"
assert mock_job.cover_config["mode"] == "auto_frame"
assert mock_job.cover_config["url"] == "https://oss/fallback-cover.jpg"
assert mock_db.commit.called
assert result.cover_url == "https://oss/fallback-cover.jpg"
def test_finalize_video_title_override(self):
"""body.video_title 覆盖默认 AI数字人_xxx."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
mock_service = MagicMock()
mock_job = _make_mock_render_job(
status="completed",
output_video_url="https://oss/v.mp4",
output_cover_url="https://oss/c.jpg",
)
mock_service.get_render_job.return_value = mock_job
finalized = self._make_finalized_video()
mock_service.finalize_job.return_value = finalized
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest(video_title="我和我的养生馆")
with (
patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service),
patch("app.api.routes.ai_avatar_render.generate_smart_cover") as mock_gen,
):
result = finalize_render_job(job_id="render-1", body=body, current_user=mock_user, db=mock_db)
mock_gen.assert_not_called()
call_kwargs = mock_service.finalize_job.call_args.kwargs
assert call_kwargs["video_title"] == "我和我的养生馆"
assert result.status == "success"
def test_finalize_service_error_mapped_to_http(self):
"""service 抛 AiAvatarRenderError → 按 code 映射 HTTP 状态码."""
from app.api.routes.ai_avatar_render import finalize_render_job
from app.schemas.ai_avatar_render import FinalizeRenderRequest
from app.services.ai_avatar_render_service import AiAvatarRenderError
from fastapi import HTTPException
mock_service = MagicMock()
mock_job = _make_mock_render_job(
status="completed",
output_video_url="https://oss/v.mp4",
output_cover_url="https://oss/c.jpg",
)
mock_service.get_render_job.return_value = mock_job
mock_service.finalize_job.side_effect = AiAvatarRenderError("成片视频不存在", code="OutputVideoMissing")
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
mock_user = _make_mock_auth_user()
body = FinalizeRenderRequest()
with (
patch("app.services.ai_avatar_render_service.AiAvatarRenderService", return_value=mock_service),
patch("app.api.routes.ai_avatar_render.generate_smart_cover") as mock_gen,
):
with pytest.raises(HTTPException) as exc_info:
finalize_render_job(job_id="render-1", body=body, current_user=mock_user, db=mock_db)
assert exc_info.value.status_code == 400
mock_gen.assert_not_called()
+112
View File
@@ -8,8 +8,11 @@ from unittest.mock import MagicMock, patch
import pytest
from packages.application.ditto_emotion_service import (
EMO_ANGER,
EMO_HAPPY,
EMO_NEUTRAL,
EMO_SAD,
EMO_SURPRISE,
DittoEmotionService,
EmotionSegment,
_parse_emotion_json,
@@ -362,3 +365,112 @@ class TestWarnIfSuspiciousAllNeutral:
with caplog.at_level("WARNING", logger="packages.application.ditto_emotion_service"):
_warn_if_suspicious_all_neutral("家人们!", [])
assert not caplog.records
# ── build_timeline 全中性兜底置空(GPU端表情公式配套修复1)─────────
class TestBuildTimelineAllNeutralFallback:
"""LLM 兜底特征(全 emo=4 且 intensity<=0.1)→ 不传 emo_timeline,交 GPU 端自行处理."""
def test_llm_failure_fallback_returns_empty(self):
"""LLM 调用失败 → _cached_analyze 兜底全中性0.1 → build_timeline 置空."""
import packages.application.ditto_emotion_service as m
svc = _make_service(enabled=True)
m._cached_analyze.cache_clear()
try:
with patch.object(svc, "_call_llm", return_value=[]):
result = svc.build_timeline("全兜底置空检测专用独一无二文案甲。全兜底置空检测专用文案乙。", 5.0)
assert result == ""
finally:
m._cached_analyze.cache_clear()
def test_all_neutral_low_intensity_returns_empty(self):
"""LLM 返回全 emo=4 且 intensity<=0.1(兜底特征)→ 置空."""
import packages.application.ditto_emotion_service as m
svc = _make_service(enabled=True)
m._cached_analyze.cache_clear()
fake_reply = json.dumps(
[
{"text": "低强度中性检测专用文案甲", "emo": 4, "intensity": 0.1},
{"text": "低强度中性检测专用文案乙", "emo": 4, "intensity": 0.05},
]
)
try:
with patch.object(svc, "_call_llm", return_value=_parse_emotion_json(fake_reply)):
assert svc.build_timeline("低强度中性检测专用文案甲。低强度中性检测专用文案乙。", 5.0) == ""
finally:
m._cached_analyze.cache_clear()
def test_all_neutral_but_higher_intensity_kept(self):
"""全中性但 intensity>0.1(LLM 真实输出特征)→ 正常生成 timeline,不误伤."""
import packages.application.ditto_emotion_service as m
svc = _make_service(enabled=True)
m._cached_analyze.cache_clear()
fake_reply = json.dumps(
[
{"text": "高强度中性保留检测专用文案甲", "emo": 4, "intensity": 0.3},
{"text": "高强度中性保留检测专用文案乙", "emo": 4, "intensity": 0.2},
]
)
try:
with patch.object(svc, "_call_llm", return_value=_parse_emotion_json(fake_reply)):
result = svc.build_timeline("高强度中性保留检测专用文案甲。高强度中性保留检测专用文案乙。", 4.0)
data = json.loads(result)
assert len(data) == 2
assert all(item["emo"] == 4 for item in data)
finally:
m._cached_analyze.cache_clear()
# ── timeline entry 强度保底 0.35(GPU端表情公式配套修复3)─────────
class TestTimelineIntensityFloor:
def test_low_intensity_floored(self):
segs = [EmotionSegment("ab", EMO_HAPPY, 0.2)]
entries = align_timeline_by_length(segs, 2.0)
assert entries[0].intensity == 0.35
def test_floor_boundary_values(self):
segs = [
EmotionSegment("a", EMO_HAPPY, 0.1),
EmotionSegment("b", EMO_SAD, 0.34),
EmotionSegment("c", EMO_ANGER, 0.35),
]
entries = align_timeline_by_length(segs, 3.0)
assert entries[0].intensity == 0.35
assert entries[1].intensity == 0.35
assert entries[2].intensity == 0.35
def test_high_intensity_unchanged(self):
segs = [EmotionSegment("ab", EMO_HAPPY, 0.6)]
entries = align_timeline_by_length(segs, 2.0)
assert entries[0].intensity == 0.6
def test_floor_applies_in_timings_path(self):
segs = [EmotionSegment("hello", EMO_SAD, 0.15)]
timings = [{"start": 0.0, "end": 2.0}]
entries = align_timeline_by_timings(segs, timings, 2.0)
assert entries[0].intensity == 0.35
# ── timings 数量不匹配回退字数占比(GPU端表情公式配套修复2,固化行为)───
class TestAlignTimingsMismatchCharRatio:
def test_mismatch_falls_back_to_char_ratio_with_emo_mapping(self):
"""LLM 情绪分句(按逗号)与 TTS 分句(句末标点)数量不一致时,
必须回退到按情绪分句字数占比分配,保证 emo 与时间段一一对应."""
segs = [
EmotionSegment("a" * 6, EMO_HAPPY, 0.5),
EmotionSegment("b" * 2, EMO_SAD, 0.5),
EmotionSegment("c" * 2, EMO_SURPRISE, 0.5),
]
timings = [{"start": 0.0, "end": 10.0}] # len 1 != 3
entries = align_timeline_by_timings(segs, timings, 10.0)
assert len(entries) == 3
assert entries[0].start == 0.0
assert entries[0].end == 6.0
assert entries[1].start == 6.0
assert entries[1].end == 8.0
assert entries[2].start == 8.0
assert entries[2].end == 10.0
assert [e.emo for e in entries] == [EMO_HAPPY, EMO_SAD, EMO_SURPRISE]
@@ -535,7 +535,6 @@ class TestViralVideoResumeForRegenerate:
# 模拟已经生成过文案和视频
job.status = ViralVideoStatus.COPY_GENERATED
job.copy_result = {"shots": [{"x": 1}], "voiceover_script": "旧文案"}
job.intent_result = {"intent": "旧意图"}
job.storyboard = [{"x": 1}]
job.generated_copy_text = "旧文案"
job.result_video_url = "http://old.mp4"
@@ -549,7 +548,6 @@ class TestViralVideoResumeForRegenerate:
assert job.status == ViralVideoStatus.RUNNING
assert job.copy_result is None
assert job.intent_result is None
assert job.storyboard is None
assert job.generated_copy_text == ""
assert job.result_video_url == ""
@@ -564,14 +562,12 @@ class TestViralVideoResumeForRegenerate:
job = ViralVideoJob(user_id="u1", images=["img1"])
job.status = ViralVideoStatus.COMPLETED
job.copy_result = {"shots": [], "voiceover_script": "xx"}
job.intent_result = {"intent": "x"}
job.result_video_url = "http://v.mp4"
job.resume_from_image_analyzed()
assert job.status == ViralVideoStatus.RUNNING
assert job.copy_result is None
assert job.intent_result is None
assert job.result_video_url == ""
def test_first_call_from_image_analyzed_keeps_fields(self):
@@ -588,14 +584,3 @@ class TestViralVideoResumeForRegenerate:
assert job.status == ViralVideoStatus.RUNNING
assert job.image_analysis == {"products": []}
assert job.industry == "美妆"
def test_wait_user_confirm_rejected(self):
"""wait_user_confirm 中间状态应被拒绝(前端正在编辑/确认文案)。"""
import pytest
from packages.domain.viral_video import ViralVideoJob, ViralVideoStatus
job = ViralVideoJob(user_id="u1", images=["img1"])
job.status = ViralVideoStatus.WAIT_USER_CONFIRM
with pytest.raises(ValueError, match="Cannot resume"):
job.resume_from_image_analyzed()
+81
View File
@@ -486,6 +486,87 @@ class TestLipsyncServiceUnit:
assert call_kwargs.kwargs["audio_url"] == "https://example.com/direct-audio.mp3"
assert job.audio_url == "https://example.com/direct-audio.mp3"
def test_create_job_presynth_backfills_script_text(self, mock_mediakit, mock_cosyvoice):
"""预合成模式 script_text 为空时,从 sentence_timings 拼接兜底.
背景:前端 #1875 起预合成 payload 不带 script_text,导致 job.script_text
落库为空,Ditto 任务 script_len=0、情绪分析被跳过。
"""
from app.services.lipsync_service import LipsyncService
mock_db = MagicMock()
svc = LipsyncService(
mock_db,
client=mock_mediakit,
cosyvoice_service=mock_cosyvoice,
voice_clone_repo=MagicMock(),
)
svc.settings.use_gpu_lipsync = False
timings = [
{"index": 0, "text": "大家好,", "start_time": 0.0, "end_time": 1.0},
{"index": 1, "text": "欢迎来到直播间。", "start_time": 1.0, "end_time": 2.5},
]
job = svc.create_job(
user_id="user-1",
video_url="https://example.com/video.mp4",
audio_url="https://example.com/audio.mp3",
audio_duration=2.5,
sentence_timings=timings,
script_text="", # 前端预合成 payload 不带文案
)
assert job.script_text == "大家好,欢迎来到直播间。"
def test_create_job_presynth_keeps_supplied_script_text(self, mock_mediakit, mock_cosyvoice):
"""预合成模式显式传了 script_text 时,保留原文不做拼接."""
from app.services.lipsync_service import LipsyncService
mock_db = MagicMock()
svc = LipsyncService(
mock_db,
client=mock_mediakit,
cosyvoice_service=mock_cosyvoice,
voice_clone_repo=MagicMock(),
)
svc.settings.use_gpu_lipsync = False
timings = [
{"index": 0, "text": "分句一", "start_time": 0.0, "end_time": 1.0},
]
job = svc.create_job(
user_id="user-1",
video_url="https://example.com/video.mp4",
audio_url="https://example.com/audio.mp3",
audio_duration=1.0,
sentence_timings=timings,
script_text="原始文案,含标点。",
)
assert job.script_text == "原始文案,含标点。"
def test_create_job_presynth_empty_timings_falls_back_to_direct(self, mock_mediakit, mock_cosyvoice):
"""sentence_timings 空列表时 is_pre_synth=False,退化为直接音频模式(语义固化)."""
from app.services.lipsync_service import LipsyncService
mock_db = MagicMock()
svc = LipsyncService(
mock_db,
client=mock_mediakit,
cosyvoice_service=mock_cosyvoice,
voice_clone_repo=MagicMock(),
)
svc.settings.use_gpu_lipsync = False
job = svc.create_job(
user_id="user-1",
video_url="https://example.com/video.mp4",
audio_url="https://example.com/audio.mp3",
sentence_timings=[],
script_text="",
)
# 直接音频模式:无 timings 可拼,script_text 保持空(不报错)
assert job.script_text == ""
mock_mediakit.submit_lipsync.assert_called_once()
class TestErrorHandling:
"""v3: 音色解析与错误码在 service 层处理,路由层做 HTTP 状态码映射."""
@@ -0,0 +1,165 @@
"""#2308 parser字段映射单测:
1. 新schema XML(<shot>+<visual>+<action_details>)字段正确映射
2. 旧schema XML(<visual>混写)能拆分出cam/visual/action
3. markdown中文分镜字段正确提取
"""
from __future__ import annotations
import sys
from unittest.mock import MagicMock
import pytest
@pytest.fixture
def mock_job():
job = MagicMock()
job.duration = 15
job.video_ratio = "9:16"
job.marketing_purpose = "品牌推广"
job.image_analysis = {"images": []}
job.images = []
job.user_copy_text = ""
job.fusion_level = "ai_polish"
return job
def _import_wv(monkeypatch):
for mod in ["worker_app.celery_app", "worker_app.db"]:
monkeypatch.setitem(sys.modules, mod, MagicMock())
import celery
monkeypatch.setattr(celery, "shared_task", lambda **kw: (lambda f: f))
from worker_app.tasks import viral_video as wv
return wv
# === 1. 新schema(v3.1+):独立 <shot>/<visual>/<action_details> ===
NEW_SCHEMA_XML = """<script>
<copy_display_markdown><![CDATA[来御众堂放松一下]]></copy_display_markdown>
<clips>
<clip index="1">
<time_range>0-4秒</time_range>
<voiceover>生完娃,是不是连照镜子都没勇气了?</voiceover>
<shot>中近景平视,50mm f/2.0,缓慢推镜</shot>
<visual>暖光浴室镜前,妈妈略显疲惫地看向镜中自己,镜灯打在侧脸</visual>
<action_details>妈妈手扶洗手台,微微低头再抬眼看镜,眼眶微红</action_details>
<reference_image_index>0</reference_image_index>
</clip>
<clip index="2">
<time_range>4-10秒</time_range>
<voiceover>推开门,暖光和一杯热茶先接住了你。</voiceover>
<shot>中景平视,35mm f/2.8,缓慢平移</shot>
<visual>门店前台暖光木色装修,茶水冒热气,背景柔和虚化</visual>
<action_details>店员微笑双手递上茶杯,妈妈伸手接过,指尖轻触杯沿</action_details>
<reference_image_index>1</reference_image_index>
</clip>
</clips>
<voiceover_script>生完娃,是不是连照镜子都没勇气了?推开门,暖光和一杯热茶先接住了你。</voiceover_script>
<theme>产后妈妈走进御众堂</theme>
</script>"""
def test_new_schema_xml_field_mapping(monkeypatch, mock_job):
wv = _import_wv(monkeypatch)
r = wv._parse_script(NEW_SCHEMA_XML, mock_job)
assert r is not None
assert len(r["shots"]) == 2
s0 = r["shots"][0]
# shot_type 应是纯摄影参数
assert "缓慢推镜" in s0["shot_type_angle_movement"]
assert "妈妈" not in s0["shot_type_angle_movement"]
# scene_and_dialogue 只放对白
assert s0["scene_and_dialogue"] == "生完娃,是不是连照镜子都没勇气了?"
# action_details 有内容
assert "手扶洗手台" in s0["action_details"]
# visual 字段有画面
assert "暖光" in s0["visual"]
assert "推镜" not in s0["visual"]
# === 2. 旧schema(无<shot>/<action_details>标签,<visual>混写):兼容拆分 ===
OLD_SCHEMA_XML = """<script>
<clips>
<clip index="1">
<time_range>0-4秒</time_range>
<voiceover>生完娃,是不是连照镜子都没勇气了?</voiceover>
<visual>中近景平视,暖光下妈妈略显疲惫地看向镜中,镜头缓缓推近,妈妈手扶洗手台微微低头</visual>
<reference_image_index>0</reference_image_index>
</clip>
</clips>
<voiceover_script>生完娃,是不是连照镜子都没勇气了?</voiceover_script>
<theme>测试</theme>
</script>"""
def test_old_schema_xml_splits_visual(monkeypatch, mock_job):
wv = _import_wv(monkeypatch)
r = wv._parse_script(OLD_SCHEMA_XML, mock_job)
assert r is not None
s0 = r["shots"][0]
# 摄影参数段应含景别/运镜关键词
assert "中近景" in s0["shot_type_angle_movement"] or "推" in s0["shot_type_angle_movement"]
# action_details 应含动作动词
assert s0["action_details"], "action_details不应为空(旧schema兼容应从<visual>拆出动作)"
assert "手扶" in s0["action_details"] or "低头" in s0["action_details"]
# scene_and_dialogue 只放对白,不重复画面
assert s0["scene_and_dialogue"] == "生完娃,是不是连照镜子都没勇气了?"
# === 3. markdown 中文分镜字段 ===
MARKDOWN_INPUT = """## 视频主题:御众堂探店
### 镜头1
- 时间范围:0-4秒
- 景别/角度与运镜:中近景平视,缓慢推镜
- 画面:暖光下妈妈看向镜中,镜灯打在侧脸
- 动作与真人细节:妈妈手扶洗手台,微微低头
- 口播:"生完娃,是不是连照镜子都没勇气了?"
### 镜头2
- 时间范围:4-10秒
- 景别:中景平视
- 运镜:缓慢平移
- 画面:门店前台暖光木色装修
- 动作:店员递茶,妈妈伸手接过
- 口播:"推开门,暖光和一杯热茶先接住了你"
"""
def test_markdown_field_extraction(monkeypatch, mock_job):
wv = _import_wv(monkeypatch)
r = wv._parse_script(MARKDOWN_INPUT, mock_job)
assert r is not None
assert len(r["shots"]) == 2
s0 = r["shots"][0]
assert "中近景" in s0["shot_type_angle_movement"]
assert "妈妈手扶洗手台" in s0["action_details"]
assert s0["scene_and_dialogue"] == "生完娃,是不是连照镜子都没勇气了?"
# === 4. _split_visual_field 单元测试 ===
def test_split_visual_field(monkeypatch):
wv = _import_wv(monkeypatch)
# 典型旧schema混写:"中近景平视,暖光下一位妈妈略显疲惫地看向镜中,镜头缓缓推近,妈妈手扶洗手台微微低头"
cam, vis, act = wv._split_visual_field(
"中近景平视,暖光下妈妈略显疲惫地看向镜中,镜头缓缓推近,妈妈手扶洗手台微微低头"
)
assert cam and "中近景" in cam
# 动作/表情段应被拆到 act(含"妈妈"主语+动词)
assert act and ("手扶" in act or "疲惫" in act or "看向" in act)
# 画面描述允许为空(当动作段本身承载了主要画面信息时),或含"暖光"
assert (not vis) or ("暖光" in vis) or ("妈妈" in vis)
def test_split_visual_short(monkeypatch):
wv = _import_wv(monkeypatch)
cam, vis, act = wv._split_visual_field("特写固定镜头")
# 切不开时整体归camera/visual,action为空
assert "特写" in cam
if __name__ == "__main__":
pytest.main([__file__, "-v"])
+2 -67
View File
@@ -34,7 +34,6 @@ class TestViralVideoStatus:
def test_status_values(self):
assert ViralVideoStatus.PENDING == "pending"
assert ViralVideoStatus.RUNNING == "running"
assert ViralVideoStatus.WAIT_USER_CONFIRM == "wait_user_confirm"
assert ViralVideoStatus.COMPLETED == "completed"
assert ViralVideoStatus.FAILED == "failed"
assert ViralVideoStatus.CANCELLED == "cancelled"
@@ -66,28 +65,6 @@ class TestViralVideoJobStateTransitions:
with pytest.raises(ValueError, match="Cannot transition"):
job.mark_running()
def test_mark_wait_user_confirm(self):
job = ViralVideoJob(user_id="u1", status=ViralVideoStatus.RUNNING)
intent = {"intent": "推广", "key_messages": ["卖点1"]}
job.mark_wait_user_confirm(intent)
assert job.status == ViralVideoStatus.WAIT_USER_CONFIRM
assert job.intent_result == intent
def test_mark_wait_user_confirm_from_non_running_raises(self):
job = ViralVideoJob(user_id="u1", status=ViralVideoStatus.PENDING)
with pytest.raises(ValueError, match="Cannot transition"):
job.mark_wait_user_confirm({})
def test_resume_from_confirm(self):
job = ViralVideoJob(user_id="u1", status=ViralVideoStatus.WAIT_USER_CONFIRM)
job.resume_from_confirm()
assert job.status == ViralVideoStatus.RUNNING
def test_resume_from_non_confirm_raises(self):
job = ViralVideoJob(user_id="u1", status=ViralVideoStatus.RUNNING)
with pytest.raises(ValueError, match="Cannot resume"):
job.resume_from_confirm()
def test_mark_completed(self):
job = ViralVideoJob(user_id="u1", status=ViralVideoStatus.RUNNING)
job.mark_completed("https://oss.example.com/video.mp4")
@@ -140,7 +117,6 @@ class TestViralVideoStage:
expected_order = [
"image_analysis",
"video_analysis",
"intent_parsing",
"script_generation",
"review",
"tts",
@@ -157,46 +133,6 @@ class TestViralVideoStage:
class TestViralVideoSchemas:
"""Pydantic Schema 校验测试。"""
def test_create_request_valid(self):
from app.schemas.viral_video import CreateViralVideoRequest
req = CreateViralVideoRequest(images=["https://example.com/img.jpg"])
assert req.images == ["https://example.com/img.jpg"]
assert req.fusion_level == "ai_polish"
assert req.style_strength == "medium"
assert req.duration == 15
def test_create_request_empty_images_raises(self):
from app.schemas.viral_video import CreateViralVideoRequest
with pytest.raises(ValidationError):
CreateViralVideoRequest(images=[])
def test_create_request_invalid_fusion_level(self):
from app.schemas.viral_video import CreateViralVideoRequest
with pytest.raises(ValidationError):
CreateViralVideoRequest(
images=["https://example.com/img.jpg"],
fusion_level="invalid_level",
)
def test_create_request_invalid_style_strength(self):
from app.schemas.viral_video import CreateViralVideoRequest
with pytest.raises(ValidationError):
CreateViralVideoRequest(
images=["https://example.com/img.jpg"],
style_strength="ultra",
)
def test_confirm_intent_request_defaults(self):
from app.schemas.viral_video import ConfirmIntentRequest
req = ConfirmIntentRequest()
assert req.confirmed_copy == ""
assert req.adjustments == ""
def test_analyze_style_request(self):
from app.schemas.viral_video import AnalyzeStyleRequest
@@ -549,7 +485,7 @@ class TestPipelineIntegration:
):
"""v1.6: TTS整段合成 → 上传TTS到OSS → 单次 Seedance → 上传成片。"""
from apps.worker.worker_app.tasks.viral_video import (
resume_viral_video_pipeline,
run_viral_video_render,
)
job = ViralVideoJob(
@@ -557,7 +493,6 @@ class TestPipelineIntegration:
images=["https://img.com/1.jpg"],
industry="美妆",
status=ViralVideoStatus.RUNNING,
intent_result={"intent": "推广"},
duration=15,
video_ratio="9:16",
)
@@ -583,7 +518,7 @@ class TestPipelineIntegration:
mock_render.return_value = ("/tmp/video.mp4", {"completion_tokens": 1000000})
mock_upload.return_value = "https://oss.example.com/final.mp4"
result = resume_viral_video_pipeline.run("job-001")
result = run_viral_video_render.run("job-001")
assert result["ok"] is True
assert result["video_url"] == "https://oss.example.com/final.mp4"
+562
View File
@@ -0,0 +1,562 @@
"""双格式 parser + voiceover 字段 + markdown 兜底解析测试。
覆盖:
1. 双段式(中文 markdown + <script> XML 块)解析
2. 纯 XML 解析(已有测试覆盖,确认兼容)
3. 纯中文 markdown 解析(镜头1/镜头2/关键字段)
4. Shot voiceover 字段存在性 + _assemble_seedance_prompt 取值
5. 参数传递集成测试(marketing_purpose/target_audience/fusion_level)
6. markdown 解析失败返回 None
"""
from __future__ import annotations
import sys
from unittest.mock import MagicMock
import pytest
# ── 通用 fixtures ──────────────────────────────────────────────────────
@pytest.fixture
def mock_job():
"""构造一个最小可用的 ViralVideoJob mock。"""
job = MagicMock()
job.duration = 15
job.video_ratio = "9:16"
job.marketing_purpose = "品牌推广"
job.image_analysis = {"images": []}
job.images = []
job.user_copy_text = ""
job.fusion_level = "ai_polish"
return job
def _import_wv(monkeypatch):
"""导入 worker viral_video 模块(mock celery/db 依赖)。"""
for mod in ["worker_app.celery_app", "worker_app.db"]:
monkeypatch.setitem(sys.modules, mod, MagicMock())
import celery
monkeypatch.setattr(celery, "shared_task", lambda **kw: (lambda f: f))
from worker_app.tasks import viral_video as wv
return wv
# ════════════════════════════════════════════════════════════════════════
# 1. 双段式解析:中文 markdown + <script> XML 块
# ════════════════════════════════════════════════════════════════════════
class TestDualFormatParsing:
"""格式B:中文可读说明 + 末尾 <script>...</script> XML 块。"""
DUAL_FORMAT_INPUT = """## 视频总览
这是一个关于口红试色的短视频,目标受众是年轻女性。
### 逐秒镜头拆解
- 整体风格:暖色调、近景特写
- 节奏:前3秒给出钩子,中段展示产品,结尾引导行动
### 硬性约束
- 无字幕无水印
- 人物一致性
---
以下是符合规范的分镜脚本:
<script>
<copy_display_markdown><![CDATA[今天试一支超显白的口红,姐妹们看过来]]></copy_display_markdown>
<clips>
<clip index="1">
<time_range>0-5秒</time_range>
<voiceover>姐妹们,今天试一支超显白的色号</voiceover>
<visual>近景平视,暖光下手持口红缓慢展示</visual>
<reference_image_index>0</reference_image_index>
</clip>
<clip index="2">
<time_range>5-10秒</time_range>
<voiceover>你看这个颜色,涂上去整个手都亮了</voiceover>
<visual>特写,嘴唇涂抹口红过程</visual>
<reference_image_index>1</reference_image_index>
</clip>
<clip index="3">
<time_range>10-15秒</time_range>
<voiceover>真的超好看,推荐给你们</voiceover>
<visual>中景,微笑展示最终效果</visual>
<reference_image_index>0</reference_image_index>
</clip>
</clips>
<voiceover_script>姐妹们,今天试一支超显白的色号。你看这个颜色,涂上去整个手都亮了。真的超好看,推荐给你们。</voiceover_script>
<theme>口红试色分享</theme>
</script>"""
def test_dual_format_extracts_xml_block(self, monkeypatch, mock_job):
"""双段式输入应正确提取 <script> 块并按 XML 路径解析。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.DUAL_FORMAT_INPUT, mock_job)
assert result is not None
assert result["voiceover_script"]
assert "姐妹们" in result["voiceover_script"]
assert len(result["shots"]) == 3
assert result["title"] == "口红试色分享"
def test_dual_format_shots_have_voiceover(self, monkeypatch, mock_job):
"""双段式解析后每个 shot 都应有独立 voiceover 字段。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.DUAL_FORMAT_INPUT, mock_job)
assert result is not None
for shot in result["shots"]:
assert "voiceover" in shot
assert isinstance(shot["voiceover"], str)
assert "超显白" in result["shots"][0]["voiceover"]
assert "推荐" in result["shots"][2]["voiceover"]
# ════════════════════════════════════════════════════════════════════════
# 2. 纯 XML 解析(兼容性测试)
# ════════════════════════════════════════════════════════════════════════
class TestPureXmlParsing:
"""格式A:纯 XML,与原有 v3 格式完全一致。"""
PURE_XML = """<script>
<copy_display_markdown><![CDATA[分享一款口红]]></copy_display_markdown>
<clips>
<clip index="1">
<time_range>0-5秒</time_range>
<voiceover>大家好,今天分享一款口红</voiceover>
<visual>近景平视,缓慢推镜</visual>
<action_details>手持口红特写</action_details>
<audio_bgm>轻快流行BGM</audio_bgm>
<transition>硬切</transition>
<reference_image_index>0</reference_image_index>
</clip>
<clip index="2">
<time_range>5-10秒</time_range>
<voiceover>颜色特别好看</voiceover>
<visual>特写,固定镜头</visual>
<audio_bgm>轻快BGM继续</audio_bgm>
<transition>硬切</transition>
<reference_image_index>1</reference_image_index>
</clip>
<clip index="3">
<time_range>10-15秒</time_range>
<voiceover>很显白,推荐给大家</voiceover>
<visual>中景,微笑展示</visual>
<audio_bgm>轻快BGM结束</audio_bgm>
<transition>结束</transition>
<reference_image_index>0</reference_image_index>
</clip>
</clips>
<voiceover_script>大家好呀,今天来给大家分享一款超显白的口红。颜色特别好看很显气质,真心推荐给姐妹们</voiceover_script>
<theme>口红分享</theme>
</script>"""
def test_pure_xml_parses_correctly(self, monkeypatch, mock_job):
"""纯 XML 格式应能正确解析出所有字段。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.PURE_XML, mock_job)
assert result is not None
assert result["title"] == "口红分享"
assert len(result["shots"]) == 3
assert "口红" in result["voiceover_script"]
def test_pure_xml_shots_have_voiceover(self, monkeypatch, mock_job):
"""纯 XML 路径也应为每个 shot 生成 voiceover 字段。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.PURE_XML, mock_job)
assert result is not None
assert result["shots"][0]["voiceover"] == "大家好,今天分享一款口红"
assert result["shots"][1]["voiceover"] == "颜色特别好看"
assert result["shots"][2]["voiceover"] == "很显白,推荐给大家"
# ════════════════════════════════════════════════════════════════════════
# 3. 纯中文 markdown 解析(格式C 兜底)
# ════════════════════════════════════════════════════════════════════════
class TestPureMarkdownParsing:
"""格式C:纯中文结构化 markdown,无 <script> 标签。"""
MARKDOWN_INPUT = """## 视频主题:咖啡店探店
这是一段关于咖啡店探店的短视频分镜。
### 镜头1
- 时间范围:0-5秒
- 景别:近景平视
- 运镜:缓慢推镜
- 口播:"大家好,今天带你们来一家超有氛围的咖啡店"
- 画面:暖光木质装修的咖啡店门面,镜头缓缓推近
- 动作:推门进入
- 音效:轻爵士BGM
- 转场:硬切
- 参考图:1
### 镜头2
- 时间范围:5-10秒
- 景别:特写
- 运镜:固定镜头
- 口播:"你看这杯拿铁,拉花也太好看了吧"
- 画面:咖啡师制作拿铁特写,奶泡拉花清晰可见
- 动作:咖啡师拉花过程
- 音效:咖啡机声+轻爵士
- 转场:叠化
- 参考图:2
### 镜头3
- 时间范围:10-15秒
- 景别:中景
- 运镜:缓慢拉远
- 口播:"推荐给你们,周末来坐坐吧"
- 画面:店内全景,客人悠闲品咖啡,暖光氛围
- 动作:端起杯子品尝
- 音效:轻爵士BGM渐弱
- 转场:结束
- 参考@图片1"""
def test_markdown_parses_shots(self, monkeypatch, mock_job):
"""纯中文 markdown 应能解析出3个镜头。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.MARKDOWN_INPUT, mock_job)
assert result is not None
assert len(result["shots"]) == 3
def test_markdown_extracts_theme(self, monkeypatch, mock_job):
"""应从"视频主题"提取 theme。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.MARKDOWN_INPUT, mock_job)
assert result is not None
assert "咖啡店" in result["title"]
def test_markdown_extracts_voiceover(self, monkeypatch, mock_job):
"""每个镜头应提取口播到 voiceover 字段。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.MARKDOWN_INPUT, mock_job)
assert result is not None
assert "咖啡店" in result["shots"][0]["voiceover"]
assert "拿铁" in result["shots"][1]["voiceover"]
assert "推荐" in result["shots"][2]["voiceover"]
def test_markdown_extracts_time_range(self, monkeypatch, mock_job):
"""应正确提取各镜头的 time_range。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.MARKDOWN_INPUT, mock_job)
assert result is not None
assert result["shots"][0]["time_range"] == "0-5秒"
assert result["shots"][1]["time_range"] == "5-10秒"
assert result["shots"][2]["time_range"] == "10-15秒"
def test_markdown_extracts_audio_bgm(self, monkeypatch, mock_job):
"""应正确提取音效/BGM。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.MARKDOWN_INPUT, mock_job)
assert result is not None
assert "爵士" in result["shots"][0]["audio_bgm"] or "BGM" in result["shots"][0]["audio_bgm"]
def test_markdown_extracts_transition(self, monkeypatch, mock_job):
"""应正确提取转场。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.MARKDOWN_INPUT, mock_job)
assert result is not None
assert result["shots"][0]["transition"] == "硬切"
assert result["shots"][1]["transition"] == "叠化"
assert result["shots"][2]["transition"] == "结束"
def test_markdown_extracts_reference_image(self, monkeypatch, mock_job):
"""应正确提取参考图索引(从1开始转为0-based)。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.MARKDOWN_INPUT, mock_job)
assert result is not None
assert result["shots"][0]["reference_image_index"] == 0 # 参考图:1 -> 0
assert result["shots"][1]["reference_image_index"] == 1 # 参考图:2 -> 1
# 镜头3用"参考@图片1"格式 -> 0
assert result["shots"][2]["reference_image_index"] == 0
def test_markdown_voiceover_script_concatenated(self, monkeypatch, mock_job):
"""voiceover_script 应是各镜头口播的拼接。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(self.MARKDOWN_INPUT, mock_job)
assert result is not None
assert "咖啡店" in result["voiceover_script"]
assert "拿铁" in result["voiceover_script"]
assert "推荐" in result["voiceover_script"]
def test_markdown_chinese_number_shots(self, monkeypatch, mock_job):
"""支持中文数字镜头标识(镜头一/镜头二/...)。"""
wv = _import_wv(monkeypatch)
md_cn = """## 视频主题:测试
### 镜头一
- 时间范围:0-5秒
- 口播:"第一段口播内容很长足够通过校验"
- 画面:近景展示
### 镜头二
- 时间范围:5-10秒
- 口播:"第二段口播内容也要足够长才行"
- 画面:特写展示
### 镜头三
- 时间范围:10-15秒
- 口播:"第三段口播内容结尾"
- 画面:中景展示
"""
result = wv._parse_script(md_cn, mock_job)
assert result is not None
assert len(result["shots"]) == 3
# ════════════════════════════════════════════════════════════════════════
# 4. Shot voiceover 字段 + _assemble_seedance_prompt
# ════════════════════════════════════════════════════════════════════════
class TestShotVoiceoverField:
"""确认 parser 输出的每个 shot dict 都有独立 voiceover 字段。"""
def test_xml_path_shot_voiceover(self, monkeypatch, mock_job):
"""XML 路径解析后 shot 有 voiceover 字段。"""
wv = _import_wv(monkeypatch)
xml = """<script>
<clips>
<clip index="1">
<time_range>0-5秒</time_range>
<voiceover>第一句口播</voiceover>
<visual>近景展示</visual>
<reference_image_index>0</reference_image_index>
</clip>
<clip index="2">
<time_range>5-10秒</time_range>
<voiceover>第二句口播</voiceover>
<visual>特写展示</visual>
<reference_image_index>-1</reference_image_index>
</clip>
<clip index="3">
<time_range>10-15秒</time_range>
<voiceover>第三句口播</voiceover>
<visual>中景展示</visual>
<reference_image_index>0</reference_image_index>
</clip>
</clips>
<voiceover_script>第一句口播。第二句口播。第三句口播。</voiceover_script>
<theme>测试主题</theme>
</script>"""
result = wv._parse_script(xml, mock_job)
assert result is not None
for shot in result["shots"]:
assert "voiceover" in shot
assert result["shots"][0]["voiceover"] == "第一句口播"
assert result["shots"][1]["voiceover"] == "第二句口播"
assert result["shots"][2]["voiceover"] == "第三句口播"
def test_fallback_script_shot_voiceover(self, monkeypatch, mock_job):
"""_fallback_script 的 shot dict 也应有 voiceover 字段。"""
wv = _import_wv(monkeypatch)
fb = wv._fallback_script(mock_job)
assert fb["shots"]
for shot in fb["shots"]:
assert "voiceover" in shot
assert shot["voiceover"] == fb["voiceover_script"]
def test_assemble_seedance_uses_shot_voiceover(self, monkeypatch, mock_job):
"""_assemble_seedance_prompt 应能取到 shot 的 voiceover 字段。"""
wv = _import_wv(monkeypatch)
mock_job.video_model = "seedance-2.5"
cr = {
"overview": {"theme": "测试", "total_duration": 15, "aspect_ratio": "9:16"},
"scene_and_lighting": "明亮",
"shots": [
{
"time_range": "0-5秒",
"shot_type_angle_movement": "近景",
"scene_and_dialogue": "画面描述|测试口播",
"action_details": "展示",
"audio_bgm": "BGM",
"transition": "硬切",
"reference_image_index": 0,
"voiceover": "这是第一句口播",
},
{
"time_range": "5-10秒",
"shot_type_angle_movement": "特写",
"scene_and_dialogue": "画面描述",
"action_details": "",
"audio_bgm": "BGM",
"transition": "硬切",
"reference_image_index": 0,
"voiceover": "这是第二句口播",
},
{
"time_range": "10-15秒",
"shot_type_angle_movement": "中景",
"scene_and_dialogue": "画面描述",
"action_details": "",
"audio_bgm": "BGM",
"transition": "结束",
"reference_image_index": 0,
"voiceover": "这是第三句口播",
},
],
"hard_constraints": ["无字幕"],
"negative_prompts": ["水印"],
}
prompt = wv._assemble_seedance_prompt(cr, mock_job)
# Seedance 格式应包含口播内容
assert "第一句口播" in prompt
assert "第二句口播" in prompt
# ════════════════════════════════════════════════════════════════════════
# 5. 参数传递集成测试(3个核心参数)
# ════════════════════════════════════════════════════════════════════════
class TestParameterPassing:
"""验证核心用户参数从 job -> build_messages 的传递链路。"""
def test_marketing_purpose_in_messages(self, monkeypatch):
"""marketing_purpose='门店引流' 应出现在 build_messages 的 user 消息中。"""
from packages.application.viral_video.prompt_loader import build_messages, get_template, invalidate
invalidate()
template = get_template("storyboard")
assert template is not None
messages = build_messages(
template,
marketing_purpose="门店引流",
marketing_purpose_hint="【营销目的:门店引流】以到店为核心",
industry="美容",
image_summary="",
theme_hint="门店引流",
duration="15",
image_count="2",
aspect_ratio="9:16",
tone="亲切自然",
target_audience="25-35岁女性",
target_customer_hint="【目标人群】25-35岁女性",
persona_hint="【人设】亲切自然",
viral_structure_hint="【爆款结构】自由组织",
language_hint="【语言】中文",
extra_requirements="(AI 创作)",
video_style_section="",
duration_constraint_text="视频时长15秒。口播总字数严格控制在38~75字之间",
)
user_msg = messages[-1]["content"]
assert "门店引流" in user_msg
def test_target_audience_in_messages(self, monkeypatch):
"""target_audience='年轻妈妈' 应出现在 build_messages 的 user 消息中。"""
from packages.application.viral_video.prompt_loader import build_messages, get_template, invalidate
invalidate()
template = get_template("storyboard")
assert template is not None
messages = build_messages(
template,
marketing_purpose="品牌推广",
marketing_purpose_hint="【营销目的】品牌推广",
industry="母婴",
image_summary="",
theme_hint="母婴品牌",
duration="15",
image_count="1",
aspect_ratio="9:16",
tone="温柔亲切",
target_audience="年轻妈妈",
target_customer_hint="【目标人群】年轻妈妈群体",
persona_hint="【人设】温柔",
viral_structure_hint="【爆款结构】自由",
language_hint="【语言】中文",
extra_requirements="(AI 创作)",
video_style_section="",
duration_constraint_text="视频时长15秒。",
)
user_msg = messages[-1]["content"]
assert "年轻妈妈" in user_msg
def test_fusion_level_user_primary_in_messages(self, monkeypatch):
"""fusion_level=user_primary + user_copy_text 应生成'必须原封不动使用'字样。"""
from packages.application.viral_video.prompt_loader import build_messages, get_template, invalidate
invalidate()
template = get_template("storyboard")
assert template is not None
# 模拟 worker 里 fusion_level=user_primary 时构造的 extra_req
extra_req = (
"【用户指定文案,必须原封不动使用,一个字都不要改,"
"你只需要根据文案生成分镜脚本的镜头描述和画面,"
"copy_display_markdown里的文案必须与用户原文完全一致】:\n"
"我们的新店开业啦,全场八折优惠"
)
messages = build_messages(
template,
marketing_purpose="促销活动",
marketing_purpose_hint="【营销目的】促销",
industry="零售",
image_summary="",
theme_hint="新店开业",
duration="15",
image_count="0",
aspect_ratio="9:16",
tone="热情",
target_audience="周边居民",
target_customer_hint="【目标人群】周边居民",
persona_hint="【人设】热情",
viral_structure_hint="【爆款结构】自由",
language_hint="【语言】中文",
extra_requirements=extra_req,
video_style_section="",
duration_constraint_text="视频时长15秒。",
)
user_msg = messages[-1]["content"]
assert "必须原封不动使用" in user_msg
assert "新店开业" in user_msg
# ════════════════════════════════════════════════════════════════════════
# 6. markdown 解析失败返回 None
# ════════════════════════════════════════════════════════════════════════
class TestMarkdownParseFailures:
"""无法解析的输入应返回 None。"""
def test_plain_text_returns_none(self, monkeypatch, mock_job):
"""纯文本不含镜头标识,应返回 None。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script("这是一段普通文本,没有任何镜头标识。", mock_job)
assert result is None
def test_empty_string_returns_none(self, monkeypatch, mock_job):
"""空字符串应返回 None。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script("", mock_job)
assert result is None
def test_none_returns_none(self, monkeypatch, mock_job):
"""None 输入应返回 None。"""
wv = _import_wv(monkeypatch)
result = wv._parse_script(None, mock_job)
assert result is None
def test_markdown_without_voiceover_returns_none(self, monkeypatch, mock_job):
"""有镜头标识但没有口播内容,voiceover_script 为空,应返回 None。"""
wv = _import_wv(monkeypatch)
md = """### 镜头1
- 画面:近景展示产品
### 镜头2
- 画面:特写展示产品
### 镜头3
- 画面:中景展示产品
"""
result = wv._parse_script(md, mock_job)
assert result is None
+7 -21
View File
@@ -84,20 +84,6 @@ class TestScriptGenerationV16:
assert out["final_copy"] == out["voiceover_script"]
assert len(out["shots"]) >= 1
def test_safe_json_loads_parses_fenced_code(self):
from apps.worker.worker_app.tasks.viral_video import _safe_json_loads
fenced = '```json\n{"voiceover_script": "你好", "shots": []}\n```'
out = _safe_json_loads(fenced)
assert out is not None
assert out["voiceover_script"] == "你好"
def test_safe_json_loads_handles_none(self):
from apps.worker.worker_app.tasks.viral_video import _safe_json_loads
assert _safe_json_loads(None) is None
assert _safe_json_loads("not json") is None
def test_assemble_seedance_prompt_contains_sections(self, mock_job):
from apps.worker.worker_app.tasks.viral_video import _assemble_seedance_prompt
@@ -289,19 +275,19 @@ class TestDoubaoClientVideoGen:
# ── P0-3: resume 从 job 读 image_analysis ────────────────────────
class TestResumeReadsImageAnalysis:
def test_resume_uses_persisted_image_analysis(self):
"""resume/render pipeline 应从 job.image_analysis 读(v1.5 _run_render_pipeline 共享渲染逻辑)。"""
class TestRenderReadsImageAnalysis:
def test_render_uses_persisted_image_analysis(self):
"""render pipeline 应从 job.copy_result 读(_run_render_pipeline 共享渲染逻辑)。"""
import inspect
from apps.worker.worker_app.tasks import viral_video as vv
# v1.5 改造后 resume 委托给 _run_render_pipeline,那里读取 job.image_analysis
# render task 委托给 _run_render_pipeline,那里读取 job.copy_result
src = inspect.getsource(vv._run_render_pipeline)
assert "job.copy_result" in src
# resume 本身应该调用 _run_render_pipeline
resume_src = inspect.getsource(vv.resume_viral_video_pipeline)
assert "_run_render_pipeline" in resume_src
# run_viral_video_render 本身应该调用 _run_render_pipeline
render_src = inspect.getsource(vv.run_viral_video_render)
assert "_run_render_pipeline" in render_src
# ============ PR1: _replace_henjin_everywhere 已删除(错别字改为 prompt NEGATIVE_RULES 禁止) ============
+213
View File
@@ -0,0 +1,213 @@
"""PR3 P2 爆款视频后端:errors/stages/param_guides 基础单测。"""
from __future__ import annotations
def test_errors_hierarchy():
from packages.domain.viral_video import (
ViralVideoComplianceError,
ViralVideoConfigError,
ViralVideoCreditsError,
ViralVideoError,
ViralVideoLLMError,
ViralVideoResourceError,
ViralVideoTimeoutError,
ViralVideoUpstreamError,
)
# 基类
e = ViralVideoError()
assert e.code == "viral_video_error"
assert e.http_status == 500
assert str(e)
# 上游错误
ue = ViralVideoUpstreamError("doubao", upstream_code="50411", user_message="模型不可用", status_code=400)
assert isinstance(ue, ViralVideoError)
assert ue.provider == "doubao"
assert ue.upstream_code == "50411"
assert ue.http_status == 502
assert ue.status_code == 400
# 合规错误带 issues
ce = ViralVideoComplianceError(user_message="审核未通过", issues=[{"text": "违规", "severity": "error"}])
assert ce.code == "compliance_rejected"
assert ce.http_status == 400
assert len(ce.issues) == 1
# 资源/积分/超时/LLM/配置
assert ViralVideoResourceError("video_file", "x").http_status == 502
assert ViralVideoCreditsError().http_status == 402
te = ViralVideoTimeoutError("rendering", timeout_s=180)
assert te.stage == "rendering"
assert te.http_status == 504
le = ViralVideoLLMError(stage="script", model="seed-2.1-lite", detail="max retries")
assert le.http_status == 502
assert le.model == "seed-2.1-lite"
assert ViralVideoConfigError().http_status == 500
def test_stages_constants():
from packages.domain.viral_video import (
PROGRESS_COPY_DONE,
PROGRESS_COPY_START,
PROGRESS_COPY_STREAMING,
PROGRESS_DONE,
PROGRESS_IMAGE_DONE,
PROGRESS_REVIEW,
PROGRESS_TRUST,
PROGRESS_TTS,
PROGRESS_UPLOAD_DONE,
PROGRESS_VIDEO_DONE,
PROGRESS_VIDEO_START,
STAGE_COPY_GENERATION,
STAGE_IMAGE_ANALYSIS,
STAGE_PHASE_MESSAGES,
STAGE_REVIEW,
STAGE_TRUST_CHAIN,
STAGE_TTS,
STAGE_UPLOADING,
STAGE_VIDEO_GENERATION,
)
assert STAGE_IMAGE_ANALYSIS == "image_analysis"
assert STAGE_COPY_GENERATION == "script_generation"
assert STAGE_REVIEW == "review"
assert STAGE_TRUST_CHAIN == "trust_chain"
assert STAGE_VIDEO_GENERATION == "rendering"
assert STAGE_UPLOADING == "uploading"
# 进度单调递增
assert PROGRESS_IMAGE_DONE < PROGRESS_COPY_START < PROGRESS_COPY_STREAMING < PROGRESS_COPY_DONE
assert PROGRESS_COPY_DONE < PROGRESS_REVIEW < PROGRESS_TRUST < PROGRESS_TTS < PROGRESS_VIDEO_START
assert PROGRESS_VIDEO_START < PROGRESS_VIDEO_DONE < PROGRESS_UPLOAD_DONE <= PROGRESS_DONE
# 阶段中文提示
assert "分析" in STAGE_PHASE_MESSAGES[STAGE_IMAGE_ANALYSIS]
assert "审核" in STAGE_PHASE_MESSAGES[STAGE_REVIEW]
def test_param_guides_invalid_type_returns_none():
from packages.application.viral_video.param_guides import get_param_guide
assert get_param_guide("bogus", "x") is None
assert get_param_guide("persona", "") is None
assert get_param_guide("persona", " ") is None
def test_fallback_script_honest_short(monkeypatch):
"""兜底文案必须短、诚实、基于真实 VLM 输入。"""
import sys
from unittest.mock import MagicMock
# mock celery/db 依赖
for mod in ["worker_app.celery_app", "worker_app.db"]:
monkeypatch.setitem(sys.modules, mod, MagicMock())
import celery
monkeypatch.setattr(celery, "shared_task", lambda **kw: (lambda f: f))
from worker_app.tasks import viral_video as wv
from packages.domain.viral_video import ViralVideoJob
# 有 name+brand
j = ViralVideoJob(user_id="u", duration=15, video_ratio="9:16")
j.image_analysis = {"images": [{"name": "口红", "brand": "XX牌", "type": "product"}]}
fb = wv._fallback_script(j)
assert "XX牌" in fb["voiceover_script"]
assert "口红" in fb["voiceover_script"]
assert len(fb["voiceover_script"]) < 20 # 非常短,不再是营销套话
assert fb["shots"] and len(fb["shots"]) == 1 # 只有一个镜头
assert fb["shots"][0]["time_range"] == "0-15秒"
# 只有 name
j2 = ViralVideoJob(user_id="u", duration=10)
j2.image_analysis = {"images": [{"name": "面馆", "type": "store"}]}
fb2 = wv._fallback_script(j2)
assert "来看看面馆" in fb2["voiceover_script"]
assert "都很赞" not in fb2["voiceover_script"] # 不再有营销套话
# 都没有
j3 = ViralVideoJob(user_id="u", duration=15)
j3.image_analysis = {"images": []}
fb3 = wv._fallback_script(j3)
assert "好东西" in fb3["voiceover_script"]
# user_copy_text 优先
j4 = ViralVideoJob(user_id="u", duration=15, user_copy_text="自定义文案")
j4.image_analysis = {"images": [{"name": "X", "brand": "Y"}]}
fb4 = wv._fallback_script(j4)
assert fb4["voiceover_script"] == "自定义文案"
def test_video_analysis_returns_none_on_import_error(monkeypatch):
"""ImportError 时返回 None,下游 isinstance(dict) 会跳过。"""
import sys
from unittest.mock import MagicMock
for mod in ["worker_app.celery_app", "worker_app.db"]:
monkeypatch.setitem(sys.modules, mod, MagicMock())
import celery
monkeypatch.setattr(celery, "shared_task", lambda **kw: (lambda f: f))
# 让 import viral_video.video_analyzer 抛 ImportError
import builtins
from worker_app.tasks import viral_video as wv
from packages.domain.viral_video import ViralVideoJob
real_import = builtins.__import__
def fake_import(name, *a, **kw):
if "video_analyzer" in name:
raise ImportError("no module")
return real_import(name, *a, **kw)
monkeypatch.setattr(builtins, "__import__", fake_import)
j = ViralVideoJob(user_id="u", reference_video_url="https://x/y.mp4")
res = wv._step_video_analysis(j)
assert res is None
def test_seedance_prompt_uses_helpers(monkeypatch):
"""_assemble_seedance_prompt 拆分后输出包含必要段落,且长度合理。"""
import sys
from unittest.mock import MagicMock, patch
for mod in ["worker_app.celery_app", "worker_app.db"]:
monkeypatch.setitem(sys.modules, mod, MagicMock())
import celery
monkeypatch.setattr(celery, "shared_task", lambda **kw: (lambda f: f))
from worker_app.tasks import viral_video as wv
from packages.domain.viral_video import ViralVideoJob
j = ViralVideoJob(user_id="u", duration=15, video_ratio="9:16", images=["https://x/1.jpg"])
copy_result = {
"overview": {"theme": "口红试色", "total_duration": 15, "aspect_ratio": "9:16"},
"scene_and_lighting": "暖光,室内美妆场景",
"shots": [
{
"time_range": "0-15秒",
"shot_type_angle_movement": "中景",
"voiceover": "这支口红颜色超正",
"reference_image_index": 0,
"transition": "结束",
}
],
"hard_constraints": ["无水印"],
"negative_prompts": ["字幕"],
}
with patch("packages.domain.points_rules.get_viral_video_model_config", return_value={"provider": "doubao"}):
with patch.object(wv, "_shot_visual", return_value="女生涂口红特写"):
with patch.object(wv, "_parse_shot_seconds", return_value=(0, 15)):
p = wv._assemble_seedance_prompt(copy_result, j)
assert "【视频总览】" in p
assert "【分镜脚本】" in p
assert "【硬性约束】" in p
assert "【负面提示词】" in p
assert "@图片1" in p
assert "口红试色" in p
+2 -2
View File
@@ -130,7 +130,7 @@ class TestTemplates:
duration_constraint_text=build_duration_constraint_text(15),
)
assert "去油快" in rendered
assert "38~60字" in rendered
assert "38~75字" in rendered
# ── constants ─────────────────────────────────────────────────────────────
@@ -149,7 +149,7 @@ class TestConstants:
def test_duration_constraint_text_mentions_chars_and_shots(self):
text = build_duration_constraint_text(15)
assert "38" in text and "60" in text
assert "38" in text and "75" in text
assert "镜头" in text
def test_get_expected_shot_count(self):
+10 -75
View File
@@ -1,11 +1,12 @@
"""viral_video.py HTTP 端点单元测试(celery send_task 分支覆盖)。
直接调用路由函数(不启动 TestClient),通过 patch 注入 repo/session/user,
覆盖 4 个 celery_app.send_task(...) 调用点:
覆盖三步流程 celery_app.send_task(...) 调用点:
- create_viral_video (generate) -> worker.run_viral_video_pipeline
- retry_viral_video_job (retry) -> worker.run_viral_video_pipeline
- confirm_intent -> worker.resume_viral_video_pipeline
- analyze_images (analyze-images) -> worker.run_viral_video_analyze
- generate_copy (generate-copy) -> worker.run_viral_video_generate_copy
- confirm_copy (confirm-copy) -> worker.run_viral_video_render
- retry_viral_video_job (retry) -> worker.run_viral_video_analyze
- analyze_style -> worker.run_video_style_analysis
"""
@@ -44,6 +45,7 @@ def _make_job(job_id: str = "job-1", user_id: str = "u1", status: str = "pending
"persona_id": "",
"viral_structure": "",
"marketing_purpose": "",
"tone": "",
"bgm_preference": "",
"duration": 15,
"user_copy_text": "",
@@ -61,7 +63,6 @@ def _make_job(job_id: str = "job-1", user_id: str = "u1", status: str = "pending
"completed_at": None,
"stage": "",
"progress": 0.0,
"intent_result": None,
"image_analysis": None,
"storyboard": None,
"generated_copy_text": "",
@@ -86,42 +87,7 @@ def _make_job(job_id: str = "job-1", user_id: str = "u1", status: str = "pending
return job
# ── generate ────────────────────────────────────────────────────────────
class TestCreateViralVideo:
def _req(self, **kw):
from app.schemas.viral_video import CreateViralVideoRequest
d = {"images": ["https://x.com/a.jpg"], "industry": "电商", "target_customer": "年轻人"}
d.update(kw)
return CreateViralVideoRequest(**d)
def test_generate_dispatches_celery_task(self):
from app.api.routes import viral_video as vv_mod
req = self._req()
user = _auth_user("u1")
session = MagicMock()
saved_job = _make_job(job_id="job-new", user_id="u1", status="pending")
repo = MagicMock()
def fake_save(job):
job.id = saved_job.id
repo.save.side_effect = fake_save
with (
patch.object(vv_mod, "_get_job_repo", return_value=repo),
patch.object(vv_mod.celery_app, "send_task") as mock_send,
):
resp = vv_mod.create_viral_video(req, authenticated_user=user, session=session)
mock_send.assert_called_once_with("worker.run_viral_video_pipeline", args=[saved_job.id])
assert resp.id == saved_job.id
# ── retry ───────────────────────────────────────────────────────────────
# ── retry ──────────────────────────────────────────────────────────────
class TestRetryViralVideo:
@@ -339,37 +305,6 @@ class TestRetryViralVideo:
assert call_args.kwargs["amount"] == 7.0
# ── confirm-intent ──────────────────────────────────────────────────────
class TestConfirmIntent:
def test_confirm_intent_dispatches_resume_task(self):
from app.api.routes import viral_video as vv_mod
from app.schemas.viral_video import ConfirmIntentRequest
from packages.domain.viral_video import ViralVideoStatus
user = _auth_user("u1")
session = MagicMock()
job = _make_job(job_id="job-cfm", user_id="u1", status=ViralVideoStatus.WAIT_USER_CONFIRM)
repo = MagicMock()
repo.get.return_value = job
req = ConfirmIntentRequest(confirmed_copy="确认后的文案")
with (
patch.object(vv_mod, "_get_job_repo", return_value=repo),
patch.object(vv_mod.celery_app, "send_task") as mock_send,
):
resp = vv_mod.confirm_intent("job-cfm", req, authenticated_user=user, session=session)
assert job.user_copy_text == "确认后的文案"
job.resume_from_confirm.assert_called_once()
mock_send.assert_called_once_with("worker.resume_viral_video_pipeline", args=["job-cfm"])
assert resp.id == "job-cfm"
# ── analyze-style ───────────────────────────────────────────────────────
class TestAnalyzeStyle:
def test_analyze_style_dispatches_analysis_task(self):
@@ -474,7 +409,7 @@ class TestGenerateCopy:
assert resp.id == "job-gc"
def test_generate_copy_rejects_wrong_status(self):
"""wait_user_confirm 等中间状态不允许调用 generate-copy(状态保护)。"""
"""cancelled 等非预期状态不允许调用 generate-copy(状态保护)。"""
import pytest
from app.api.routes import viral_video as vv_mod
from app.schemas.viral_video import GenerateCopyRequest
@@ -484,8 +419,8 @@ class TestGenerateCopy:
user = _auth_user("u1")
session = MagicMock()
# wait_user_confirm 属于前端在编辑/确认文案的中间状态,应拒绝重新触发生成
job = _make_job(job_id="job-gc2", user_id="u1", status=ViralVideoStatus.WAIT_USER_CONFIRM)
# cancelled 状态下不应触发生成文案
job = _make_job(job_id="job-gc2", user_id="u1", status=ViralVideoStatus.CANCELLED)
repo = MagicMock()
repo.get.return_value = job
+402 -11
View File
@@ -34,6 +34,7 @@ def _make_job(job_id: str = "job-1", user_id: str = "u1", status: str = "pending
"persona_id": "",
"viral_structure": "",
"marketing_purpose": "",
"tone": "",
"bgm_preference": "",
"duration": 15,
"user_copy_text": "",
@@ -51,7 +52,6 @@ def _make_job(job_id: str = "job-1", user_id: str = "u1", status: str = "pending
"completed_at": None,
"stage": "",
"progress": 0.0,
"intent_result": None,
"image_analysis": None,
"storyboard": None,
"copy_result": {"voiceover_script": "测试口播", "shots": [{"clip_id": 1}]},
@@ -265,9 +265,9 @@ class TestBug2VoiceoverWordCount:
assert "{duration_constraint_text}" in _STORYBOARD_USER
# 15 秒规格 min_chars=38, max_chars=60,约束文本中含具体数字
t15 = build_duration_constraint_text(15)
assert "38" in t15 and "60" in t15
assert "38" in t15 and "75" in t15
t30 = build_duration_constraint_text(30)
assert "75" in t30 and "120" in t30
assert "75" in t30 and "150" in t30
# get_duration_spec 兜底:越界 clamp 到 5/30,None/0 默认 15 秒档位
assert get_duration_spec(5)["min_chars"] == 12
assert get_duration_spec(999)["min_shots"] == 6
@@ -966,7 +966,7 @@ class TestIssue2249UserParamsFix:
def test_bug2_persona_style_guide_covers_boss_ip(self):
"""Bug2: _PERSONA_STYLE_GUIDE 必须覆盖'老板型IP'"""
from apps.worker.worker_app.tasks.viral_video import _PERSONA_STYLE_GUIDE
from apps.worker.worker_app.tasks.viral_video import _DEFAULT_PERSONA_HINTS as _PERSONA_STYLE_GUIDE
assert "老板型IP" in _PERSONA_STYLE_GUIDE
assert "老板" in _PERSONA_STYLE_GUIDE["老板型IP"] or "第一人称" in _PERSONA_STYLE_GUIDE["老板型IP"]
@@ -1050,13 +1050,6 @@ class TestIssue2249UserParamsFix:
assert hasattr(job, "language")
assert job.language == "zh-CN"
def test_bug5_language_in_create_request(self):
"""Bug5: CreateViralVideoRequest 接受 language"""
from apps.api.app.schemas.viral_video import CreateViralVideoRequest
req = CreateViralVideoRequest(images=["http://a.jpg"], language="en-US")
assert req.language == "en-US"
def test_bug5_language_in_generate_copy_request(self):
"""Bug5: GenerateCopyRequest 接受 language"""
from apps.api.app.schemas.viral_video import GenerateCopyRequest
@@ -1128,3 +1121,401 @@ class TestIssue2249UserParamsFix:
assert "marketing_purpose" in _STORYBOARD_SYSTEM or "persona" in _STORYBOARD_SYSTEM
assert "不能忽略" in _STORYBOARD_SYSTEM or "必须" in _STORYBOARD_SYSTEM
class TestV4ValidationDeadlineFixes:
"""v4 编导分镜四叠 bug 回归测试(PR #2294):
1. max_chars 放宽(15s 60→75)
2. _fallback_script brand==name 去重
3. 口播超长自动截断接受
4. build_messages few-shot 多轮消息
"""
def test_duration_spec_relaxed(self):
"""验证 15s max_chars 已放宽到 75,30s 到 150"""
from packages.application.viral_video.constants import get_duration_spec
assert get_duration_spec(15)["max_chars"] == 75
assert get_duration_spec(15)["min_chars"] == 38
assert get_duration_spec(10)["max_chars"] == 50
assert get_duration_spec(20)["max_chars"] == 100
assert get_duration_spec(30)["max_chars"] == 150
assert get_duration_spec(5)["max_chars"] == 25
def test_fallback_script_brand_name_dedup(self):
"""_fallback_script: brand==name 时不应拼 'X家的X'"""
from unittest.mock import MagicMock, patch
from apps.worker.worker_app.tasks import viral_video as vv
job = MagicMock()
job.duration = 15
job.video_ratio = "9:16"
job.user_copy_text = None
job.images = []
# 构造 VLM 输出 brand==name
job.image_analysis = {"images": [{"type": "product", "name": "御众堂", "brand": "御众堂", "has_person": False}]}
job.marketing_purpose = ""
job.style_guide = None
job.persona_id = ""
job.viral_structure = ""
job.language = "zh-CN"
job.industry = ""
job.target_customer = ""
job.fusion_level = "ai_polish"
with patch.object(vv, "normalize_image_analysis", return_value=job.image_analysis):
result = vv._fallback_script(job)
vo = result["voiceover_script"]
# 不应出现"御众堂家的御众堂"重复
assert "御众堂家的御众堂" not in vo
assert "来看看御众堂" in vo
assert len(result["shots"]) == 1
def test_fallback_script_brand_name_different(self):
"""_fallback_script: brand≠name 时仍正常拼接 'X家的Y'"""
from unittest.mock import MagicMock, patch
from apps.worker.worker_app.tasks import viral_video as vv
job = MagicMock()
job.duration = 15
job.video_ratio = "9:16"
job.user_copy_text = None
job.images = []
job.image_analysis = {
"images": [{"type": "product", "name": "修护精华", "brand": "御众堂", "has_person": False}]
}
job.marketing_purpose = ""
job.style_guide = None
job.persona_id = ""
job.viral_structure = ""
job.language = "zh-CN"
job.industry = ""
job.target_customer = ""
job.fusion_level = "ai_polish"
with patch.object(vv, "normalize_image_analysis", return_value=job.image_analysis):
result = vv._fallback_script(job)
vo = result["voiceover_script"]
assert "御众堂家的修护精华" in vo
def test_build_messages_with_example(self):
"""build_messages: example_output 非空时生成 4 条消息 few-shot"""
from packages.application.viral_video.prompt_loader import (
PromptTemplate,
build_messages,
)
tpl = PromptTemplate(
name="storyboard",
prompt_type="storyboard",
version=99,
system_prompt="系统:你是分镜师",
user_prompt_template="请为{product}写脚本",
example_output="<script>示例输出</script>",
)
msgs = build_messages(tpl, product="测试商品")
assert len(msgs) == 4
assert msgs[0]["role"] == "system"
assert msgs[1]["role"] == "user"
assert "示例" in msgs[1]["content"]
assert msgs[2]["role"] == "assistant"
assert "<script>示例输出</script>" in msgs[2]["content"]
assert msgs[3]["role"] == "user"
assert "测试商品" in msgs[3]["content"]
def test_build_messages_without_example(self):
"""build_messages: example_output 为空时退化为 2 条消息"""
from packages.application.viral_video.prompt_loader import (
PromptTemplate,
build_messages,
)
tpl = PromptTemplate(
name="storyboard",
prompt_type="storyboard",
version=99,
system_prompt="系统:你是分镜师",
user_prompt_template="请写脚本",
example_output="",
)
msgs = build_messages(tpl)
assert len(msgs) == 2
assert msgs[0]["role"] == "system"
assert msgs[1]["role"] == "user"
def test_truncate_voiceover_preserves_full_sentences(self):
"""_truncate_voiceover: 按句号切句保留完整句,不截到半句"""
from apps.worker.worker_app.tasks.viral_video import _truncate_voiceover
text = "今天给大家推荐一款好产品。它的效果特别棒,用完皮肤水水润润的。大家一定要试试哦。"
out = _truncate_voiceover(text, 25)
assert len(out) <= 25
assert out.endswith("。")
assert "推荐一款好产品" in out
class TestStreamFinishReasonLength:
"""流式finish_reason=length截断检测,确保截断raw被丢弃走下一attempt(P0 2026-10-14)"""
def _make_client(self, chunks, finish_after=None, finish_reason="stop"):
from unittest.mock import MagicMock
client = MagicMock()
client.is_available = True
client.model = "test-model"
client.last_finish_reason = ""
def _stream_iter(*a, **kw):
for i, ch in enumerate(chunks):
yield ch
if finish_after is not None and i + 1 >= finish_after:
client.last_finish_reason = finish_reason
return
client.chat_completion_stream = MagicMock(side_effect=lambda *a, **kw: _stream_iter())
client.chat_completion = MagicMock(return_value=None)
return client
def test_length_truncation_returns_none(self):
"""流式被max_tokens截断时应返回None,不降级同步,让外层重试"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from unittest.mock import MagicMock
from apps.worker.worker_app.tasks import viral_video as vv
partial = '<script><copy_display_markdown>文案</copy_display_markdown><clips><clip index="1"><time_range>0-5秒</time_range>'
client = self._make_client([partial], finish_after=1, finish_reason="length")
on_delta = MagicMock()
raw = vv._call_llm_once(
client,
messages=[{"role": "user", "content": "hi"}],
temperature=0.8,
max_tokens=2500,
timeout=60,
on_delta=on_delta,
)
assert raw is None, "截断时应返回None让外层重试"
client.chat_completion.assert_not_called()
def test_normal_stop_returns_raw(self):
"""正常finish_reason=stop时应返回完整raw"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from unittest.mock import MagicMock
from apps.worker.worker_app.tasks import viral_video as vv
full = "<script><copy_display_markdown>文案</copy_display_markdown><clips></clips></script>"
client = self._make_client([full], finish_after=1, finish_reason="stop")
on_delta = MagicMock()
raw = vv._call_llm_once(
client,
messages=[{"role": "user", "content": "hi"}],
temperature=0.8,
max_tokens=2500,
timeout=60,
on_delta=on_delta,
)
assert raw == full
def test_noop_lines_removed(self):
"""确认死代码行已删除"""
import inspect
from apps.worker.worker_app.tasks import viral_video as vv
src = inspect.getsource(vv._step_script_generation)
assert 'llm_messages[0]["content"]' not in src
assert 'llm_messages[-1]["content"]' not in src
class TestMarkdownTimeRangeSplit:
"""markdown 解析器支持按"0-4秒/4-8秒"时间范围开头分镜(v6 中文格式)"""
def test_time_range_split_produces_correct_shots(self):
"""纯中文 markdown 用「0-4秒」「4-8秒」分段,应正确切分镜头"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from apps.worker.worker_app.tasks import viral_video as vv
job = _make_job(duration=20, marketing_purpose="产品展示")
raw = (
"视频主题: 御众堂产后修复\n"
"0-4秒\n"
"画面: 门店招牌特写,阳光照射\n"
"景别: 近景平拍,缓慢推进\n"
'口播: "宝妈们别划走"\n'
"BGM: 轻快钢琴\n"
"4-8秒\n"
"画面: 店内环境,技师微笑迎客\n"
"景别: 中景平视,固定镜头\n"
'口播: "御众堂产后修复,专业呵护"\n'
"BGM: 轻快钢琴\n"
"8-14秒\n"
"画面: 修复仪器操作特写\n"
"景别: 特写俯拍,缓慢横移\n"
'口播: "先进设备,科学恢复"\n'
"转场: 硬切\n"
"14-20秒\n"
"画面: 宝妈满意笑容+门店地址字幕\n"
"景别: 中景平视,缓慢拉远\n"
'口播: "点击下方预约,到店体验"\n'
)
result = vv._parse_script_markdown(raw, job, 20, "9:16", "产后修复")
assert result is not None, "时间范围分块应解析成功"
assert len(result["shots"]) == 4, f"应切分4个镜头,实际{len(result['shots'])}"
assert result["shots"][0]["time_range"] == "0-4秒"
assert result["shots"][1]["time_range"] == "4-8秒"
assert result["shots"][2]["time_range"] == "8-14秒"
assert result["shots"][3]["time_range"] == "14-20秒"
assert "宝妈们别划走" in result["shots"][0]["voiceover"]
assert "御众堂产后修复" in result["shots"][1]["voiceover"]
assert "先进设备" in result["shots"][2]["voiceover"]
assert "点击下方预约" in result["shots"][3]["voiceover"]
assert result["title"] == "御众堂产后修复"
full_vo = result["voiceover_script"]
assert "宝妈们别划走" in full_vo
assert "点击下方预约" in full_vo
def test_time_range_with_dash_and_tilde(self):
"""支持「0-4秒」「4~8秒」「12到20秒」多种连接符"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from apps.worker.worker_app.tasks import viral_video as vv
job = _make_job(duration=12)
raw = (
'0~4秒\n画面: 产品开箱\n口播: "快看这个"\n'
'4-8秒\n画面: 产品细节\n口播: "质感满分"\n'
'8到12秒\n画面: 使用效果\n口播: "赶紧下单"\n'
)
result = vv._parse_script_markdown(raw, job, 12, "9:16", "开箱")
assert result is not None
assert len(result["shots"]) == 3
assert result["shots"][0]["time_range"] == "0-4秒"
assert result["shots"][1]["time_range"] == "4-8秒"
assert result["shots"][2]["time_range"] == "8-12秒"
def test_shot_number_marker_takes_priority_over_time(self):
"""同时有「镜头1」和「0-4秒」时,优先按镜头编号分割(不重复切分)"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from apps.worker.worker_app.tasks import viral_video as vv
job = _make_job(duration=8)
raw = '镜头1 0-4秒\n画面: 开场\n口播: "开场话"\n' '镜头2 4-8秒\n画面: 主体\n口播: "主体话"\n'
result = vv._parse_script_markdown(raw, job, 8, "9:16", "测试")
assert result is not None
assert len(result["shots"]) == 2
assert result["shots"][0]["time_range"] == "0-4秒"
def test_no_markers_returns_none(self):
"""没有任何分镜标记时返回None"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from apps.worker.worker_app.tasks import viral_video as vv
job = _make_job(duration=10)
raw = "今天给大家推荐一个好物,真的非常好用,价格实惠。"
result = vv._parse_script_markdown(raw, job, 10, "9:16", "推荐")
assert result is None
def test_parse_script_routes_to_time_markdown(self):
"""_parse_script 调度:无XML时自动走时间范围markdown解析"""
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from apps.worker.worker_app.tasks import viral_video as vv
job = _make_job(duration=8)
raw = '0-4秒\n画面: 开场\n口播: "你好"\n' '4-8秒\n画面: 结束\n口播: "再见"\n'
result = vv._parse_script(raw, job)
assert result is not None
assert len(result["shots"]) == 2
assert "你好" in result["voiceover_script"]
class TestSafeFormatHardening:
"""_safe_format 必须能扛住模板里任意花括号样例,不抛异常、不返回半截内容"""
def test_missing_key_preserves_placeholder(self):
from packages.application.viral_video.prompt_loader import _safe_format
out = _safe_format("时长{duration}秒,{missing}保留", {"duration": "20"})
assert out == "时长20秒,{missing}保留"
def test_attribute_access_on_string_preserves_placeholder(self):
"""复现v5事故:{duration.1格式写全6项参数}访问str的非法属性不应抛异常"""
from packages.application.viral_video.prompt_loader import _safe_format
out = _safe_format(
"总时长:{duration.1格式写全6项参数,第二个镜头起末尾加转场}秒",
{"duration": "20"},
)
assert "总时长:{duration.1格式写全6项参数,第二个镜头起末尾加转场}秒" == out
def test_json_braces_in_example_preserved(self):
"""模板里给LLM示例JSON时,{"key":"val"}不应被当format_spec解析"""
from packages.application.viral_video.prompt_loader import _safe_format
out = _safe_format(
'输出JSON示例:{"key": "value", "n": 1},时长{duration}秒',
{"duration": "20"},
)
assert '{"key": "value", "n": 1}' in out
assert "时长20秒" in out
def test_chinese_example_braces_preserved(self):
"""中文花括号示例如{示例}原样保留"""
from packages.application.viral_video.prompt_loader import _safe_format
out = _safe_format("规则:不要输出{示例}这类占位符,时长{duration}", {"duration": "20"})
assert "{示例}" in out
assert "时长20" in out
def test_escaped_braces_unescaped(self):
"""{{escaped}} 正常输出 {escaped}"""
from packages.application.viral_video.prompt_loader import _safe_format
out = _safe_format("这是{{escaped}},时长{duration}", {"duration": "20"})
assert out == "这是{escaped},时长20"
def test_legitimate_attribute_access_works(self):
"""合法对象属性访问仍正常工作"""
from packages.application.viral_video.prompt_loader import _safe_format
class Obj:
brand = "御众堂"
out = _safe_format("品牌:{obj.brand}", {"obj": Obj()})
assert out == "品牌:御众堂"
def test_invalid_format_spec_preserves_placeholder(self):
"""{val:invalid} 这种坏格式符原样保留"""
from packages.application.viral_video.prompt_loader import _safe_format
out = _safe_format("坏格式{val:invalid_chinese}结束", {"val": "x"})
assert "{val:invalid_chinese}" in out
def test_normal_format_spec_still_works(self):
"""合法格式符 {n:>3} 仍正常工作"""
from packages.application.viral_video.prompt_loader import _safe_format
out = _safe_format("数字{n:>3}结束", {"n": 5})
assert "5" in out
-2
View File
@@ -209,7 +209,6 @@ class TestReviewWiring:
"voiceover_script": "这款口红显白又持久",
"shots": [{"scene_and_dialogue": "展示口红"}],
}
job.intent_result = {"key_messages": ["显白", "持久"], "intent": "推广"}
pass_result = ReviewResult(passed=True, score=90, issues=[], rewrite_suggestions=[])
with patch.object(Reviewer, "review", return_value=pass_result):
@@ -228,7 +227,6 @@ class TestReviewWiring:
"voiceover_script": "这款口红100%有效",
"shots": [{"scene_and_dialogue": "展示"}],
}
job.intent_result = {"key_messages": ["显白"], "intent": "推广"}
fail_result = ReviewResult(
passed=False,
+5 -27
View File
@@ -106,31 +106,11 @@ class TestWorkerEmitProgress:
assert parsed["type"] == "viral_video:failed"
assert parsed["data"]["error"] == "oom"
def test_emit_progress_wait_user_event(self):
fake_r = MagicMock()
with patch("redis.from_url", return_value=fake_r):
worker_vv._emit_progress(
"job-4",
"intent_parsing",
35,
message="waiting for you",
event_type="viral_video:wait_user",
)
_, payload = fake_r.publish.call_args.args
parsed = json.loads(payload)
assert parsed["type"] == "viral_video:wait_user"
# ---------------------------------------------------------------------------
# Pure helpers on the route module
# ---------------------------------------------------------------------------
class TestWSHelpers:
def test_estimate_progress_maps_status(self):
assert vv_module._estimate_progress(_make_job(status="pending")) == 0.0
assert vv_module._estimate_progress(_make_job(status="running")) == 5.0
assert vv_module._estimate_progress(_make_job(status="wait_user_confirm")) == 35.0
assert vv_module._estimate_progress(_make_job(status="completed")) == 100.0
assert vv_module._estimate_progress(_make_job(status="failed")) == 0.0
@@ -140,8 +120,6 @@ class TestWSHelpers:
assert isinstance(msg, str) and msg
job_failed = _make_job(status="failed", error_msg="boom")
assert "boom" in vv_module._initial_message(job_failed)
job_wait = _make_job(status="wait_user_confirm")
assert "等待" in vv_module._initial_message(job_wait)
def test_stage_from_status_falls_back(self):
assert isinstance(vv_module._stage_from_status(_make_job(status="pending")), str)
@@ -325,16 +303,16 @@ class TestWSInitialSnapshot:
# Session was used for both ownership check and initial snapshot.
assert sess.close.call_count >= 2
def test_running_job_with_enum_status(self):
def test_image_analyzed_job_sends_snapshot(self):
job = _make_job(
status=SimpleNamespace(value="wait_user_confirm"),
status=SimpleNamespace(value="image_analyzed"),
user_id="user-a",
is_terminal=False,
)
received, _, _ = _run_ws_handshake(job=job)
assert received[0]["data"]["status"] == "wait_user_confirm"
assert received[0]["progress"] == 35.0
assert "等待" in received[0]["message"]
assert received[0]["data"]["status"] == "image_analyzed"
assert received[0]["progress"] == 15.0
assert "图片分析" in received[0]["message"]
def test_already_completed_job_sends_completion_event_and_closes(self):
job = _make_job(