Compare commits

..

256 Commits

Author SHA1 Message Date
xiaoxia 916e624516 test: remove deleted module imports from smoke.test.tsx
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 2s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 48s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 58s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m7s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m9s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m32s
AI Code Review / AI Code Review (pull_request) Successful in 6m40s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 9m21s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 11m6s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 12m4s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 30m13s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 12s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m12s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 40h7m36s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 40h37m45s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 40h37m48s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 40h37m51s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 40h37m14s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 40h37m9s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 40h37m12s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 40h37m14s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 40h37m14s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 40h37m19s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 40h37m20s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 40h7m2s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 40h7m2s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 40h7m5s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 40h7m5s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 40h37m8s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 40h37m9s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 40h37m19s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 40h37m22s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 40h37m19s
2026-09-14 22:45:51 +08:00
xiaoxia b3fc8f1a58 test: remove obsolete test for deleted page/hook (useTemplateSelection.test.tsx)
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 40m48s
CI/CD Pipeline / Check if frontend-only change (pull_request) Failing after 40m53s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Failing after 40m54s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 1m38s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / 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 / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 40h41m1s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 40h41m7s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 40h41m12s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 40h40m41s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 40h40m41s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 40h40m32s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 40h40m34s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 40h40m34s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 40h40m35s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 40h40m36s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 40h40m41s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 40h40m30s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 40h40m30s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 40h40m41s
2026-09-14 21:49:29 +08:00
xiaoxia 0dbb44b8e5 test: remove obsolete test for deleted page/hook (TemplateLibrary.test.tsx)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
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
2026-09-14 21:49:24 +08:00
xiaoxia b619aa4fe9 test: remove obsolete test for deleted page/hook (MyTemplates.test.tsx)
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) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
PR Automation / Auto Approve on CI Green (pull_request) Failing after 40m47s
AI Code Review / AI Code Review (pull_request) Failing after 40m54s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m18s
2026-09-14 21:49:19 +08:00
xiaoxia 63b991f0c6 test: remove obsolete test for deleted page/hook (EditingPlanner.test.tsx)
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 / Validate - Style (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:49:14 +08:00
xiaoxia 4a244f7530 style: reformat smart-match-pool.test.ts with repo prettier config (#1899 #1900)
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 4s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 49s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m24s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m29s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m47s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 2m4s
AI Code Review / AI Code Review (pull_request) Successful in 6m33s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 10m8s
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 / 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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 41h45m23s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 41h45m31s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 41h44m53s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 41h44m46s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 41h44m51s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 41h44m53s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 41h44m53s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 41h45m0s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 41h45m1s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 41h44m45s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 41h44m46s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 41h44m54s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 41h44m54s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 41h45m4s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 42h20m20s
2026-09-14 21:38:03 +08:00
xiaoxia fd7cc25ac5 style: reformat GeneratePage.test.tsx with repo prettier config (#1899 #1900)
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) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Waiting to run
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:38:00 +08:00
xiaoxia 93b32eba51 style: reformat navigation.test.ts with repo prettier config (#1899 #1900) 2026-09-14 21:37:58 +08:00
xiaoxia b18e5964f6 style: reformat appRoutes.tsx with repo prettier config (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
AI Code Review / AI Code Review (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
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:37:55 +08:00
xiaoxia 37290c09d7 style: reformat useStepNavigation.ts with repo prettier config (#1899 #1900)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:37:51 +08:00
xiaoxia 0697d9c67d style: reformat useStep2Materials.ts with repo prettier config (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:37:48 +08:00
xiaoxia 0e2cefaa5f style: reformat index.ts with repo prettier config (#1899 #1900)
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:37:45 +08:00
xiaoxia 712e381ec6 style: reformat useSmartMatch.ts with repo prettier config (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:37:36 +08:00
xiaoxia ad8dda8e36 style: reformat constants.ts with repo prettier config (#1899 #1900) 2026-09-14 21:37:32 +08:00
xiaoxia a42310215e style: reformat Step2MaterialSelect.tsx with repo prettier config (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
AI Code Review / AI Code Review (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:37:27 +08:00
xiaoxia 763dee6fb9 style: reformat GenerateStepActions.tsx with repo prettier config (#1899 #1900)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
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 Approve on CI Green (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
2026-09-14 21:37:24 +08:00
xiaoxia 5a9c97d64c style: reformat GeneratePage.tsx with repo prettier config (#1899 #1900) 2026-09-14 21:37:22 +08:00
xiaoxia e40175d358 style: reformat types.ts with repo prettier config (#1899 #1900)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 0s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Waiting to run
CI/CD Pipeline / Validate - Style (pull_request) Waiting to run
CI/CD Pipeline / Build Staging Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Validate - Security (pull_request) Waiting to run
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Waiting to run
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Unit Tests (pull_request) Waiting to run
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Integration Tests (pull_request) Waiting to run
CI/CD Pipeline / Frontend Lint (pull_request) Waiting to run
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (pull_request) Waiting to run
CI/CD Pipeline / PR Build Web Image (pull_request) Waiting to run
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Waiting to run
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:37:20 +08:00
xiaoxia a74f586564 style: reformat navigation.ts with repo prettier config (#1899 #1900) 2026-09-14 21:37:18 +08:00
xiaoxia ac606fcf1e style: format appRoutes.tsx with prettier (#1899 #1900)
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 / PR Build Web Image (pull_request) Successful in 1m43s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m58s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m0s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m33s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 3m32s
AI Code Review / AI Code Review (pull_request) Successful in 7m1s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / 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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 41h54m8s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 41h54m17s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 41h54m18s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 41h53m47s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 41h53m44s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 41h53m46s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 41h53m46s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 41h53m49s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 41h53m49s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 41h53m55s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 41h53m38s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 41h53m38s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 41h53m47s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 41h53m48s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 42h29m12s
2026-09-14 21:29:13 +08:00
xiaoxia d176589c53 style: format useStepNavigation.ts with prettier (#1899 #1900) 2026-09-14 21:29:09 +08:00
xiaoxia 6f06de232b style: format useStep2Materials.ts with prettier (#1899 #1900)
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:29:05 +08:00
xiaoxia 1962b8abc3 style: format index.ts with prettier (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
2026-09-14 21:29:02 +08:00
xiaoxia 517d05f9cc style: format useSmartMatch.ts with prettier (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
AI Code Review / AI Code Review (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
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:28:59 +08:00
xiaoxia 8fcb0c99b0 style: format constants.ts with prettier (#1899 #1900) 2026-09-14 21:28:57 +08:00
xiaoxia f14a83ff35 style: format Step2MaterialSelect.tsx with prettier (#1899 #1900)
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 / Validate - Style (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 41h54m48s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 41h54m19s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 41h54m18s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 41h54m20s
2026-09-14 21:28:55 +08:00
xiaoxia b86b500152 style: format GenerateStepActions.tsx with prettier (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:28:53 +08:00
xiaoxia c3503523e6 style: format navigation.ts with prettier (#1899 #1900) 2026-09-14 21:28:52 +08:00
xiaoxia ab1896b609 style: format editing-planner/types.ts with prettier (#1899 #1900)
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
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 48s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 58s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 46s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m13s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 3m30s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / 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
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 41h58m13s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 41h58m25s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 42h0m10s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 41h57m55s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 41h57m57s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 41h57m30s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 41h57m30s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 41h57m43s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 41h58m44s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 41h59m39s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 41h59m42s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 41h57m30s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 41h57m36s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 41h57m44s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 42h33m24s
2026-09-14 21:23:26 +08:00
xiaoxia 5d3ef34857 style: format GeneratePage.tsx with prettier (#1899 #1900) 2026-09-14 21:23:24 +08:00
xiaoxia d201d6fd91 chore: remove obsolete test file (#1899 #1900)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 36s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m1s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 37s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / Frontend 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
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
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 42h0m36s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 42h1m33s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 42h2m10s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 42h1m4s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 42h1m36s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 42h0m6s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 42h0m7s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 42h0m8s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 42h1m4s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 42h1m4s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 42h0m6s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 42h0m6s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 42h1m41s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 42h1m45s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 42h35m33s
2026-09-14 21:21:26 +08:00
xiaoxia 85ad3827ad chore: remove obsolete test file (#1899 #1900) 2026-09-14 21:21:24 +08:00
xiaoxia dbe9adf04e chore: remove obsolete test file (#1899 #1900)
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) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:21:20 +08:00
xiaoxia 968176f036 chore: remove obsolete test file (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:21:18 +08:00
xiaoxia 0619f096dd chore: remove obsolete test file (#1899 #1900)
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) 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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:21:13 +08:00
xiaoxia 1e9a981586 chore: remove obsolete test file (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:21:10 +08:00
xiaoxia e866851284 chore: remove obsolete test file (#1899 #1900) 2026-09-14 21:21:07 +08:00
xiaoxia dcae4fcef8 chore: remove obsolete test file (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:21:05 +08:00
xiaoxia caa7d9958b chore: remove obsolete test file (#1899 #1900)
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) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:21:03 +08:00
xiaoxia 0b9d3cb523 chore: remove obsolete test file (#1899 #1900)
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 2s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:20:41 +08:00
xiaoxia 75bec86e9f style: format GeneratePage.test.tsx with prettier
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:20:16 +08:00
xiaoxia 81fd9eceb3 style: format navigation.test.ts with prettier
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Validate - Style (pull_request) Waiting to run
CI/CD Pipeline / Validate - Security (pull_request) Waiting to run
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Waiting to run
CI/CD Pipeline / Unit Tests (pull_request) Waiting to run
CI/CD Pipeline / Integration Tests (pull_request) Waiting to run
CI/CD Pipeline / Frontend Lint (pull_request) Waiting to run
CI/CD Pipeline / Frontend Unit Tests (pull_request) Waiting to run
CI/CD Pipeline / PR Build API Image (pull_request) Waiting to run
CI/CD Pipeline / PR Build Web Image (pull_request) Waiting to run
CI/CD Pipeline / PR Build Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging Web Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:20:11 +08:00
xiaoxia 6451165e0e style: format smart-match-pool.test.ts with prettier
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) 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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:20:07 +08:00
xiaoxia f7fc8d0741 test: add isAssetUsable mock to api/assets (#1899)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 43s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 30s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 2m18s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m2s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / 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
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 42h3m48s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 42h6m57s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 42h5m44s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 42h6m31s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 42h6m31s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 42h6m32s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 42h3m14s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 42h4m5s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 42h5m13s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 42h5m14s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 42h5m14s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 42h6m27s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 42h6m34s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 42h40m40s
2026-09-14 21:16:30 +08:00
xiaoxia 5e5ee488fe test: remove templates assertion from navigation test (#1899)
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 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:16:26 +08:00
xiaoxia 0c70dbb7ca test: rewrite smart-match-pool test for computeLimitFromClipCount (#1899) 2026-09-14 21:16:23 +08:00
xiaoxia 44bc0a24ff fix: remove unused userTemplates/handleInvalidTemplate (#1899 #1900)
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 / Validate - Style (pull_request) Has been cancelled
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 / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 42h8m0s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 42h7m35s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 42h7m31s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 42h7m29s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 42h7m28s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 42h7m27s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 42h7m34s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 42h7m41s
2026-09-14 21:15:26 +08:00
xiaoxia 7bf825dadb chore: remove test file ClipCard.test.tsx (#1899 #1900)
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 / Frontend Lint (pull_request) Failing after 29s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 46s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 1m8s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (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
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 42h9m17s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 42h9m22s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 42h9m33s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 42h9m3s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 42h9m2s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 42h9m2s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 42h8m49s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 42h8m52s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 42h8m52s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 42h9m0s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 42h9m1s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 42h8m47s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 42h8m47s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 42h9m17s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 42h44m27s
2026-09-14 21:13:50 +08:00
xiaoxia 5a6ac78df9 chore: remove test file AddClipPicker.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
2026-09-14 21:13:47 +08:00
xiaoxia 923e577630 chore: remove test file WatermarkPanel.test.tsx (#1899 #1900)
PR Automation / Auto Approve on CI Green (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
AI Code Review / AI Code Review (pull_request) Has been cancelled
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
2026-09-14 21:13:41 +08:00
xiaoxia 9332c9d60e chore: remove test file useTtsPanel.test.ts (#1899 #1900) 2026-09-14 21:13:38 +08:00
xiaoxia b4f9d21ef8 chore: remove test file constants.test.ts (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
2026-09-14 21:13:36 +08:00
xiaoxia 90278d816c chore: remove test file VoiceSelector.test.tsx (#1899 #1900) 2026-09-14 21:13:32 +08:00
xiaoxia 3296a71166 chore: remove test file TtsSlider.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:13:29 +08:00
xiaoxia 6487e790a4 chore: remove test file TtsPanel.test.tsx (#1899 #1900)
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
2026-09-14 21:13:26 +08:00
xiaoxia 4fa1f35e05 chore: remove test file TtsPanel.smoke.test.tsx (#1899 #1900)
CI/CD Pipeline / Validate - Style (pull_request) Blocked by required conditions
CI/CD Pipeline / Validate - Security (pull_request) Blocked by required conditions
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Blocked by required conditions
CI/CD Pipeline / Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Check if frontend-only change (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Waiting to run
PR Automation / Auto Merge on CI Green + Approved (pull_request) Blocked by required conditions
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
2026-09-14 21:13:25 +08:00
xiaoxia b062d9fed2 chore: remove test file TransitionSelector.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Blocked by required conditions
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
2026-09-14 21:13:21 +08:00
xiaoxia 14f36a3bb9 chore: remove test file TimelinePanel.test.tsx (#1899 #1900) 2026-09-14 21:13:18 +08:00
xiaoxia bc42f515bf chore: remove test file SubtitleStylePanel.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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
AI Code Review / AI Code Review (pull_request) Waiting to run
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Waiting to run
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
2026-09-14 21:13:15 +08:00
xiaoxia c811d266f2 chore: remove test file useStickerPanel.test.ts (#1899 #1900)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
AI Code Review / AI Code Review (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
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (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
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
2026-09-14 21:13:10 +08:00
xiaoxia a7738dcfc3 chore: remove test file constants.test.ts (#1899 #1900) 2026-09-14 21:13:06 +08:00
xiaoxia bbc24b8363 chore: remove test file StickerTabs.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
AI Code Review / AI Code Review (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
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:13:02 +08:00
xiaoxia 86cd68ea8d chore: remove test file StickerPropsEditor.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:12:58 +08:00
xiaoxia c144b4adaf chore: remove test file StickerList.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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
PR Automation / Auto Approve on CI Green (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
AI Code Review / AI Code Review (pull_request) Has been cancelled
2026-09-14 21:12:55 +08:00
xiaoxia 1b6a10d594 chore: remove test file StickerPanel.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
2026-09-14 21:12:50 +08:00
xiaoxia c8a6209a70 chore: remove test file SpeedPanel.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Blocked by required conditions
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
2026-09-14 21:12:47 +08:00
xiaoxia 380d64be7a chore: remove test file SaveModal.test.tsx (#1899 #1900) 2026-09-14 21:12:43 +08:00
xiaoxia 4efb7d2f3a chore: remove test file PreviewPlayer.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (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 / Check if frontend-only change (pull_request) Successful in 2s
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Waiting to run
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:12:38 +08:00
xiaoxia 0d491d7fe6 chore: remove test file usePipConfigPanel.test.ts (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Blocked by required conditions
CI/CD Pipeline / Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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 started running
2026-09-14 21:12:34 +08:00
xiaoxia 0c71b8ccc8 chore: remove test file constants.test.ts (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:12:31 +08:00
xiaoxia 8ce9064cf9 chore: remove test file PipPreview.test.tsx (#1899 #1900)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
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 / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (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 Approve on CI Green (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
2026-09-14 21:12:25 +08:00
xiaoxia d6d009d1ee chore: remove test file LayerList.test.tsx (#1899 #1900) 2026-09-14 21:12:20 +08:00
xiaoxia 0787911600 chore: remove test file LayerConfig.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:12:16 +08:00
xiaoxia 671a5d17c5 chore: remove test file PipConfigPanel.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Blocked by required conditions
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Blocked by required conditions
CI/CD Pipeline / Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:12:13 +08:00
xiaoxia 62a0ceea4d chore: remove test file MediaPanel.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:12:10 +08:00
xiaoxia 53bd275f3c chore: remove test file IntroOutroPanel.test.tsx (#1899 #1900)
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) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Waiting to run
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging API Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging Web Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
PR Automation / Auto Approve on CI Green (pull_request) Waiting to run
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
PR Automation / Auto Merge on CI Green + Approved (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
2026-09-14 21:12:05 +08:00
xiaoxia c88cdbf41a chore: remove test file GreenScreenPanel.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (pull_request) Waiting to run
CI/CD Pipeline / Validate - Style (pull_request) Blocked by required conditions
CI/CD Pipeline / Validate - Security (pull_request) Blocked by required conditions
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Blocked by required conditions
CI/CD Pipeline / Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
Preview Deploy / Deploy Preview Environment (pull_request) Waiting to run
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
2026-09-14 21:12:03 +08:00
xiaoxia 919c7a8fec chore: remove test file FilterPanel.test.tsx (#1899 #1900)
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 / Validate - Style (pull_request) Has been cancelled
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 / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 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 / Build Production Web Image (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 Approve on CI Green (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
2026-09-14 21:12:01 +08:00
xiaoxia 2813d7f9c8 chore: remove test file EditorClipList.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (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 Approve on CI Green (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
2026-09-14 21:11:59 +08:00
xiaoxia ef5265b0fb chore: remove test file ClipPropertiesPanel.test.tsx (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (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 / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (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
2026-09-14 21:11:58 +08:00
xiaoxia 00cf91930b chore: remove test file BgmSelector.test.tsx (#1899 #1900) 2026-09-14 21:11:57 +08:00
xiaoxia d5fe189c84 fix: 添加最小化 editing-planner/types.ts 供 api 层导入 (#1899 #1900)
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
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 35s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m9s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m17s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 1m43s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 2m43s
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
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 / 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
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m52s
AI Code Review / AI Code Review (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 42h14m44s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 42h14m56s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 42h14m58s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 42h14m27s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 42h14m21s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 42h14m26s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 42h14m26s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 42h14m29s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 42h14m29s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 42h14m29s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 42h14m14s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 42h14m15s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 42h14m28s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 42h14m26s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 42h14m30s
2026-09-14 21:08:43 +08:00
xiaoxia dc79fbfa7b refactor: remove templates/editing-planner/my-templates nav items (#1900)
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) Failing after 48s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 1m3s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m4s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 1m48s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 3m23s
AI Code Review / AI Code Review (pull_request) Successful in 6m48s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 10m18s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m38s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 12m27s
CI/CD Pipeline / Validate - Security (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 / Staging API Integration Tests (pull_request) Failing after 42h29m20s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 42h29m28s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 42h29m30s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 42h28m57s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 42h28m55s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 42h28m55s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 42h28m55s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 42h28m58s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 42h28m58s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 42h29m0s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 42h29m0s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 42h28m50s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 42h28m50s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 42h29m2s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 43h4m23s
2026-09-14 20:52:40 +08:00
xiaoxia 12afeee905 refactor: remove templates/editing-planner/my-templates routes (#1900) 2026-09-14 20:52:39 +08:00
xiaoxia 18702f58c0 refactor: rewrite GeneratePage for 5-step flow with clipCount (#1899) 2026-09-14 20:52:12 +08:00
xiaoxia 79f985de6b refactor: simplify useGenerateFormState - remove template UI, add clipCount (#1899) 2026-09-14 20:49:58 +08:00
xiaoxia 1cbcec7508 refactor: replace templateSegments with clipCount in useSmartMatch (#1899) 2026-09-14 20:48:55 +08:00
xiaoxia 0f443f794d refactor: replace templateSegments with clipCount in useStep2Materials (#1899) 2026-09-14 20:48:55 +08:00
xiaoxia 73cddd73ba refactor: update Step2MaterialSelect with clipCount prop, remove template props (#1899) 2026-09-14 20:47:55 +08:00
xiaoxia dd4193e7ba refactor: rewrite useStepNavigation for 5-step flow (#1899) 2026-09-14 20:47:55 +08:00
xiaoxia d446149304 refactor: rewrite GenerateStepActions for 5-step flow (#1899) 2026-09-14 20:47:05 +08:00
xiaoxia f8987dcca7 refactor: rewrite GenerateStepContent for 5-step flow (#1899) 2026-09-14 20:47:04 +08:00
xiaoxia d48e8d4bdf refactor: update STEPS to 5 steps + add clip count constants (#1899) 2026-09-14 20:45:58 +08:00
xiaoxia 347b2a367a refactor: delete useTitleCoverSync.ts (#1899 #1900) 2026-09-14 20:45:33 +08:00
xiaoxia d41b4cacbc refactor: delete useTemplateSelection.ts (#1899 #1900) 2026-09-14 20:45:33 +08:00
xiaoxia 1a11ee2b17 refactor: delete useStep1Template.ts (#1899 #1900) 2026-09-14 20:45:32 +08:00
xiaoxia 821cd85267 refactor: delete Step1TemplateSelect.tsx (#1899 #1900) 2026-09-14 20:45:32 +08:00
xiaoxia 32d6604fe3 refactor: delete useMyTemplates.ts (#1899 #1900) 2026-09-14 20:45:31 +08:00
xiaoxia 0cf2e0edc6 refactor: delete TemplateCard.tsx (#1899 #1900) 2026-09-14 20:45:31 +08:00
xiaoxia bc443e4dbb refactor: delete MyTemplates.tsx (#1899 #1900) 2026-09-14 20:45:30 +08:00
xiaoxia 550a834588 refactor: delete MyTemplates.css (#1899 #1900) 2026-09-14 20:45:30 +08:00
xiaoxia 0db752684e refactor: delete templateLibrary.ts (#1899 #1900) 2026-09-14 20:45:29 +08:00
xiaoxia 6e61d9ba5e refactor: delete templateLibrary.ts (#1899 #1900) 2026-09-14 20:45:29 +08:00
xiaoxia 11b9955bac refactor: delete templates.css (#1899 #1900) 2026-09-14 20:45:29 +08:00
xiaoxia 14e098ca8f refactor: delete useTemplateLibrary.ts (#1899 #1900) 2026-09-14 20:45:28 +08:00
xiaoxia 663e5ef777 refactor: delete useTemplateDetail.ts (#1899 #1900) 2026-09-14 20:45:28 +08:00
xiaoxia a181384ce5 refactor: delete templateLibrary.ts (#1899 #1900) 2026-09-14 20:45:27 +08:00
xiaoxia 8e6c42ad6d refactor: delete types.ts (#1899 #1900) 2026-09-14 20:45:27 +08:00
xiaoxia be960bf55e refactor: delete index.ts (#1899 #1900) 2026-09-14 20:45:26 +08:00
xiaoxia 0e6b305ba0 refactor: delete StyleConfig.tsx (#1899 #1900) 2026-09-14 20:45:26 +08:00
xiaoxia 88f62096b5 refactor: delete SegmentList.tsx (#1899 #1900) 2026-09-14 20:45:26 +08:00
xiaoxia 9f332aac38 refactor: delete PreviewArea.tsx (#1899 #1900) 2026-09-14 20:45:25 +08:00
xiaoxia a667239137 refactor: delete DetailFooter.tsx (#1899 #1900) 2026-09-14 20:45:24 +08:00
xiaoxia 78454c2bf2 refactor: delete TemplateToolbar.tsx (#1899 #1900) 2026-09-14 20:45:24 +08:00
xiaoxia b8643bb643 refactor: delete TemplateHeader.tsx (#1899 #1900) 2026-09-14 20:45:23 +08:00
xiaoxia 3eaa130670 refactor: delete TemplateGrid.tsx (#1899 #1900) 2026-09-14 20:45:23 +08:00
xiaoxia 56b6a72a04 refactor: delete TemplateDetailModal.tsx (#1899 #1900) 2026-09-14 20:45:21 +08:00
xiaoxia 6e96ef4c32 refactor: delete TemplateCard.tsx (#1899 #1900) 2026-09-14 20:45:21 +08:00
xiaoxia e31255dec6 refactor: delete TemplateLibrary.tsx (#1899 #1900) 2026-09-14 20:45:20 +08:00
xiaoxia 59ba499032 refactor: delete timeline.ts (#1899 #1900) 2026-09-14 20:45:20 +08:00
xiaoxia 0970edb2fd refactor: delete selectors.ts (#1899 #1900) 2026-09-14 20:45:19 +08:00
xiaoxia 211c4f4e05 refactor: delete clipProperties.ts (#1899 #1900) 2026-09-14 20:45:19 +08:00
xiaoxia 9c93911ae4 refactor: delete watermark.ts (#1899 #1900) 2026-09-14 20:45:18 +08:00
xiaoxia f67e6400ba refactor: delete tts.ts (#1899 #1900) 2026-09-14 20:45:17 +08:00
xiaoxia c8a2ea0b3b refactor: delete trim.ts (#1899 #1900) 2026-09-14 20:45:17 +08:00
xiaoxia ee7f37229a refactor: delete transition.ts (#1899 #1900) 2026-09-14 20:45:17 +08:00
xiaoxia 70fb1a69da refactor: delete title.ts (#1899 #1900) 2026-09-14 20:45:16 +08:00
xiaoxia 60d44c11dc refactor: delete subtitle.ts (#1899 #1900) 2026-09-14 20:45:15 +08:00
xiaoxia d8596fec28 refactor: delete sticker.ts (#1899 #1900) 2026-09-14 20:45:14 +08:00
xiaoxia 21335df135 refactor: delete speed.ts (#1899 #1900) 2026-09-14 20:45:14 +08:00
xiaoxia c7132f6350 refactor: delete pip.ts (#1899 #1900) 2026-09-14 20:45:13 +08:00
xiaoxia 32606867bf refactor: delete intro-outro.ts (#1899 #1900) 2026-09-14 20:45:13 +08:00
xiaoxia 9a9cfe0415 refactor: delete index.ts (#1899 #1900) 2026-09-14 20:45:12 +08:00
xiaoxia 055e1689db refactor: delete filter.ts (#1899 #1900) 2026-09-14 20:45:12 +08:00
xiaoxia 90d808d5fd refactor: delete clipProperties.ts (#1899 #1900) 2026-09-14 20:45:12 +08:00
xiaoxia e942e8b5bb refactor: delete clip.ts (#1899 #1900) 2026-09-14 20:45:11 +08:00
xiaoxia 4d21fdd36b refactor: delete chroma-key.ts (#1899 #1900) 2026-09-14 20:45:05 +08:00
xiaoxia 2fd67ed309 refactor: delete useWatermarkConfig.ts (#1899 #1900) 2026-09-14 20:45:05 +08:00
xiaoxia 726843a1cf refactor: delete useVoicePreview.ts (#1899 #1900) 2026-09-14 20:45:04 +08:00
xiaoxia b27d16c2fa refactor: delete useVoiceMaterials.ts (#1899 #1900) 2026-09-14 20:45:04 +08:00
xiaoxia 357943816f refactor: delete useUndoRedo.ts (#1899 #1900) 2026-09-14 20:45:03 +08:00
xiaoxia 19b93deea8 refactor: delete useTtsPanel.ts (#1899 #1900) 2026-09-14 20:45:02 +08:00
xiaoxia b2b20535e4 refactor: delete useTrimDrag.ts (#1899 #1900) 2026-09-14 20:45:02 +08:00
xiaoxia 9df56657f0 refactor: delete useTimelineMenus.ts (#1899 #1900) 2026-09-14 20:45:01 +08:00
xiaoxia 8e6a46ae0a refactor: delete useTemplateManagement.ts (#1899 #1900) 2026-09-14 20:45:01 +08:00
xiaoxia f98ad2d9bb refactor: delete useStickerItems.ts (#1899 #1900) 2026-09-14 20:45:01 +08:00
xiaoxia 9e2a84b2c9 refactor: delete usePlaybackControl.ts (#1899 #1900) 2026-09-14 20:45:00 +08:00
xiaoxia 99186d30c0 refactor: delete usePipLayers.ts (#1899 #1900) 2026-09-14 20:44:59 +08:00
xiaoxia fa7ec8fc67 refactor: delete useGlobalSettings.ts (#1899 #1900) 2026-09-14 20:44:59 +08:00
xiaoxia 0213447bdc refactor: delete useEditorDrawers.ts (#1899 #1900) 2026-09-14 20:44:59 +08:00
xiaoxia 59c74e344f refactor: delete useEditPlanClipMutations.ts (#1899 #1900) 2026-09-14 20:44:58 +08:00
xiaoxia 2a65263b7d refactor: delete useEditPlanClipList.ts (#1899 #1900) 2026-09-14 20:44:58 +08:00
xiaoxia 0ec30fad27 refactor: delete useClipReorder.ts (#1899 #1900) 2026-09-14 20:44:57 +08:00
xiaoxia 87b2fec441 refactor: delete useClipImport.ts (#1899 #1900) 2026-09-14 20:44:57 +08:00
xiaoxia 118b364520 refactor: delete index.ts (#1899 #1900) 2026-09-14 20:44:57 +08:00
xiaoxia 231f6063e4 refactor: delete useClipTrim.ts (#1899 #1900) 2026-09-14 20:44:56 +08:00
xiaoxia ce8ad6db4e refactor: delete useClipEffects.ts (#1899 #1900) 2026-09-14 20:44:56 +08:00
xiaoxia c2e2b3aa1b refactor: delete useClipBasicOps.ts (#1899 #1900) 2026-09-14 20:44:55 +08:00
xiaoxia f2a44e5393 refactor: delete index.ts (#1899 #1900) 2026-09-14 20:44:54 +08:00
xiaoxia e0e631bf7b refactor: delete useClipDrag.ts (#1899 #1900) 2026-09-14 20:44:54 +08:00
xiaoxia c0404816c3 refactor: delete useContextMenu.ts (#1899 #1900) 2026-09-14 20:44:53 +08:00
xiaoxia ce4e9b95e8 refactor: delete useAddPicker.ts (#1899 #1900) 2026-09-14 20:44:53 +08:00
xiaoxia 7f7c3bb683 refactor: delete useTemplateSave.ts (#1899 #1900) 2026-09-14 20:44:52 +08:00
xiaoxia 47ffde9c3b refactor: delete useTemplateList.ts (#1899 #1900) 2026-09-14 20:44:52 +08:00
xiaoxia 231293adef refactor: delete useTemplateDetail.ts (#1899 #1900) 2026-09-14 20:44:52 +08:00
xiaoxia 253e21efdf refactor: delete usePlanLoading.ts (#1899 #1900) 2026-09-14 20:44:51 +08:00
xiaoxia 76f8c47324 refactor: delete watermark.ts (#1899 #1900) 2026-09-14 20:44:50 +08:00
xiaoxia 326927b16c refactor: delete tts.ts (#1899 #1900) 2026-09-14 20:44:50 +08:00
xiaoxia 574cb89014 refactor: delete timeline.ts (#1899 #1900) 2026-09-14 20:44:50 +08:00
xiaoxia d60eba950e refactor: delete subtitleStyle.ts (#1899 #1900) 2026-09-14 20:44:49 +08:00
xiaoxia 72bf12253e refactor: delete sticker.ts (#1899 #1900) 2026-09-14 20:44:49 +08:00
xiaoxia 0df1eea3bf refactor: delete pipConfig.ts (#1899 #1900) 2026-09-14 20:44:49 +08:00
xiaoxia ac5c3ed1ef refactor: delete introOutro.ts (#1899 #1900) 2026-09-14 20:44:48 +08:00
xiaoxia 5402134bd3 refactor: delete filter.ts (#1899 #1900) 2026-09-14 20:44:48 +08:00
xiaoxia 339789c135 refactor: delete clipProperties.ts (#1899 #1900) 2026-09-14 20:44:47 +08:00
xiaoxia 3dcea7393a refactor: delete constants.ts (#1899 #1900) 2026-09-14 20:44:47 +08:00
xiaoxia 37dcaf498c refactor: delete WatermarkTypeTabs.tsx (#1899 #1900) 2026-09-14 20:44:46 +08:00
xiaoxia d08b4e82eb refactor: delete WatermarkCommonSection.tsx (#1899 #1900) 2026-09-14 20:44:46 +08:00
xiaoxia dd5aeb4bb3 refactor: delete TextWatermarkSection.tsx (#1899 #1900) 2026-09-14 20:44:45 +08:00
xiaoxia 7d24f7a1fd refactor: delete ScrollWatermarkSection.tsx (#1899 #1900) 2026-09-14 20:44:45 +08:00
xiaoxia c62e7d2261 refactor: delete ImageWatermarkSection.tsx (#1899 #1900) 2026-09-14 20:44:44 +08:00
xiaoxia 0f3d6b5421 refactor: delete usePlayheadDrag.ts (#1899 #1900) 2026-09-14 20:44:44 +08:00
xiaoxia c7b99fadbb refactor: delete TrimPreview.tsx (#1899 #1900) 2026-09-14 20:44:44 +08:00
xiaoxia 53bcac7df6 refactor: delete TimelineHeader.tsx (#1899 #1900) 2026-09-14 20:44:43 +08:00
xiaoxia f8c1610bd9 refactor: delete TimeRuler.tsx (#1899 #1900) 2026-09-14 20:44:43 +08:00
xiaoxia 7fec9afda0 refactor: delete ContextMenu.tsx (#1899 #1900) 2026-09-14 20:44:43 +08:00
xiaoxia c254a791e2 refactor: delete ClipTrack.tsx (#1899 #1900) 2026-09-14 20:44:42 +08:00
xiaoxia c0c6e954dc refactor: delete ClipCard.tsx (#1899 #1900) 2026-09-14 20:44:41 +08:00
xiaoxia 2a8218a89d refactor: delete AddClipPicker.tsx (#1899 #1900) 2026-09-14 20:44:41 +08:00
xiaoxia 28444a7d8a refactor: delete SubtitlePreview.tsx (#1899 #1900) 2026-09-14 20:44:41 +08:00
xiaoxia 8825f2f45f refactor: delete SubtitlePositionSelector.tsx (#1899 #1900) 2026-09-14 20:44:40 +08:00
xiaoxia c5c887e571 refactor: delete SubtitleModeSwitch.tsx (#1899 #1900) 2026-09-14 20:44:39 +08:00
xiaoxia d31631ace2 refactor: delete SubtitleEffectButtons.tsx (#1899 #1900) 2026-09-14 20:44:39 +08:00
xiaoxia 5a8287354d refactor: delete IntroOutroBlock.tsx (#1899 #1900) 2026-09-14 20:44:38 +08:00
xiaoxia e661a07b69 refactor: delete GreenScreenSliders.tsx (#1899 #1900) 2026-09-14 20:44:37 +08:00
xiaoxia 5838b4aa9f refactor: delete GreenScreenPreview.tsx (#1899 #1900) 2026-09-14 20:44:36 +08:00
xiaoxia 5f116474d7 refactor: delete GreenScreenPresets.tsx (#1899 #1900) 2026-09-14 20:44:35 +08:00
xiaoxia 5c3161199e refactor: delete GreenScreenCustomColor.tsx (#1899 #1900) 2026-09-14 20:44:35 +08:00
xiaoxia c6a5b1cf92 refactor: delete FilterPresetGrid.tsx (#1899 #1900) 2026-09-14 20:44:34 +08:00
xiaoxia d64d918f3b refactor: delete FilterManualAdjust.tsx (#1899 #1900) 2026-09-14 20:44:34 +08:00
xiaoxia 0909e39715 refactor: delete types.ts (#1899 #1900) 2026-09-14 20:44:33 +08:00
xiaoxia 03b6fb0bd8 refactor: delete GlobalDrawers.tsx (#1899 #1900) 2026-09-14 20:44:33 +08:00
xiaoxia c20d4152a7 refactor: delete ClipLevelDrawers.tsx (#1899 #1900) 2026-09-14 20:44:32 +08:00
xiaoxia 7125f10ec0 refactor: delete index.tsx (#1899 #1900) 2026-09-14 20:44:31 +08:00
xiaoxia 33a09b1a6c refactor: delete VoiceMaterialSection.tsx (#1899 #1900) 2026-09-14 20:44:30 +08:00
xiaoxia 78741ed588 refactor: delete ClipTypeAndDuration.tsx (#1899 #1900) 2026-09-14 20:44:30 +08:00
xiaoxia 50ad79b1c7 refactor: delete AdvancedEntries.tsx (#1899 #1900) 2026-09-14 20:44:28 +08:00
xiaoxia 32f858ce3e refactor: delete TitleSettingsSection.tsx (#1899 #1900) 2026-09-14 20:44:28 +08:00
xiaoxia c34e8f60cf refactor: delete SubtitleSettingsSection.tsx (#1899 #1900) 2026-09-14 20:44:27 +08:00
xiaoxia 7dc0c833e9 refactor: delete StatsSection.tsx (#1899 #1900) 2026-09-14 20:44:26 +08:00
xiaoxia 7e8eb2cd58 refactor: delete ClipDetailSection.tsx (#1899 #1900) 2026-09-14 20:44:25 +08:00
xiaoxia 7b9840a6a9 refactor: delete BgmSettingsSection.tsx (#1899 #1900) 2026-09-14 20:44:24 +08:00
xiaoxia 69a1b16dd5 refactor: delete useBgmSelector.ts (#1899 #1900) 2026-09-14 20:44:23 +08:00
xiaoxia 45bd7995b7 refactor: delete index.tsx (#1899 #1900) 2026-09-14 20:44:22 +08:00
xiaoxia 43f460d078 refactor: delete BgmMixConfig.tsx (#1899 #1900) 2026-09-14 20:44:21 +08:00
xiaoxia 3966409a37 refactor: delete BgmItem.tsx (#1899 #1900) 2026-09-14 20:44:20 +08:00
xiaoxia 69da356a9a refactor: delete WatermarkPanel.tsx (#1899 #1900) 2026-09-14 20:44:19 +08:00
xiaoxia 6204712fa3 refactor: delete index.tsx (#1899 #1900) 2026-09-14 20:44:18 +08:00
xiaoxia e706d8ef9c refactor: delete useTtsPanel.ts (#1899 #1900) 2026-09-14 20:44:17 +08:00
xiaoxia 7a03b629c6 refactor: delete constants.ts (#1899 #1900) 2026-09-14 20:44:15 +08:00
xiaoxia 831d7a9711 refactor: delete VoiceSelector.tsx (#1899 #1900) 2026-09-14 20:44:15 +08:00
xiaoxia be5a295b7f refactor: delete TtsSlider.tsx (#1899 #1900) 2026-09-14 20:44:07 +08:00
xiaoxia cc1e1ef9c5 refactor: delete TransitionSelector.tsx (#1899 #1900) 2026-09-14 20:44:06 +08:00
xiaoxia ac68a4def3 refactor: delete TopBar.tsx (#1899 #1900) 2026-09-14 20:44:04 +08:00
xiaoxia bee15fd6f9 refactor: delete TimelinePanel.tsx (#1899 #1900) 2026-09-14 20:44:04 +08:00
xiaoxia c8fa0e0f19 refactor: delete SubtitleStylePanel.tsx (#1899 #1900) 2026-09-14 20:44:03 +08:00
xiaoxia 7e1a52925e refactor: delete index.tsx (#1899 #1900) 2026-09-14 20:44:02 +08:00
xiaoxia db455e40ef refactor: delete useStickerPanel.ts (#1899 #1900) 2026-09-14 20:44:01 +08:00
xiaoxia 056fc4b257 refactor: delete constants.ts (#1899 #1900) 2026-09-14 20:43:59 +08:00
xiaoxia dcc100d646 refactor: delete StickerTabs.tsx (#1899 #1900) 2026-09-14 20:43:58 +08:00
xiaoxia 6c12e0d847 refactor: delete StickerPropsEditor.tsx (#1899 #1900) 2026-09-14 20:43:56 +08:00
xiaoxia 56e3ab92a6 refactor: delete StickerList.tsx (#1899 #1900) 2026-09-14 20:43:55 +08:00
xiaoxia 24c86dcabf refactor: delete StatusBar.tsx (#1899 #1900) 2026-09-14 20:43:54 +08:00
xiaoxia f8cce24fa4 refactor: delete SpeedPanel.tsx (#1899 #1900) 2026-09-14 20:43:50 +08:00
xiaoxia 3a2323a2af refactor: delete SaveModal.tsx (#1899 #1900) 2026-09-14 20:43:50 +08:00
xiaoxia fdafad2947 refactor: delete RightPanel.tsx (#1899 #1900) 2026-09-14 20:43:48 +08:00
xiaoxia 6c6b8aed75 refactor: delete PreviewPlayer.tsx (#1899 #1900) 2026-09-14 20:43:46 +08:00
xiaoxia c91514d7d8 refactor: delete PreviewPlayer.css (#1899 #1900) 2026-09-14 20:43:45 +08:00
xiaoxia 6d83087be1 refactor: delete index.tsx (#1899 #1900) 2026-09-14 20:43:44 +08:00
xiaoxia d7abc9ddd7 refactor: delete usePipConfigPanel.ts (#1899 #1900) 2026-09-14 20:43:42 +08:00
xiaoxia 1c9f7642fa refactor: delete constants.ts (#1899 #1900) 2026-09-14 20:43:41 +08:00
xiaoxia 8826629583 refactor: delete PipPreview.tsx (#1899 #1900) 2026-09-14 20:43:37 +08:00
xiaoxia 52d083dbe2 refactor: delete LayerList.tsx (#1899 #1900) 2026-09-14 20:43:37 +08:00
xiaoxia 5c33769930 refactor: delete LayerConfig.tsx (#1899 #1900) 2026-09-14 20:43:36 +08:00
xiaoxia d47803bf35 refactor: delete ModeBar.tsx (#1899 #1900) 2026-09-14 20:43:35 +08:00
xiaoxia 4442e46d1d refactor: delete MediaPanel.tsx (#1899 #1900) 2026-09-14 20:43:34 +08:00
xiaoxia 2c8038e5f4 refactor: delete IntroOutroPanel.tsx (#1899 #1900) 2026-09-14 20:43:33 +08:00
xiaoxia edc68067e9 refactor: delete GreenScreenPanel.tsx (#1899 #1900) 2026-09-14 20:43:33 +08:00
xiaoxia 7fd57e8fd1 refactor: delete FilterPanel.tsx (#1899 #1900) 2026-09-14 20:43:29 +08:00
xiaoxia d325572dd7 refactor: delete EditorDrawers.tsx (#1899 #1900) 2026-09-14 20:43:28 +08:00
xiaoxia 1c1b82ea66 refactor: delete EditorClipList.tsx (#1899 #1900) 2026-09-14 20:43:27 +08:00
xiaoxia 96ccac7fff refactor: delete EditingDrawers.tsx (#1899 #1900) 2026-09-14 20:43:27 +08:00
xiaoxia ac6b001ac3 refactor: delete ClipPropertiesPanel.tsx (#1899 #1900) 2026-09-14 20:43:23 +08:00
xiaoxia 42ef3d27e4 refactor: delete BgmSelector.tsx (#1899 #1900) 2026-09-14 20:43:23 +08:00
xiaoxia eb5aef7890 refactor: delete EditingPlanner.tsx (#1899 #1900) 2026-09-14 20:43:22 +08:00
xiaoxia 09f1189d65 refactor: delete EditingPlanner.css (#1899 #1900) 2026-09-14 20:43:21 +08:00
322 changed files with 2706 additions and 12128 deletions
-7
View File
@@ -196,10 +196,3 @@ DOUBAO_MODEL=doubao-seed-1-6-250615
DOUBAO_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
DOUBAO_TIMEOUT=30
DOUBAO_MAX_RETRIES=2
# ==================== 积分/会员系统 (#1895) ====================
# 积分扣点总开关:默认 false(对现有用户零影响)。
# P2 阶段各业务路由逐个接入 @points_gate 时,用
# `if settings.points_enabled: ...`
# 包裹扣点逻辑;所有路由接入完成并验证通过后再在 staging/prod 打开。
POINTS_ENABLED=false
-133
View File
@@ -1,133 +0,0 @@
"""add membership & points system
Revision ID: 076_membership_points
Revises: 075_add_sentence_timings
Create Date: 2026-09-15
"""
import sqlalchemy as sa
from sqlalchemy import text
from alembic import op
revision = "076_membership_points"
down_revision = "075_add_sentence_timings"
branch_labels = None
depends_on = None
def upgrade() -> None:
# 1. users 表新增字段
with op.batch_alter_table("users") as batch:
batch.add_column(
sa.Column("is_member", sa.Boolean(), nullable=False, server_default=sa.text("false")),
)
batch.add_column(
sa.Column("member_type", sa.String(20), nullable=True),
)
batch.add_column(
sa.Column("member_expires_at", sa.DateTime(), nullable=True),
)
batch.add_column(
sa.Column("points_balance", sa.Integer(), nullable=False, server_default=sa.text("0")),
)
# 2. points_accounts 积分账户表
op.create_table(
"points_accounts",
sa.Column("id", sa.String(36), primary_key=True),
sa.Column("user_id", sa.String(36), nullable=False, unique=True, index=True),
sa.Column("balance", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("total_earned", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("total_spent", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column(
"created_at",
sa.DateTime(),
nullable=False,
server_default=sa.text("NOW()"),
),
sa.Column(
"updated_at",
sa.DateTime(),
nullable=False,
server_default=sa.text("NOW()"),
),
)
# 3. points_transactions 积分流水表
op.create_table(
"points_transactions",
sa.Column("id", sa.String(36), primary_key=True),
sa.Column("user_id", sa.String(36), nullable=False, index=True),
sa.Column("account_id", sa.String(36), nullable=False, index=True),
sa.Column("type", sa.String(20), nullable=False, index=True),
sa.Column("source", sa.String(50), nullable=False, index=True),
sa.Column("amount", sa.Integer(), nullable=False),
sa.Column("balance_after", sa.Integer(), nullable=False),
sa.Column("description", sa.String(255), nullable=False, server_default=""),
sa.Column("ref_id", sa.String(100), nullable=False, server_default=""),
sa.Column(
"created_at",
sa.DateTime(),
nullable=False,
server_default=sa.text("NOW()"),
),
)
# 4. points_orders 积分/会员订单表
op.create_table(
"points_orders",
sa.Column("id", sa.String(36), primary_key=True),
sa.Column("user_id", sa.String(36), nullable=False, index=True),
sa.Column("order_type", sa.String(20), nullable=False),
sa.Column("product_code", sa.String(50), nullable=False),
sa.Column("amount_cents", sa.Integer(), nullable=False),
sa.Column("original_amount_cents", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("discount", sa.Float(), nullable=False, server_default=sa.text("1.0")),
sa.Column("points_amount", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column("status", sa.String(20), nullable=False, server_default="pending", index=True),
sa.Column("payment_method", sa.String(50), nullable=True),
sa.Column("payment_id", sa.String(100), nullable=True),
sa.Column("paid_at", sa.DateTime(), nullable=True),
sa.Column(
"created_at",
sa.DateTime(),
nullable=False,
server_default=sa.text("NOW()"),
),
)
# 5. daily_usage_records 每日使用记录表
op.create_table(
"daily_usage_records",
sa.Column("id", sa.String(36), primary_key=True),
sa.Column("user_id", sa.String(36), nullable=False, index=True),
sa.Column("usage_date", sa.DateTime(), nullable=False),
sa.Column("usage_type", sa.String(50), nullable=False, server_default="free_clip"),
sa.Column("count", sa.Integer(), nullable=False, server_default=sa.text("0")),
sa.Column(
"updated_at",
sa.DateTime(),
nullable=False,
server_default=sa.text("NOW()"),
),
sa.UniqueConstraint(
"user_id",
"usage_date",
"usage_type",
name="uq_daily_usage_user_date_type",
),
)
def downgrade() -> None:
op.drop_table("daily_usage_records")
op.drop_table("points_orders")
op.drop_table("points_transactions")
op.drop_table("points_accounts")
with op.batch_alter_table("users") as batch:
batch.drop_column("points_balance")
batch.drop_column("member_expires_at")
batch.drop_column("member_type")
batch.drop_column("is_member")
-22
View File
@@ -6,7 +6,6 @@ from app.api.routes.assets import router as assets_router
from app.api.routes.auth import router as auth_router
from app.api.routes.chunked_upload import router as chunked_upload_router
from app.api.routes.classification_jobs import router as classification_jobs_router
from app.api.routes.clips_standalone import router as clips_standalone_router
from app.api.routes.cover_templates import router as cover_templates_router
from app.api.routes.duplication import router as duplication_router
from app.api.routes.feature_flags import router as feature_flags_router
@@ -18,10 +17,8 @@ from app.api.routes.health import router as health_check_router
from app.api.routes.ingest_jobs import router as ingest_jobs_router
from app.api.routes.internal_render import router as internal_render_router
from app.api.routes.lipsync import router as lipsync_router
from app.api.routes.points import points_router, usage_router
from app.api.routes.projects import router as projects_router
from app.api.routes.scripts import router as scripts_router
from app.api.routes.scripts_ai import router as scripts_ai_router
from app.api.routes.share import router as share_router
from app.api.routes.subscription import router as subscription_router
from app.api.routes.tags import router as tags_router
@@ -159,10 +156,6 @@ api_router.include_router(
prefix="/templates",
tags=["Template"],
)
api_router.include_router(
clips_standalone_router,
tags=["Clips"],
)
api_router.include_router(
templates_editor_router,
prefix="/templates/{template_id}/editor",
@@ -191,23 +184,8 @@ api_router.include_router(
prefix="/scripts",
tags=["ScriptLibrary"],
)
api_router.include_router(
scripts_ai_router,
prefix="/scripts",
tags=["ScriptLibrary AI"],
)
api_router.include_router(
ai_avatar_render_router,
prefix="/ai-avatar/render",
tags=["AI Avatar Render"],
)
api_router.include_router(
points_router,
prefix="/points",
tags=["Points"],
)
api_router.include_router(
usage_router,
prefix="/usage",
tags=["Usage"],
)
@@ -1,91 +0,0 @@
"""默认模板兜底共享逻辑(P0 #1922).
提供 get_or_create_default_template_id(db, user_id) 共享函数,
供 templates.py 列表查询、clips_standalone.py 独立端点、dependencies.py
resolve_draft_plan_id 三处复用,避免三处各写一套兜底逻辑产生分叉。
根因:PR#1918 清理模板管理 API 时误删了 GET /templates 自动创建默认模板
兜底,前端 PR#1913 去掉空 tid 拦截后首次进入生成页拼出
/templates//editor/clips/from-assets(双斜杠)→ FastAPI 404,阻断新用户首次
生成。
"""
from __future__ import annotations
import logging
from typing import Optional
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
def get_or_create_default_template_id(db: Session, user_id: str) -> Optional[str]:
"""获取或自动创建默认配音模板的 id。
判定逻辑(不做异常降级,只有确实创建失败时才回滚重查):
1. 查用户名下 is_active=True 且有 TemplateClipConfig 的模板 → 返回其 id;
2. 无则调用 CreateTemplateUseCase 创建一条默认 voice_over 模板;
3. 创建异常时 rollback 再重查一次(防并发唯一键冲突),重查仍无返回 None。
"""
from packages.adapters.sqlalchemy_impl.models import (
TemplateClipConfigModel,
TemplateModel,
)
from packages.adapters.sqlalchemy_impl.template_repository import (
SQLAlchemyTemplateRepository,
)
from packages.application.template.commands import (
CreateTemplateCommand,
SegmentCommand,
)
from packages.application.template.use_cases import CreateTemplateUseCase
existing = (
db.query(TemplateModel)
.filter(TemplateModel.user_id == user_id, TemplateModel.is_active.is_(True))
.order_by(TemplateModel.created_at.asc())
.first()
)
if existing is not None:
has_seg = (
db.query(TemplateClipConfigModel.id).filter(TemplateClipConfigModel.template_id == existing.id).first()
)
if has_seg:
return existing.id
try:
repo = SQLAlchemyTemplateRepository(db)
cmd = CreateTemplateCommand(
user_id=user_id,
name="默认配音模板",
mode="voice_over",
category="default",
tags=[],
title_config={},
subtitle_config={},
bgm_config={},
estimated_duration=0.0,
segments=[SegmentCommand(segment_order=0, duration_min=1.0, duration_max=30.0)],
)
tpl = CreateTemplateUseCase(repo).execute(cmd)
db.commit()
logger.info("auto-created default voice_over template: id=%s user=%s", tpl.id, user_id)
return tpl.id
except Exception:
db.rollback()
# 重查:可能并发请求已建好
existing = (
db.query(TemplateModel)
.filter(TemplateModel.user_id == user_id, TemplateModel.is_active.is_(True))
.order_by(TemplateModel.created_at.asc())
.first()
)
if existing is not None:
has_seg = (
db.query(TemplateClipConfigModel.id).filter(TemplateClipConfigModel.template_id == existing.id).first()
)
if has_seg:
return existing.id
logger.exception("failed to auto-create default template user=%s", user_id)
return None
+2 -2
View File
@@ -1,6 +1,6 @@
"""路由层共享辅助函数 — 消除跨文件重复定义。"""
from datetime import UTC, datetime
from datetime import datetime, timezone
from typing import Any
from fastapi import HTTPException, status
@@ -138,4 +138,4 @@ def format_utc_datetime(dt: datetime | None) -> str:
return dt
if dt.tzinfo is None:
return dt.isoformat() + "Z"
return dt.astimezone(UTC).isoformat().replace("+00:00", "Z")
return dt.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
+6 -6
View File
@@ -5,7 +5,7 @@
from __future__ import annotations
from typing import Literal
from typing import List, Literal
from app.services.ai_service import TITLE_STYLES, generate_smart_titles, semantic_match_assets
from fastapi import APIRouter
@@ -31,7 +31,7 @@ class GenerateTitlesRequest(BaseModel):
class GenerateTitlesResponse(BaseModel):
"""智能标题生成响应."""
titles: list[str] = Field(..., description="生成的标题列表")
titles: List[str] = Field(..., description="生成的标题列表")
style: str = Field(..., description="实际使用的风格")
source: str = Field(..., description="来源:doubao 或 fallback")
description: str = Field(..., description="原始描述")
@@ -53,7 +53,7 @@ class AssetMatchItem(BaseModel):
id: str = Field(..., description="素材ID")
name: str = Field(default="", description="素材名称")
tags: list[str] = Field(default_factory=list, description="标签列表")
tags: List[str] = Field(default_factory=list, description="标签列表")
description: str = Field(default="", description="素材描述")
@@ -61,7 +61,7 @@ class SemanticMatchRequest(BaseModel):
"""语义匹配请求."""
description: str = Field(..., min_length=1, max_length=500, description="目标视频内容描述")
assets: list[AssetMatchItem] = Field(..., min_length=1, max_length=100, description="待匹配素材列表")
assets: List[AssetMatchItem] = Field(..., min_length=1, max_length=100, description="待匹配素材列表")
top_k: int = Field(default=0, ge=0, le=100, description="返回前K个,0返回全部")
@@ -75,7 +75,7 @@ class SemanticMatchResultItem(AssetMatchItem):
class SemanticMatchResponse(BaseModel):
"""语义匹配响应."""
matches: list[SemanticMatchResultItem] = Field(..., description="按匹配度降序排列的素材列表")
matches: List[SemanticMatchResultItem] = Field(..., description="按匹配度降序排列的素材列表")
source: str = Field(..., description="来源:doubao / fallback")
description: str = Field(..., description="原始描述")
total: int = Field(..., description="输入素材总数")
@@ -99,7 +99,7 @@ def generate_titles(request: GenerateTitlesRequest):
return GenerateTitlesResponse(**result)
@router.get("/titles/styles", response_model=list[TitleStyleInfo])
@router.get("/titles/styles", response_model=List[TitleStyleInfo])
def list_title_styles():
"""获取支持的标题风格列表."""
return [
+4 -4
View File
@@ -11,7 +11,7 @@
from __future__ import annotations
import logging
from datetime import UTC, datetime
from datetime import datetime, timezone
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_db_session
@@ -82,7 +82,7 @@ def create_render_job(
logger.exception("Celery 任务投递失败(创建): job_id=%s err=%s", job.id, exc)
job.status = "failed"
job.error_message = f"任务提交失败:{exc}"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
svc.db.commit()
svc.db.refresh(job)
return AiAvatarRenderJobResponse.model_validate(job)
@@ -183,7 +183,7 @@ def retry_render_job(
logger.exception("Celery 任务投递失败(重试): job_id=%s err=%s", job.id, exc)
job.status = "failed"
job.error_message = f"任务提交失败:{exc}"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
svc.db.commit()
svc.db.refresh(job)
return AiAvatarRenderJobResponse.model_validate(job)
@@ -246,7 +246,7 @@ def generate_render_smart_cover(
"url": cover_url,
}
job.output_cover_url = cover_url
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
db.commit()
except Exception as exc:
logger.warning("更新 render_job 封面字段失败(不影响返回): job_id=%s err=%s", job_id, exc)
+2 -2
View File
@@ -1,5 +1,5 @@
import logging
from typing import Any, Optional
from typing import Any, List, Optional
from app.api.routes._helpers import check_project_access, format_utc_datetime
from app.auth import AuthenticatedUser, get_current_user
@@ -390,7 +390,7 @@ def update_asset_review_status(
return _to_asset_response(updated)
@router.post("/batch", response_model=list[AssetResponse])
@router.post("/batch", response_model=List[AssetResponse])
def batch_get_assets(
request: BatchGetRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
+6 -6
View File
@@ -8,7 +8,7 @@ import json
import logging
import shutil
import tempfile
from datetime import UTC, datetime, timedelta
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
from uuid import uuid4
@@ -156,7 +156,7 @@ def _cleanup_expired_uploads() -> int:
if not CHUNK_STORAGE_ROOT.exists():
return 0
now = datetime.now(UTC)
now = datetime.now(timezone.utc)
cleaned = 0
for meta_file in CHUNK_STORAGE_ROOT.glob("*.meta.json"):
@@ -166,7 +166,7 @@ def _cleanup_expired_uploads() -> int:
expires_at = datetime.fromisoformat(meta["expires_at"])
if expires_at.tzinfo is None:
expires_at = expires_at.replace(tzinfo=UTC)
expires_at = expires_at.replace(tzinfo=timezone.utc)
# Only cleanup uploads that are not actively being uploaded
if expires_at < now and meta.get("status") != "uploading":
@@ -226,7 +226,7 @@ async def init_chunked_upload(
# Generate upload ID
upload_id = uuid4().hex
now = datetime.now(UTC)
now = datetime.now(timezone.utc)
expires_at = now + timedelta(hours=CHUNK_EXPIRY_HOURS)
# Create chunk directory
@@ -421,9 +421,9 @@ async def upload_chunk(
# Check expiry
expires_at = datetime.fromisoformat(meta["expires_at"])
if expires_at.tzinfo is None:
expires_at = expires_at.replace(tzinfo=UTC)
expires_at = expires_at.replace(tzinfo=timezone.utc)
if expires_at < datetime.now(UTC):
if expires_at < datetime.now(timezone.utc):
raise HTTPException(status_code=status.HTTP_410_GONE, detail="Upload has expired")
# Validate chunk index
@@ -1,90 +0,0 @@
"""独立的从素材创建片段端点(不依赖 template_id 路径参数).
POST /api/v1/clips/from-assets
- 与 /api/v1/templates/{template_id}/editor/clips/from-assets 功能一致
- 区别:template_id 从 body 传入(可选),为空时后端自动创建/查找默认模板
- 解决前端首次加载时 templateId 为空导致双斜杠 404 的问题(P0 #1922)
- 内部复用 resolve_draft_plan_id 和 create_clips_from_assets_editor 的核心逻辑
"""
from __future__ import annotations
import logging
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_asset_repository, get_db_session
from app.services.edit_plan_service import EditPlanService
from app.services.edit_template_service import EditTemplateService
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.asset_repository import SQLAlchemyAssetRepository
from ._default_template import get_or_create_default_template_id
from .templates_editor.clips import create_clips_from_assets_editor
from .templates_editor.dependencies import resolve_draft_plan_id
from .templates_editor.schemas import ClipsFromAssetsRequest, ClipsFromAssetsResponse
logger = logging.getLogger(__name__)
router = APIRouter(tags=["Clips"])
class StandaloneClipsRequest(ClipsFromAssetsRequest):
"""扩展请求:template_id 可选(不传则后端自动兜底默认模板)。"""
template_id: str | None = None
def _get_editor_services_direct(db: Session) -> tuple[EditTemplateService, EditPlanService]:
"""直接构造服务实例(非 Depends 版本,供独立端点内部调用)。"""
return EditTemplateService(db), EditPlanService(db)
@router.post("/clips/from-assets", response_model=ClipsFromAssetsResponse)
def create_clips_from_assets(
body: StandaloneClipsRequest,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db_session),
current_user: AuthenticatedUser = Depends(get_current_user),
asset_repo: SQLAlchemyAssetRepository = Depends(get_asset_repository),
) -> ClipsFromAssetsResponse:
"""从素材批量创建片段(template_id 可选,为空自动兜底)。"""
user_id = str(current_user.user.id)
services = _get_editor_services_direct(db)
# 1. 解析/兜底 template_id,拿到 plan_id
template_id = (body.template_id or "").strip()
if not template_id:
template_id = get_or_create_default_template_id(db, user_id)
if not template_id:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="无法自动创建默认模板,请刷新页面重试",
)
plan_id = resolve_draft_plan_id(
template_id=template_id,
services=services,
current_user=current_user,
db=db,
auto_create_default=False, # 上面已兜底过
)
# 2. 构造标准化请求(去除独立端扩展字段),复用原端点核心逻辑
core_body = ClipsFromAssetsRequest(
asset_ids=body.asset_ids,
clip_type=body.clip_type,
clip_count=body.clip_count,
required_clips_count=body.required_clips_count,
)
# 3. 直接调用原端点函数(此时所有 Depends 依赖已手动传入)
return create_clips_from_assets_editor(
template_id=template_id,
body=core_body,
background_tasks=background_tasks,
plan_id=plan_id,
services=services,
asset_repo=asset_repo,
db=db,
current_user=current_user,
)
+2 -2
View File
@@ -11,7 +11,7 @@ from __future__ import annotations
import ipaddress
import logging
import re
from typing import Any, Optional
from typing import Any, List, Optional
from urllib.parse import urlparse
from app.auth import AuthenticatedUser, get_current_user
@@ -41,7 +41,7 @@ router = APIRouter(tags=["Generation"])
class GenerateCoverRequest(BaseModel):
"""AI 封面生成请求体"""
asset_ids: list[str] = Field(default_factory=list, description="素材 ID 列表(确定视频来源)")
asset_ids: List[str] = Field(default_factory=list, description="素材 ID 列表(确定视频来源)")
cover_type: str = Field(
default="ai_frame",
description="封面类型: ai_frame / manual / upload / ai_regenerate",
@@ -30,74 +30,6 @@ logger = logging.getLogger(__name__)
router = APIRouter()
def _get_or_create_default_template_id(db: Session, user_id: str) -> str | None:
"""为用户查找一个有效模板;若不存在则自动创建默认配音模板。
前端 #1911 删除了模板选择 UI,当调用方未传 template_id/source_edit_plan_id
时(如剪辑页首次进入直接选片),后端兜底查找/创建默认模板,避免 400。
Returns:
template_id(字符串);失败时返回 None。
"""
from packages.adapters.sqlalchemy_impl.models import TemplateClipConfigModel, TemplateModel
from packages.adapters.sqlalchemy_impl.template_repository import SQLAlchemyTemplateRepository
from packages.application.template.commands import CreateTemplateCommand, SegmentCommand
from packages.application.template.use_cases import CreateTemplateUseCase
# 1. 先查已有有效模板(is_active=True 且存在片段配置)
existing = (
db.query(TemplateModel)
.filter(
TemplateModel.user_id == user_id,
TemplateModel.is_active.is_(True),
)
.order_by(TemplateModel.created_at.asc())
.first()
)
if existing is not None:
# 验证该模板是否有片段配置;若没有继续尝试创建默认
has_seg = (
db.query(TemplateClipConfigModel.id).filter(TemplateClipConfigModel.template_id == existing.id).first()
)
if has_seg:
return existing.id
# 2. 无有效模板 → 自动创建默认配音模板
try:
repo = SQLAlchemyTemplateRepository(db)
cmd = CreateTemplateCommand(
user_id=user_id,
name="默认配音模板",
mode="voice_over",
category="default",
tags=[],
title_config={},
subtitle_config={},
bgm_config={},
estimated_duration=0.0,
segments=[
SegmentCommand(
segment_order=0,
duration_min=1.0,
duration_max=30.0,
material_type=None,
),
],
)
use_case = CreateTemplateUseCase(repo)
tpl = use_case.execute(cmd)
logger.info(
"[variant-plans] 自动创建默认模板: user=%s tpl=%s",
user_id,
tpl.id,
)
return tpl.id
except Exception:
logger.exception("[variant-plans] 自动创建默认模板失败: user=%s", user_id)
return None
class VariantPlanRequest(BaseModel):
"""轻量选片请求体(与前端 variantPlans.ts 契约一致)。"""
@@ -111,8 +43,8 @@ class VariantPlanRequest(BaseModel):
@model_validator(mode="after")
def _validate(self) -> "VariantPlanRequest":
# 不再强制要求 template_id / source_edit_plan_id:
# 后端在路由内会自动查找/创建默认模板兜底(#1911 后前端不再显式选模板)。
if not self.template_id.strip() and not self.source_edit_plan_id.strip():
raise ValueError("template_id 与 source_edit_plan_id 至少需要提供一个")
try:
resolve_variant_voice_ids(
count=self.count,
@@ -162,15 +94,8 @@ def create_variant_plans(
from app.services.generation_common import resolve_latest_plan_by_template
source_plan_id = request.source_edit_plan_id.strip()
template_id = request.template_id.strip()
# P0 兜底:前端 #1911 已删除模板选择 UI,调用方可能不传 template_id;
# 此时自动为该用户查找/创建默认模板。
if not source_plan_id and not template_id:
template_id = _get_or_create_default_template_id(db, user_id) or ""
if not source_plan_id and template_id:
source_plan_id = resolve_latest_plan_by_template(db, template_id=template_id, user_id=user_id) or ""
if not source_plan_id and request.template_id.strip():
source_plan_id = resolve_latest_plan_by_template(db, template_id=request.template_id, user_id=user_id) or ""
if not source_plan_id:
raise HTTPException(
+3 -3
View File
@@ -1,4 +1,4 @@
from datetime import UTC, datetime
from datetime import datetime, timezone
import psycopg
import redis
@@ -13,7 +13,7 @@ router = APIRouter(tags=["Health"])
async def health_check():
return {
"status": "healthy",
"timestamp": datetime.now(UTC).isoformat(),
"timestamp": datetime.now(timezone.utc).isoformat(),
"version": settings.APP_VERSION,
}
@@ -33,7 +33,7 @@ async def startup_check():
all_ready = all(check["status"] == "healthy" for check in checks.values())
response = {
"status": "started" if all_ready else "starting",
"timestamp": datetime.now(UTC).isoformat(),
"timestamp": datetime.now(timezone.utc).isoformat(),
"checks": checks,
}
if not all_ready:
+1 -17
View File
@@ -12,7 +12,6 @@
from __future__ import annotations
import logging
from datetime import UTC
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import (
@@ -194,22 +193,7 @@ def get_lipsync_job(
raise HTTPException(status_code=404, detail="任务不存在")
if job.status not in ("completed", "failed"):
# 三层防御 ①:如果距上次更新超过 30 秒,同步刷新一次(避免 background task
# 静默失败导致前端永远看到 running);否则挂后台异步刷新(避免阻塞轮询)。
from datetime import datetime as _dt
_now = _dt.now(UTC)
_stale = job.updated_at is None or (_now - job.updated_at).total_seconds() > 30
if _stale:
try:
refreshed = svc.refresh_job_status(job_id, current_user.user.id)
if refreshed is not None:
job = refreshed
except Exception as exc: # noqa: BLE001
logger.error("同步刷新对口型状态失败 job_id=%s err=%s", job_id, exc, exc_info=True)
background.add_task(svc.refresh_job_status, job_id, current_user.user.id)
else:
background.add_task(svc.refresh_job_status, job_id, current_user.user.id)
background.add_task(svc.refresh_job_status, job_id, current_user.user.id)
return job
-321
View File
@@ -1,321 +0,0 @@
"""积分 & 会员 API 路由 (#1895)
导出两个 router:
- points_router: 积分相关路由,前缀 /points
- usage_router: 每日额度路由,前缀 /usage
"""
from __future__ import annotations
import logging
from datetime import datetime
from typing import Optional
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_db_session
from app.schemas.points import (
DailyUsageResponse,
MembershipStatusResponse,
PointRuleItem,
PointsBalanceResponse,
PointsCheckRequest,
PointsCheckResponse,
PointsDeductRequest,
PointsOrderResponse,
PointsPackageItem,
PointsPackagesResponse,
PointsRechargeRequest,
PointsRefundRequest,
PointsRulesResponse,
PointsTransactionsResponse,
SimpleMessageResponse,
)
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from packages.domain.points_rules import (
FREE_USER_MULTIPLIER,
MEMBER_DISCOUNT,
POINTS_PACKAGES,
POINTS_SCENES,
calculate_points_cost,
)
from packages.domain.points_service import PointsService
logger = logging.getLogger(__name__)
# ── 两个 router ──
points_router = APIRouter()
usage_router = APIRouter()
def _get_service() -> PointsService:
return PointsService()
def _is_member(user: AuthenticatedUser) -> bool:
"""判断用户是否为付费会员。"""
return getattr(user.user, "is_member", False)
def _member_type(user: AuthenticatedUser) -> str | None:
return getattr(user.user, "member_type", None)
# ════════════════════════════════════════════════════════════════
# 积分相关路由 (prefix=/points)
# ════════════════════════════════════════════════════════════════
@points_router.get("/balance", response_model=PointsBalanceResponse)
def get_balance(
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""查询当前用户积分余额 + 会员状态。"""
svc = _get_service()
account = svc.get_or_create_account(current_user.user.id, db)
return PointsBalanceResponse(
balance=account["balance"],
total_earned=account["total_earned"],
total_spent=account["total_spent"],
is_member=_is_member(current_user),
member_type=_member_type(current_user),
member_expires_at=getattr(current_user.user, "member_expires_at", None),
)
@points_router.get("/transactions", response_model=PointsTransactionsResponse)
def get_transactions(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
type: Optional[str] = Query(None, description="筛选类型: add/deduct"),
source: Optional[str] = Query(None, description="筛选来源场景"),
start_date: Optional[datetime] = Query(None),
end_date: Optional[datetime] = Query(None),
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""查询积分流水(分页+筛选)。"""
svc = _get_service()
result = svc.get_transactions(
user_id=current_user.user.id,
db=db,
page=page,
page_size=page_size,
type_filter=type,
source_filter=source,
start_date=start_date,
end_date=end_date,
)
return PointsTransactionsResponse(**result)
@points_router.get("/rules", response_model=PointsRulesResponse)
def get_rules(
_current_user: AuthenticatedUser = Depends(get_current_user),
):
"""查询所有积分消耗规则。"""
rules = []
for scene_key, scene_data in POINTS_SCENES.items():
rules.append(
PointRuleItem(
scene_key=scene_key,
name=scene_data["name"],
base_points=scene_data["base_points"],
unit=scene_data["unit"],
extra_per_30s=scene_data.get("extra_per_30s"),
)
)
return PointsRulesResponse(
rules=rules,
free_user_multiplier=FREE_USER_MULTIPLIER,
)
@points_router.get("/packages", response_model=PointsPackagesResponse)
def get_packages(
current_user: AuthenticatedUser = Depends(get_current_user),
):
"""查询可购买的积分包列表。"""
packages = []
for code, pkg in POINTS_PACKAGES.items():
unit_price = f"¥{pkg['price_cents'] / 100 / pkg['points']:.3f}/积分"
packages.append(
PointsPackageItem(
code=code,
name=pkg["name"],
points=pkg["points"],
price_cents=pkg["price_cents"],
unit_price=unit_price,
)
)
mt = _member_type(current_user)
discount = MEMBER_DISCOUNT.get(mt) if mt else None
return PointsPackagesResponse(packages=packages, user_discount=discount)
@points_router.post("/check", response_model=PointsCheckResponse)
def check_points(
body: PointsCheckRequest,
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""消费前检查余额是否足够。"""
is_mem = _is_member(current_user)
mt = _member_type(current_user)
# 混剪场景先检查免费额度
is_free_quota = False
if body.scene_key == "ai_video" and not is_mem:
svc = _get_service()
if svc.check_daily_free_clip(current_user.user.id, db):
is_free_quota = True
required = calculate_points_cost(
body.scene_key,
is_mem,
quantity=body.quantity or 1,
duration_minutes=body.duration_minutes or 0,
member_type=mt,
)
svc = _get_service()
account = svc.get_or_create_account(current_user.user.id, db)
balance = account["balance"]
return PointsCheckResponse(
allowed=is_free_quota or balance >= required,
required_points=required,
current_balance=balance,
remaining_after=balance - required,
is_free_quota=is_free_quota,
)
@points_router.post("/deduct", response_model=SimpleMessageResponse)
def deduct_points(
body: PointsDeductRequest,
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""积分扣减(内部服务调用)。"""
svc = _get_service()
result = svc.deduct_points(
user_id=current_user.user.id,
amount=body.amount,
source=body.scene_key,
db=db,
description=body.description or "",
ref_id=body.ref_id or "",
)
if not result["success"]:
raise HTTPException(
status_code=402,
detail={
"code": "INSUFFICIENT_POINTS",
"message": f"积分不足,需要 {body.amount},余额 {result['balance']}",
},
)
return SimpleMessageResponse(
success=True,
message=f"扣减 {body.amount} 积分成功",
data={"transaction_id": result["transaction_id"], "balance": result["balance"]},
)
@points_router.post("/refund", response_model=SimpleMessageResponse)
def refund_points(
body: PointsRefundRequest,
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""积分退还(内部服务调用)。"""
from packages.adapters.sqlalchemy_impl.models import PointsTransactionModel
txn = (
db.query(PointsTransactionModel)
.filter(PointsTransactionModel.id == body.transaction_id)
.first()
)
if txn is None:
raise HTTPException(status_code=404, detail="交易记录不存在")
if txn.user_id != current_user.user.id:
raise HTTPException(status_code=403, detail="无权退还他人积分")
svc = _get_service()
result = svc.refund_points(
user_id=current_user.user.id,
amount=txn.amount,
source=txn.source,
db=db,
ref_id=body.transaction_id,
description=body.reason or f"退还: {txn.description}",
)
if not result["success"]:
raise HTTPException(status_code=500, detail="退还失败")
return SimpleMessageResponse(
success=True,
message=f"退还 {txn.amount} 积分成功",
data={"transaction_id": result["transaction_id"], "balance": result["balance"]},
)
@points_router.post("/recharge", response_model=PointsOrderResponse)
def create_recharge_order(
body: PointsRechargeRequest,
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""创建积分充值订单。"""
svc = _get_service()
try:
order = svc.create_order(
user_id=current_user.user.id,
order_type="points",
product_code=body.package_id,
db=db,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from None
return PointsOrderResponse(**order)
@points_router.get("/subscription/membership", response_model=MembershipStatusResponse)
def get_membership_status(
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""获取当前用户会员状态(聚合信息)。"""
svc = _get_service()
account = svc.get_or_create_account(current_user.user.id, db)
is_mem = _is_member(current_user)
max_resolution = "1080p" if is_mem else "720p"
return MembershipStatusResponse(
is_member=is_mem,
member_type=_member_type(current_user),
member_expires_at=getattr(current_user.user, "member_expires_at", None),
points_balance=account["balance"],
max_resolution=max_resolution,
)
# ════════════════════════════════════════════════════════════════
# 每日额度路由 (prefix=/usage)
# ════════════════════════════════════════════════════════════════
@usage_router.get("/daily", response_model=DailyUsageResponse)
def get_daily_usage(
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
):
"""查询今日免费混剪额度使用情况。"""
svc = _get_service()
result = svc.get_daily_usage(current_user.user.id, db)
return DailyUsageResponse(**result)
# 为了向后兼容,也导出一个不带后缀的 router(方便旧引用)
router = points_router
-234
View File
@@ -1,234 +0,0 @@
"""Scripts AI 能力路由 — Issue #1893.
三个 AI 工具接口(均挂载在 /api/v1/scripts 前缀下):
- POST /extract-from-douyin 从抖音视频提取文案(yt-dlp 下载 + ASR 转写)
- POST /ai-rewrite AI 文案改写(复用豆包 LLM)
- POST /ai-generate-titles AI 标题生成(复用 generate_smart_titles)
"""
from __future__ import annotations
import logging
import re
import tempfile
from app.auth import AuthenticatedUser, get_current_user
from app.schemas.scripts_ai import (
AiGenerateTitlesRequest,
AiGenerateTitlesResponse,
AiRewriteRequest,
AiRewriteResponse,
ExtractFromDouyinRequest,
ExtractFromDouyinResponse,
)
from app.services.script_asr_service import (
ASRNotConfiguredError,
ASRTranscriptionError,
transcribe_to_text,
)
from fastapi import APIRouter, Depends, HTTPException, status
from packages.shared.ai_client import get_doubao_client
logger = logging.getLogger(__name__)
router = APIRouter()
# 抖音 URL 校验:支持短链 v.douyin.com 和长链 www.douyin.com/video/
_DOUYIN_URL_RE = re.compile(
r"^(https?://)?(v\.douyin\.com/\S+|www\.douyin\.com/video/\S+)$",
re.IGNORECASE,
)
def _validate_douyin_url(url: str) -> None:
"""校验抖音 URL 格式,不合法时抛 HTTPException(400)."""
if not url or not url.strip():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="链接不能为空",
)
if not _DOUYIN_URL_RE.match(url.strip()):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无效的抖音链接,仅支持 v.douyin.com 短链或 www.douyin.com/video/ 长链",
)
# ── 1. 从抖音视频提取文案 ─────────────────────────────────────────────────────
@router.post(
"/extract-from-douyin",
response_model=ExtractFromDouyinResponse,
)
def extract_from_douyin(
request: ExtractFromDouyinRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
) -> ExtractFromDouyinResponse:
"""从抖音视频下载无水印视频并通过 ASR 提取文案."""
source_url = request.url.strip()
_validate_douyin_url(source_url)
# 确保 URL 有 scheme(yt-dlp 需要完整 URL)
url_for_download = source_url
if not re.match(r"^https?://", url_for_download, re.IGNORECASE):
url_for_download = "https://" + url_for_download
# 使用临时目录下载视频,退出时自动清理
try:
with tempfile.TemporaryDirectory(prefix="douyin_extract_") as temp_dir:
import yt_dlp
ydl_opts = {
"format": "best[ext=mp4]/best",
"outtmpl": f"{temp_dir}/%(id)s.%(ext)s",
"quiet": True,
"no_warnings": True,
"noplaylist": True,
}
try:
ydl = yt_dlp.YoutubeDL(ydl_opts)
info = ydl.extract_info(url_for_download, download=True)
except Exception as exc:
logger.error("抖音视频下载失败: url=%s error=%s", source_url, exc)
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"视频下载失败: {exc}",
) from exc
if info is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="无法解析该抖音链接",
)
video_path = ydl.prepare_filename(info)
duration = float(info.get("duration") or 0)
# ASR 转写
try:
text = transcribe_to_text(video_path)
except ASRNotConfiguredError as exc:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=str(exc),
) from exc
except ASRTranscriptionError as exc:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=str(exc),
) from exc
except HTTPException:
raise
return ExtractFromDouyinResponse(
text=text,
duration_seconds=duration,
source_url=source_url,
)
# ── 2. AI 文案改写 ───────────────────────────────────────────────────────────
@router.post(
"/ai-rewrite",
response_model=AiRewriteResponse,
)
def ai_rewrite(
request: AiRewriteRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
) -> AiRewriteResponse:
"""使用豆包大模型改写文案."""
content = (request.content or "").strip()
if not content:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="文案内容不能为空",
)
style = request.style or "口语化"
client = get_doubao_client()
if not client.is_available:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail="AI 服务不可用,请联系管理员配置豆包大模型 API Key",
)
system_prompt = (
"你是一个专业的短视频文案改写专家。请对以下文案进行改写,"
"要求:保留原意、口语化、适合短视频口播、调整语序避免查重。"
)
if style:
system_prompt += f"\n风格要求:{style}"
user_prompt = f"请改写以下文案:\n\n{content}"
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
]
try:
rewritten = client.chat_completion(
messages=messages,
temperature=0.8,
max_tokens=2048,
)
except Exception as exc:
logger.error("AI 改写调用失败: %s", exc)
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"AI 改写失败: {exc}",
) from exc
if not rewritten:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail="AI 改写未返回有效结果",
)
return AiRewriteResponse(
original=content,
rewritten=rewritten.strip(),
style=style,
)
# ── 3. AI 标题生成 ───────────────────────────────────────────────────────────
@router.post(
"/ai-generate-titles",
response_model=AiGenerateTitlesResponse,
)
def ai_generate_titles(
request: AiGenerateTitlesRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
) -> AiGenerateTitlesResponse:
"""使用现有 generate_smart_titles 生成标题."""
content = (request.content or "").strip()
if not content:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="文案内容不能为空",
)
# count 限制在 1-5(Pydantic ge=1 le=5 已校验),但为兼容直接调用场景截断
count = max(1, min(5, request.count))
from app.services.ai_service import generate_smart_titles
result = generate_smart_titles(
description=content,
style="viral",
count=count,
)
titles = result.get("titles", [])[:count]
return AiGenerateTitlesResponse(titles=titles)
+6 -5
View File
@@ -4,7 +4,8 @@ from __future__ import annotations
import logging
from dataclasses import replace
from datetime import UTC, datetime
from datetime import datetime, timezone
from typing import List
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_user_repository
@@ -66,7 +67,7 @@ def _get_plan_price(plan_id: str, billing_cycle: str) -> float:
def _build_subscription_info(user: AuthenticatedUser) -> SubscriptionInfo:
"""构建订阅信息响应"""
now = datetime.now(UTC)
now = datetime.now(timezone.utc)
if user.user.subscription_expires_at:
period_end = user.user.subscription_expires_at.isoformat()
period_start = now.isoformat()
@@ -99,10 +100,10 @@ async def get_current_subscription(
return _build_subscription_info(current_user)
@router.get("/billing-records", response_model=list[BillingRecord])
@router.get("/billing-records", response_model=List[BillingRecord])
async def get_billing_records(
current_user: AuthenticatedUser = Depends(get_current_user),
) -> list[BillingRecord]:
) -> List[BillingRecord]:
"""获取账单记录列表"""
from packages.adapters.sqlalchemy_impl.billing_repository import SQLAlchemyBillingRepository
from packages.adapters.sqlalchemy_impl.session import SessionLocal
@@ -250,7 +251,7 @@ async def payment_callback(
# 计算到期时间
days = 365 if billing_cycle == "yearly" else 30
expires_at = datetime.now(UTC) + timedelta(days=days)
expires_at = datetime.now(timezone.utc) + timedelta(days=days)
repo.update_subscription_on_payment(user_id, plan, expires_at)
return {"success": True, "message": "支付成功", "record_id": record_id}
+386 -39
View File
@@ -1,13 +1,4 @@
"""Template 列表路由(供生成页自动选模板).
保留:
- GET /templates:列表查询(生成页使用)
- 默认模板自动创建兜底逻辑(复用 _default_template.get_or_create_default_template_id)
其他模板 CRUD / 分类 / 标签 / 收藏 / 复制 / 校验 / 使用统计等 HTTP 端点
已在 PR#1918 中删除(前端 PR#1911 已删除 my-templates / editing-planner /
templates 管理页面)。
"""
"""Template CRUD + generate + category routes."""
from __future__ import annotations
@@ -16,19 +7,53 @@ import logging
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_db_session
from app.schemas.template import (
CategoryResponse,
CopyTemplateRequest,
CreateCategoryRequest,
CreateTemplateRequest,
GenerateWarningResponse,
ListCategoriesResponse,
ListTagsResponse,
ListTemplatesResponse,
SegmentResponse,
TemplateResponse,
TemplateUsageResponse,
ToggleFavoriteResponse,
UpdateTemplateRequest,
ValidateTemplateRequest,
ValidateTemplateResponse,
)
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy.orm import Session
logger = logging.getLogger(__name__)
from packages.adapters.sqlalchemy_impl.template_repository import SQLAlchemyTemplateRepository
from packages.application.template.commands import ListTemplatesFilter
from packages.application.template.use_cases import CountTemplatesUseCase, ListTemplatesUseCase
from ._default_template import get_or_create_default_template_id
from packages.application.template.commands import (
CopyTemplateCommand,
CreateCategoryCommand,
CreateTemplateCommand,
ListTemplatesFilter,
SegmentCommand,
UpdateTemplateCommand,
ValidateTemplateCommand,
)
from packages.application.template.use_cases import (
CopyTemplateUseCase,
CountTemplatesUseCase,
CreateCategoryUseCase,
CreateTemplateUseCase,
DeleteCategoryUseCase,
DeleteTemplateUseCase,
GetTemplateUseCase,
ListCategoriesUseCase,
ListTagsUseCase,
ListTemplatesUseCase,
NotFoundError,
UpdateTemplateUseCase,
ValidateTemplateUseCase,
ValidationError,
)
router = APIRouter()
@@ -37,32 +62,354 @@ def _get_template_repository(session: Session = Depends(get_db_session)) -> SQLA
return SQLAlchemyTemplateRepository(session)
@router.get("", response_model=ListTemplatesResponse, summary="获取模板列表")
def _segment_to_response(seg) -> SegmentResponse:
return SegmentResponse(
id=seg.id,
template_id=seg.template_id,
segment_order=seg.segment_order,
duration_min=seg.duration_min,
duration_max=seg.duration_max,
material_type=seg.material_type,
created_at=seg.created_at,
updated_at=seg.updated_at,
)
def _to_response(template, usage_count: int = 0) -> TemplateResponse:
return TemplateResponse(
id=template.id,
user_id=template.user_id,
name=template.name,
mode=template.mode,
category=template.category,
tags=template.tags,
title_config=template.title_config,
subtitle_config=template.subtitle_config,
bgm_config=template.bgm_config,
estimated_duration=template.estimated_duration,
segments=[_segment_to_response(s) for s in getattr(template, "segments", [])],
is_active=template.is_active,
usage_count=usage_count,
created_at=template.created_at,
updated_at=template.updated_at,
)
# ── Template CRUD ──
@router.get("", response_model=ListTemplatesResponse)
def list_templates(
mode: str | None = Query(None, description="编辑模式:generic/vlog/storyboard,不传返回全部"),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=200),
category: str | None = Query(None, description="按分类筛选"),
tag: str | None = Query(None, description="按标签筛选"),
page: int = Query(1, ge=1, description="页码,从 1 开始"),
page_size: int = Query(20, ge=1, le=100, description="每页条数,默认 20"),
current_user: AuthenticatedUser = Depends(get_current_user),
repo: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
db: Session = Depends(get_db_session),
):
"""获取用户可用的模板列表(仅返回 active 状态)。"""
user_id = str(current_user.user.id)
# P0 兜底:无有效模板时自动创建默认配音模板(解决新用户首次进入生成页 404)
get_or_create_default_template_id(db, user_id)
keyword: str | None = Query(None, description="按名称关键词搜索"),
mode: str | None = Query(None, description="按剪辑模式筛选"),
valid_only: bool = Query(
False,
description="仅返回已配置片段的模板(剪辑页传 true;模板编辑器不传,可查看全部模板含草稿)",
),
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> ListTemplatesResponse:
user_id = authenticated_user.user.id
try:
tpl_filter = ListTemplatesFilter(
category=category,
tag=tag,
keyword=keyword,
mode=mode,
valid_only=valid_only,
)
use_case = ListTemplatesUseCase(template_repository)
templates = use_case.execute(user_id, skip=skip, limit=limit, filter=tpl_filter)
count_use_case = CountTemplatesUseCase(template_repository)
total = count_use_case.execute(user_id, filter=tpl_filter)
list_uc = ListTemplatesUseCase(repo)
count_uc = CountTemplatesUseCase(repo)
filters = ListTemplatesFilter(
category=category,
tag=tag,
mode=mode,
valid_only=True, # 仅返回 active + 有片段配置
# 批量查询使用次数
items = []
for t in templates:
usage = template_repository.get_usage_count(t.id)
items.append(_to_response(t, usage_count=usage))
except Exception:
logger.exception("list_templates 查询失败: user_id=%s", user_id)
return ListTemplatesResponse(items=[], total=0)
return ListTemplatesResponse(
items=items,
total=total,
)
skip = (page - 1) * page_size
templates = list_uc.execute(user_id, skip=skip, limit=page_size, filter=filters)
total = count_uc.execute(user_id, filter=filters)
items = [TemplateResponse.model_validate(tpl, from_attributes=True) for tpl in templates]
return ListTemplatesResponse(items=items, total=total)
@router.get("/{template_id}", response_model=TemplateResponse)
def get_template(
template_id: str,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> TemplateResponse:
user_id = authenticated_user.user.id
try:
use_case = GetTemplateUseCase(template_repository)
template = use_case.execute(template_id, user_id)
usage = template_repository.get_usage_count(template_id)
except Exception as _e:
logger.exception("get_template 查询失败: template_id=%s", template_id)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="模板查询失败") from _e
if template is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
return _to_response(template, usage_count=usage)
@router.post("", response_model=TemplateResponse, status_code=status.HTTP_201_CREATED)
def create_template(
request: CreateTemplateRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> TemplateResponse:
user_id = authenticated_user.user.id
command = CreateTemplateCommand(
user_id=user_id,
name=request.name,
mode=request.mode,
category=request.category,
tags=request.tags,
title_config=request.title_config,
subtitle_config=request.subtitle_config,
bgm_config=request.bgm_config,
estimated_duration=request.estimated_duration,
segments=[
SegmentCommand(
segment_order=s.segment_order,
duration_min=s.duration_min,
duration_max=s.duration_max,
material_type=s.material_type,
)
for s in request.segments
],
)
use_case = CreateTemplateUseCase(template_repository)
try:
template = use_case.execute(command)
except ValidationError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
return _to_response(template)
@router.patch("/{template_id}", response_model=TemplateResponse)
def update_template(
template_id: str,
request: UpdateTemplateRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> TemplateResponse:
user_id = authenticated_user.user.id
command = UpdateTemplateCommand(
template_id=template_id,
user_id=user_id,
name=request.name,
mode=request.mode,
category=request.category,
tags=request.tags,
title_config=request.title_config,
subtitle_config=request.subtitle_config,
bgm_config=request.bgm_config,
estimated_duration=request.estimated_duration,
segments=(
[
SegmentCommand(
segment_order=s.segment_order,
duration_min=s.duration_min,
duration_max=s.duration_max,
material_type=s.material_type,
)
for s in request.segments
]
if request.segments is not None
else None
),
)
use_case = UpdateTemplateUseCase(template_repository)
try:
template = use_case.execute(command)
except NotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e
except ValidationError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
return _to_response(template)
@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response)
def delete_template(
template_id: str,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> Response:
user_id = authenticated_user.user.id
use_case = DeleteTemplateUseCase(template_repository)
deleted = use_case.execute(template_id, user_id)
if not deleted:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
return
@router.post("/{template_id}/copy", response_model=TemplateResponse, status_code=status.HTTP_201_CREATED)
def copy_template(
template_id: str,
request: CopyTemplateRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> TemplateResponse:
"""复制模板(含所有片段配置)"""
user_id = authenticated_user.user.id
command = CopyTemplateCommand(
template_id=template_id,
user_id=user_id,
new_name=request.new_name,
)
use_case = CopyTemplateUseCase(template_repository)
try:
template = use_case.execute(command)
except NotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e
except ValidationError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
return _to_response(template)
@router.get("/{template_id}/usage", response_model=TemplateUsageResponse)
def get_template_usage(
template_id: str,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> TemplateUsageResponse:
"""获取模板使用次数(关联的剪辑计划数量)"""
user_id = authenticated_user.user.id
# 鉴权:确保模板存在且属于当前用户
use_case = GetTemplateUseCase(template_repository)
template = use_case.execute(template_id, user_id)
if template is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
usage = template_repository.get_usage_count(template_id)
return TemplateUsageResponse(template_id=template_id, usage_count=usage)
@router.post("/{template_id}/toggle-favorite", response_model=ToggleFavoriteResponse)
def toggle_favorite(
template_id: str,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> ToggleFavoriteResponse:
"""切换模板收藏状态(当前为兼容端点,始终返回 false)"""
user_id = authenticated_user.user.id
use_case = GetTemplateUseCase(template_repository)
try:
template = use_case.execute(template_id, user_id)
except Exception as _e:
logger.exception("toggle_favorite 查询失败: template_id=%s", template_id)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e
if template is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found")
return ToggleFavoriteResponse(id=template_id, is_favorite=False)
# ── Validate template ──
@router.post("/{template_id}/validate", response_model=ValidateTemplateResponse)
def validate_template(
template_id: str,
request: ValidateTemplateRequest = ValidateTemplateRequest(),
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> ValidateTemplateResponse:
user_id = authenticated_user.user.id
command = ValidateTemplateCommand(
template_id=template_id,
user_id=user_id,
voiceover_duration=request.voiceover_duration,
)
use_case = ValidateTemplateUseCase(template_repository)
try:
result = use_case.execute(command)
except NotFoundError as _e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Template not found") from _e
except ValidationError as exc:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
return ValidateTemplateResponse(
template=_to_response(result.template),
warnings=[GenerateWarningResponse(code=w.code, message=w.message, details=w.details) for w in result.warnings],
)
# ── Category CRUD ──
@router.get("/categories/list", response_model=ListCategoriesResponse)
def list_categories(
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> ListCategoriesResponse:
user_id = authenticated_user.user.id
try:
use_case = ListCategoriesUseCase(template_repository)
categories = use_case.execute(user_id)
except Exception:
logger.exception("list_categories 查询失败: user_id=%s", user_id)
return ListCategoriesResponse(items=[])
return ListCategoriesResponse(
items=[CategoryResponse(id=c.id, user_id=c.user_id, name=c.name, created_at=c.created_at) for c in categories],
)
@router.post("/categories", response_model=CategoryResponse, status_code=status.HTTP_201_CREATED)
def create_category(
request: CreateCategoryRequest,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> CategoryResponse:
user_id = authenticated_user.user.id
command = CreateCategoryCommand(user_id=user_id, name=request.name)
use_case = CreateCategoryUseCase(template_repository)
category = use_case.execute(command)
return CategoryResponse(
id=category.id,
user_id=category.user_id,
name=category.name,
created_at=category.created_at,
)
@router.delete(
"/categories/{category_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None, response_class=Response
)
def delete_category(
category_id: str,
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> Response:
user_id = authenticated_user.user.id
use_case = DeleteCategoryUseCase(template_repository)
deleted = use_case.execute(category_id, user_id)
if not deleted:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found")
return Response(status_code=204)
# ── Tags ──
@router.get("/tags/list", response_model=ListTagsResponse)
def list_tags(
authenticated_user: AuthenticatedUser = Depends(get_current_user),
template_repository: SQLAlchemyTemplateRepository = Depends(_get_template_repository),
) -> ListTagsResponse:
"""获取用户所有模板标签(去重排序)"""
user_id = authenticated_user.user.id
try:
use_case = ListTagsUseCase(template_repository)
tags = use_case.execute(user_id)
except Exception:
logger.exception("list_tags 查询失败: user_id=%s", user_id)
return ListTagsResponse(items=[])
return ListTagsResponse(items=tags)
@@ -28,7 +28,7 @@ from .adjustments import router as adjustments_router
from .ai_features import router as ai_features_router
from .bgm import router as bgm_router
from .clips import router as clips_router
from .dependencies import get_draft_plan_id, get_editor_services, resolve_draft_plan_id # noqa: F401
from .dependencies import get_draft_plan_id, get_editor_services # noqa: F401
from .draft import router as draft_router
from .effects import router as effects_router
from .export import router as export_router
@@ -622,10 +622,8 @@ def create_clips_from_assets_editor(
"""从素材批量创建片段(按模板segment配置创建,MediaKit异步更新).
逻辑:
1. 从模板读取 segments,片段数量优先级:显式 clip_count(1-10)→ 旧字段
required_clips_count(兼容,超10截断)→ 默认 3(产品默认 3 段)。
片段数大于模板 segment 数时按顺序循环复用 segment 配置。
2. 每个片段时长在对应 segment 的 duration_min ~ duration_max 之间随机取值(保留一位小数)
1. 从模板读取 segments,片段数量 = segment 数量(忽略前端传的 required_clips_count)
2. 每个片段时长在 segment 的 duration_min ~ duration_max 之间随机取值(保留一位小数)
3. 素材按片段顺序轮询分配,素材不够时同一素材切多个片段
4. 使用 replace_all_clips_transactional 原子性地清空旧片段并创建新的(随机起始时间)
5. 立即返回响应(目标 <1秒)
@@ -650,22 +648,6 @@ def create_clips_from_assets_editor(
detail="模板未配置片段",
)
# 1.5 归一化片段数量:
# 优先级:显式 clip_count → 旧字段 required_clips_count(由 schema 归一化到 clip_count)
# → 默认 3(产品默认 3 段)。按 N 循环复用 segment 配置;N <= len(segments) 时截取前 N 个
# (保持向后兼容:原模板有 N 个 segment、前端不传 clip_count 且 N<=10 时按模板段数创建;
# 默认模板仅有 1 个通用 segment 时按 clip_count=3 循环生成 3 段)。
requested_clip_count = getattr(body, "clip_count", None)
if requested_clip_count is None:
# schema 未显式传 clip_count 且无 legacy:使用模板 segments 数量,若超出 10 则截断
requested_clip_count = len(segments) if 1 <= len(segments) <= 10 else 3
requested_clip_count = max(1, min(int(requested_clip_count), 10))
effective_segments: list[tuple[int, float, float]] = []
for i in range(requested_clip_count):
src = segments[i % len(segments)]
effective_segments.append((i, float(src[1]), float(src[2])))
segments = effective_segments
# 防御:schema validator 已过滤 null/空串,这里再归一化一次,
# 避免异常入参(undefined → null)导致后续 /assets/{id} 404 / 422
asset_ids = [str(aid).strip() for aid in (body.asset_ids or []) if isinstance(aid, str) and aid.strip()]
@@ -2,16 +2,13 @@
核心依赖:
- get_editor_services: 获取模板+计划服务
- get_draft_plan_id: Depends 形式的路径依赖(template_id 路径参数必填)
- resolve_draft_plan_id: 纯函数版本,供 clips_standalone 等非路径参数场景复用
(支持空 tid 时自动兜底创建默认模板)
- get_draft_plan_id: 根据 template_id 获取或创建草稿,返回 plan_id
"""
from __future__ import annotations
import logging
from app.api.routes._default_template import get_or_create_default_template_id
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_db_session
from app.services.edit_plan_service import EditPlanService
@@ -33,61 +30,50 @@ def get_editor_services(
return EditTemplateService(db), EditPlanService(db)
def resolve_draft_plan_id(
def get_draft_plan_id(
template_id: str,
services: tuple[EditTemplateService, EditPlanService],
current_user: AuthenticatedUser,
db: Session,
auto_create_default: bool = True,
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
) -> str:
"""根据 template_id 获取或创建草稿,返回 plan_id(纯函数,不带 Depends)。
"""路径依赖:根据 template_id 获取或创建草稿,返回 plan_id.
当 auto_create_default=True 且 template_id 为空时,自动调用
get_or_create_default_template_id 创建默认模板(用于 clips_standalone
等非路径参数场景)。
这是模板编辑器路由的核心依赖——所有编辑器端点都先经过这里,
确保 template_id → plan_id 的映射始终存在。
模板读取遵循单一数据源、显式判定(不使用异常降级):
- 用户自建模板在旧表 ``templates``(归属 user_id,is_active=True);
- 全局模板在新表 ``edit_templates``(无 user_id,全局可读)。
模板不存在、已删除或不归属于当前用户时,一律返回 404。
"""
tpl_svc, plan_svc = services
user_id = str(current_user.user.id)
# 0. 空 tid 兜底
if not template_id:
if auto_create_default:
tid = get_or_create_default_template_id(db, user_id)
if not tid:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="无法自动创建默认模板,请刷新页面重试",
)
template_id = tid
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="template_id 不能为空",
)
# 1. 门禁:校验模板存在且可访问
# 0. 门禁:校验模板存在且可访问(即使草稿已缓存命中也要校验,
# 避免模板被删除/无权访问后仍可通过既有草稿 plan 继续操作)。
old_repo = SQLAlchemyTemplateRepository(db)
old_template = old_repo.get_active(template_id, user_id)
is_global_template = tpl_svc.get_template(template_id) is not None
if old_template is None and not is_global_template:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="模板不存在")
# 2. 草稿已存在 → 直接返回
# 1. 草稿已存在 → 直接返回
draft = tpl_svc.get_template_draft(template_id)
if draft is not None:
return draft.id
# 3. 全局模板(新系统)→ 用新服务创建草稿
# 2. 全局模板(新系统)→ 用新服务创建草稿
if is_global_template:
draft = tpl_svc.create_template_draft(template_id, user_id=user_id)
return draft.id
# 4. 旧模板(templates 表)→ 基于旧模板创建草稿计划
# 3. 旧模板(templates 表)→ 基于旧模板创建草稿计划
from app.services.plan_generator_service import PlanGeneratorService
from packages.domain.edit_template import EditTemplate, EditTemplateStatus
from packages.domain.template_clip_config import ClipType, TemplateClipConfig
# 构造伪 EditTemplate 对象(只填 generate_from_template 需要的字段)
pseudo_template = EditTemplate(
id=old_template.id,
name=old_template.name,
@@ -95,6 +81,7 @@ def resolve_draft_plan_id(
status=EditTemplateStatus.ACTIVE,
)
# 将旧模板 segments 转换为 clip_configs
clip_configs: list[TemplateClipConfig] = []
for seg in old_template.segments or []:
clip_configs.append(
@@ -118,6 +105,7 @@ def resolve_draft_plan_id(
)
plan = result["plan"]
# 标记为模板草稿(后续可复用 tpl_svc.get_template_draft 的查找逻辑)
plan_svc.update_plan_config(plan.id, {"is_template_draft": True})
logger.info(
@@ -127,23 +115,3 @@ def resolve_draft_plan_id(
user_id,
)
return plan.id
def get_draft_plan_id(
template_id: str,
services: tuple[EditTemplateService, EditPlanService] = Depends(get_editor_services),
current_user: AuthenticatedUser = Depends(get_current_user),
db: Session = Depends(get_db_session),
) -> str:
"""路径依赖:根据 template_id 获取或创建草稿,返回 plan_id.
Depends 版本:路径参数 template_id 由 FastAPI 保证非空,不自动兜底。
兜底逻辑走 resolve_draft_plan_id(auto_create_default=False)。
"""
return resolve_draft_plan_id(
template_id=template_id,
services=services,
current_user=current_user,
db=db,
auto_create_default=False,
)
@@ -6,9 +6,9 @@
from __future__ import annotations
import re as _re
from typing import Any, Optional
from typing import Any, List, Optional
from pydantic import BaseModel, Field, model_validator, validator
from pydantic import BaseModel, Field, validator
_EXPORT_RESOLUTION_PATTERN = _re.compile(r"^\d+x\d+$")
_EXPORT_VALID_QUALITY_PRESETS = {"ultra_fast", "fast", "balanced", "high", "best"}
@@ -21,7 +21,7 @@ _EXPORT_VALID_FORMATS = {"mp4", "mov"}
class AIRecommendRequest(BaseModel):
"""AI 推荐片段方案请求体"""
asset_ids: list[str] = Field(default_factory=list, description="素材 ID 列表")
asset_ids: List[str] = Field(default_factory=list, description="素材 ID 列表")
editing_mode: str = Field(default="one_take", description="剪辑模式: one_take / pip / voice_over / voice_pip")
target_duration: float = Field(default=30.0, ge=1.0, le=600.0, description="目标时长(秒)")
@@ -44,7 +44,7 @@ class AIRecommendResponse(BaseModel):
"""AI 推荐片段方案响应体"""
plan_id: str = Field(..., description="剪辑计划 ID")
clips: list[AIRecommendClipItem] = Field(..., description="推荐的片段列表")
clips: List[AIRecommendClipItem] = Field(..., description="推荐的片段列表")
config: dict[str, Any] = Field(..., description="推荐的 plan config(cover/title/subtitle/bgm)")
total_duration: float = Field(..., ge=0.0, description="推荐方案总时长(秒)")
confidence: float = Field(..., ge=0.0, le=1.0, description="AI 推荐置信度 (0~1)")
@@ -137,7 +137,7 @@ class ClipReorderItem(BaseModel):
class ClipReorderRequest(BaseModel):
"""片段重排序请求"""
items: list[ClipReorderItem] = Field(..., min_length=1, max_length=500, description="重排序条目列表")
items: List[ClipReorderItem] = Field(..., min_length=1, max_length=500, description="重排序条目列表")
class ClipReorderResponse(BaseModel):
@@ -151,7 +151,7 @@ class ClipReorderResponse(BaseModel):
class ClipBatchDeleteRequest(BaseModel):
"""批量删除片段请求"""
clip_ids: list[str] = Field(..., min_length=1, max_length=500, description="要删除的片段ID列表")
clip_ids: List[str] = Field(..., min_length=1, max_length=500, description="要删除的片段ID列表")
class ClipBatchDeleteResponse(BaseModel):
@@ -162,26 +162,13 @@ class ClipBatchDeleteResponse(BaseModel):
message: str = ""
# sentinel:区分「前端未传 clip_count」和「显式传 0/None」
_UNSET = object()
class ClipsFromAssetsRequest(BaseModel):
"""从素材批量创建片段请求"""
asset_ids: list[str] = Field(..., min_length=1, max_length=200, description="素材 ID 列表,按顺序追加到时间线末尾")
asset_ids: List[str] = Field(..., min_length=1, max_length=200, description="素材 ID 列表,按顺序追加到时间线末尾")
clip_type: str = Field(default="main", description="片段类型,默认 main")
clip_count: Optional[int] = Field(
default=None,
ge=1,
le=10,
description="片段数量(1-10);不传时使用旧字段 required_clips_count;两者都不传时回退为模板 segments 数量(默认 3 段)。",
)
required_clips_count: Optional[int] = Field(
default=None,
ge=1,
le=200,
description="[已废弃] 旧字段,请使用 clip_count;仅作向后兼容——clip_count 未显式传入时才回退本字段(超10截断到10)。",
default=None, ge=1, le=200, description="要求创建的片段数量;不传则等于素材数量"
)
@validator("asset_ids", pre=True)
@@ -193,23 +180,6 @@ class ClipsFromAssetsRequest(BaseModel):
return v
return [x for x in v if isinstance(x, str) and x.strip()]
@model_validator(mode="before")
@classmethod
def _backfill_clip_count(cls, data: Any) -> Any:
"""兼容旧字段 required_clips_count:仅当新字段 clip_count 未显式传入时才回退旧字段;
两者都没传时保持 clip_count=None,路由层按模板 segments 数量兜底。旧字段超 10 截断到 10。"""
if not isinstance(data, dict):
return data
has_new = "clip_count" in data and data["clip_count"] is not None
if not has_new:
legacy = data.get("required_clips_count")
if legacy is not None:
try:
data["clip_count"] = max(1, min(int(legacy), 10))
except (TypeError, ValueError):
pass
return data
class ClipsFromAssetsResponse(BaseModel):
"""从素材批量创建片段响应"""
@@ -218,7 +188,7 @@ class ClipsFromAssetsResponse(BaseModel):
created_count: int
plan_id: str = ""
message: str = ""
clip_ids: list[str] = Field(default_factory=list, description="创建的片段ID列表")
clip_ids: List[str] = Field(default_factory=list, description="创建的片段ID列表")
duplicate_warning: Optional[str] = Field(default=None, description="查重率超标警告")
exhaustion_warning: Optional[str] = Field(default=None, description="素材耗尽警告")
@@ -302,7 +272,7 @@ class ExportPresetItem(BaseModel):
class ExportPresetListResponse(BaseModel):
"""导出预设列表响应"""
items: list[ExportPresetItem]
items: List[ExportPresetItem]
total: int
@@ -316,7 +286,7 @@ class FilterPresetResponse(BaseModel):
name: str
category: str
description: str
tags: list[str] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
class FilterConfigResponse(BaseModel):
@@ -346,7 +316,7 @@ class FilterUpdateRequest(BaseModel):
class FilterPresetListResponse(BaseModel):
"""滤镜预设列表响应"""
items: list[FilterPresetResponse]
items: List[FilterPresetResponse]
total: int
@@ -360,7 +330,7 @@ class TransitionPresetResponse(BaseModel):
name: str
category: str
description: str
tags: list[str] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
default_duration: float
min_duration: float
max_duration: float
@@ -402,7 +372,7 @@ class BatchTransitionResponse(BaseModel):
class TransitionPresetListResponse(BaseModel):
"""转场预设列表响应"""
items: list[TransitionPresetResponse]
items: List[TransitionPresetResponse]
total: int
@@ -458,7 +428,7 @@ class EditorClipResponse(BaseModel):
class EditorClipListResponse(BaseModel):
"""片段列表响应"""
items: list[EditorClipResponse]
items: List[EditorClipResponse]
total: int
@@ -496,7 +466,7 @@ class EditorClipBatchItem(BaseModel):
class EditorClipBatchUpdateRequest(BaseModel):
"""批量替换clips请求(全量覆盖)"""
clips: list[EditorClipBatchItem] = Field(default_factory=list)
clips: List[EditorClipBatchItem] = Field(default_factory=list)
class EditorClipBatchUpdateResponse(BaseModel):
@@ -584,4 +554,4 @@ class EditorTimelineResponse(BaseModel):
plan_id: str
total_duration: float
scenes: list[EditorTimelineSceneResponse]
scenes: List[EditorTimelineSceneResponse]
Regular → Executable
+3 -12
View File
@@ -177,7 +177,6 @@ def synthesize(
synthesis_meta = {
"speed": request.speed,
"emotion": request.emotion or "",
"language": request.language or "zh-CN",
}
if request.metadata_:
synthesis_meta.update(request.metadata_)
@@ -463,17 +462,10 @@ def save_tts_job_to_library(
try:
proc = subprocess.run(
[
"ffprobe",
"-v",
"quiet",
"-print_format",
"json",
"-show_format",
str(tmp_path),
"ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", str(tmp_path),
],
capture_output=True,
text=True,
timeout=10,
capture_output=True, text=True, timeout=10,
)
if proc.returncode == 0:
fmt = json.loads(proc.stdout).get("format", {})
@@ -584,7 +576,6 @@ def preview_tts(
voice_id=actual_voice_id,
speed=request.speed,
emotion=request.emotion,
language=getattr(request, "language", "zh-CN"),
)
except CosyVoiceError as e:
raise HTTPException(
+3 -45
View File
@@ -287,46 +287,7 @@ def retry_voice_clone(
return _to_response(profile)
_ALLOWED_PREVIEW_EMOTIONS = {
"",
# 7 种标准英文枚举(CosyVoice v3 官方值)
"neutral",
"happy",
"sad",
"angry",
"surprised",
"fearful",
"disgusted",
# 前端中文 7 标签
"中立",
"开心",
"难过",
"生气",
"惊讶",
"恐惧",
"厌恶",
# 旧英文 4 枚举 + 常见中文别名兼容
"natural",
"excited",
"calm",
"friendly",
"自然",
"愉快",
"高兴",
"快乐",
"兴奋",
"悲伤",
"愤怒",
"惊奇",
"吃惊",
"害怕",
"讨厌",
# 灵应 P1 指定别名
"中性",
"伤心",
"沉稳",
"亲切",
}
_ALLOWED_PREVIEW_EMOTIONS = {"", "natural", "excited", "calm", "friendly"}
@router.get("/{clone_id}/preview", response_model=VoiceClonePreviewResponse)
@@ -334,10 +295,7 @@ def get_voice_clone_preview(
clone_id: str,
text: str = Query("", description="自定义试听文本,为空则使用默认示例"),
speed: float = Query(1.0, ge=0.5, le=2.0, description="语速,0.5-2.0,默认 1.0"),
emotion: str = Query(
"",
description="情绪:neutral/happy/sad/angry/surprised/fearful/disgusted,兼容旧值 natural/excited/calm/friendly,空为默认自然",
),
emotion: str = Query("", description="情绪:natural/excited/calm/friendly,空字符串为默认自然"),
authenticated_user: AuthenticatedUser = Depends(get_current_user),
repository: SQLAlchemyVoiceCloneProfileRepository = Depends(get_voice_clone_profile_repository),
cosyvoice: CosyVoiceService = Depends(get_cosyvoice_service),
@@ -353,7 +311,7 @@ def get_voice_clone_preview(
if emotion not in _ALLOWED_PREVIEW_EMOTIONS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"不支持的 emotion 值: {emotion},可选: neutral/happy/sad/angry/surprised/fearful/disgusted 或中文 中立/中性/开心/难过/伤心/生气/愤怒/惊讶/吃惊/恐惧/害怕/厌恶/讨厌 或留空",
detail=f"不支持的 emotion 值: {emotion},可选: natural/excited/calm/friendly 或留空",
)
use_case = GetVoiceCloneUseCase(repository)
+1 -1
View File
@@ -6,7 +6,7 @@ ensuring proper lifecycle management and testability.
from __future__ import annotations
from collections.abc import Generator
from typing import Generator
import redis
from app.config import settings
+1 -1
View File
@@ -4,7 +4,7 @@
import logging
import time
from collections.abc import Callable
from typing import Callable
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
@@ -10,7 +10,7 @@ Exposes:
import re
import time
from collections.abc import Callable
from typing import Callable
from fastapi import Request, Response
from prometheus_client import (
+2 -9
View File
@@ -67,10 +67,7 @@ class CreateLipsyncJobRequest(BaseModel):
voice_id: str = Field("", description="音色 ID(预置音色或克隆音色 profile UUID)")
script_text: str = Field("", description="要合成的文案(直生模式必填,最长 5000 字符)")
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速(0.5-2.0),默认 1.0")
emotion: str = Field(
"",
description="情绪(英文枚举 neutral/happy/sad/angry/surprised/fearful/disgusted,或中文 中立/开心/难过/生气/惊讶/恐惧/厌恶;空为默认自然)",
)
emotion: str = Field("", description="情绪(natural/excited/calm/friendly 或中文 自然/兴奋/沉稳/亲切)")
enable_video_loop: bool = Field(
True, description="音频长于视频时是否循环画面(AI数字人默认开启,防止音频长于视频被截断)"
@@ -123,11 +120,7 @@ class AiAvatarTtsPreviewRequest(BaseModel):
voice_id: str = Field(..., min_length=1, max_length=128, description="音色 ID")
script_text: str = Field(..., min_length=1, max_length=5000, description="要合成的文案")
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速(0.5-2.0),默认 1.0")
emotion: str = Field(
"neutral",
max_length=32,
description="情绪(英文枚举 neutral/happy/sad/angry/surprised/fearful/disgusted,或中文 中立/开心/难过/生气/惊讶/恐惧/厌恶;默认 neutral)",
)
emotion: str = Field("natural", max_length=32, description="情绪")
class AiAvatarTtsPreviewResponse(BaseModel):
-182
View File
@@ -1,182 +0,0 @@
"""积分 & 会员相关 Pydantic Schema (#1895)"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Optional
from pydantic import BaseModel, Field
# ============ 余额 & 账户 ============
class PointsBalanceResponse(BaseModel):
"""积分余额 + 会员状态"""
balance: int = Field(..., description="当前积分余额")
total_earned: int = Field(..., description="累计获得积分")
total_spent: int = Field(..., description="累计消耗积分")
is_member: bool = Field(default=False, description="是否付费会员")
member_type: Optional[str] = Field(None, description="会员类型: monthly/quarterly/yearly")
member_expires_at: Optional[datetime] = Field(None, description="会员到期时间")
# ============ 流水 ============
class PointsTransactionItem(BaseModel):
"""单条积分流水"""
id: str
type: str = Field(..., description="类型: add/deduct")
source: str = Field(..., description="来源场景")
amount: int
balance_after: int
description: str = ""
ref_id: str = ""
created_at: Optional[str] = None
class PointsTransactionsResponse(BaseModel):
"""积分流水分页响应"""
items: list[PointsTransactionItem]
total: int
page: int
page_size: int
# ============ 规则 & 积分包 ============
class PointRuleItem(BaseModel):
"""单条积分规则"""
scene_key: str
name: str
base_points: int
unit: str
extra_per_30s: Optional[int] = None
class PointsRulesResponse(BaseModel):
"""所有积分消耗规则"""
rules: list[PointRuleItem]
free_user_multiplier: float = Field(..., description="免费用户积分上浮系数")
class PointsPackageItem(BaseModel):
"""积分包信息"""
code: str
name: str
points: int
price_cents: int
unit_price: str = Field("", description="单价描述,如 ¥0.099/积分")
class PointsPackagesResponse(BaseModel):
"""可购买的积分包列表"""
packages: list[PointsPackageItem]
user_discount: Optional[float] = Field(None, description="当前用户折扣(会员)")
# ============ 消费前检查 ============
class PointsCheckRequest(BaseModel):
"""消费前余额检查请求"""
scene_key: str
duration_minutes: Optional[float] = None
quantity: Optional[int] = 1
class PointsCheckResponse(BaseModel):
"""消费前余额检查响应"""
allowed: bool
required_points: int
current_balance: int
remaining_after: int
is_free_quota: bool = False
# ============ 手动扣减 / 退还(内部接口) ============
class PointsDeductRequest(BaseModel):
"""积分扣减请求"""
scene_key: str
amount: int
description: Optional[str] = ""
ref_id: Optional[str] = ""
class PointsRefundRequest(BaseModel):
"""积分退还请求"""
transaction_id: str
reason: Optional[str] = ""
class PointsRechargeRequest(BaseModel):
"""积分充值请求"""
package_id: str = Field(..., description="积分包 code,如 starter_pack")
# ============ 订单 ============
class PointsOrderResponse(BaseModel):
"""订单信息"""
id: str
order_type: str
product_code: str
amount_cents: int
status: str
created_at: Optional[str] = None
# ============ 每日额度 ============
class DailyUsageResponse(BaseModel):
"""今日免费额度使用情况"""
free_clips_used: int
free_clips_limit: int
free_clips_remaining: int
reset_at: str
# ============ 会员状态(聚合) ============
class MembershipStatusResponse(BaseModel):
"""当前用户会员状态(聚合信息)"""
is_member: bool
member_type: Optional[str] = None
member_expires_at: Optional[datetime] = None
points_balance: int
max_resolution: str = Field(
default="1080p",
description="可用最高分辨率: 720p(free) / 1080p(paid)",
)
# ============ 通用响应 ============
class SimpleMessageResponse(BaseModel):
"""简单消息响应"""
success: bool
message: str
data: Optional[dict[str, Any]] = None
+7 -7
View File
@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime
from typing import Optional
from typing import List, Optional
from pydantic import BaseModel, Field
@@ -20,8 +20,8 @@ class ScriptResponse(BaseModel):
user_id: str
title: str
content: str
segments: list[ScriptSegment] = Field(default_factory=list)
tags: list[str] = Field(default_factory=list)
segments: List[ScriptSegment] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
created_at: datetime
updated_at: datetime
@@ -34,12 +34,12 @@ class ScriptListResponse(BaseModel):
class CreateScriptRequest(BaseModel):
title: str = Field(..., min_length=1, max_length=255)
content: str = ""
segments: list[ScriptSegment] = Field(default_factory=list)
tags: list[str] = Field(default_factory=list)
segments: List[ScriptSegment] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
class UpdateScriptRequest(BaseModel):
title: Optional[str] = Field(None, min_length=1, max_length=255)
content: Optional[str] = None
segments: Optional[list[ScriptSegment]] = None
tags: Optional[list[str]] = None
segments: Optional[List[ScriptSegment]] = None
tags: Optional[List[str]] = None
-60
View File
@@ -1,60 +0,0 @@
"""Scripts AI 能力 Pydantic schemas — Issue #1893.
抖音文案提取、AI 改写、AI 标题生成的请求/响应模型。
"""
from __future__ import annotations
from typing import List, Optional
from pydantic import BaseModel, Field
# ── 抖音文案提取 ─────────────────────────────────────────────────────────────
class ExtractFromDouyinRequest(BaseModel):
"""从抖音视频提取文案请求."""
url: str = Field(..., description="抖音视频链接(短链或长链)")
class ExtractFromDouyinResponse(BaseModel):
"""从抖音视频提取文案响应."""
text: str = Field(..., description="ASR 识别出的文案文本")
duration_seconds: float = Field(..., description="视频时长(秒)")
source_url: str = Field(..., description="原始视频链接")
# ── AI 改写 ─────────────────────────────────────────────────────────────────
class AiRewriteRequest(BaseModel):
"""AI 文案改写请求."""
content: str = Field(..., description="原文内容")
style: Optional[str] = Field("口语化", description="改写风格,如 口语化/正式/活泼")
class AiRewriteResponse(BaseModel):
"""AI 文案改写响应."""
original: str = Field(..., description="原文")
rewritten: str = Field(..., description="改写后的文案")
style: str = Field(..., description="使用的改写风格")
# ── AI 标题生成 ──────────────────────────────────────────────────────────────
class AiGenerateTitlesRequest(BaseModel):
"""AI 标题生成请求."""
content: str = Field(..., description="文案内容")
count: int = Field(3, ge=1, le=5, description="生成标题数量(1-5,默认3)")
class AiGenerateTitlesResponse(BaseModel):
"""AI 标题生成响应."""
titles: List[str] = Field(..., description="生成的标题列表")
+84 -22
View File
@@ -1,14 +1,9 @@
"""Template API schemas(精简版:仅保留列表接口 + 默认模板自动兜底所需字段).
前端 PR#1911 删除 my-templates / editing-planner / templates 管理页后,
模板 CRUD / 分类 / 标签 / 收藏 / 复制 / 校验 / 使用统计等端点全部下线,
对应 Request/Response 模型也一并清理。
"""
"""Template API schemas."""
from __future__ import annotations
from datetime import datetime
from typing import Any, Optional
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
@@ -42,12 +37,12 @@ class TemplateResponse(BaseModel):
name: str
mode: str
category: str = ""
tags: list[str] = Field(default_factory=list)
title_config: dict[str, Any] = Field(default_factory=dict)
subtitle_config: dict[str, Any] = Field(default_factory=dict)
bgm_config: dict[str, Any] = Field(default_factory=dict)
tags: List[str] = Field(default_factory=list)
title_config: Dict[str, Any] = Field(default_factory=dict)
subtitle_config: Dict[str, Any] = Field(default_factory=dict)
bgm_config: Dict[str, Any] = Field(default_factory=dict)
estimated_duration: float = 0.0
segments: list[SegmentResponse] = Field(default_factory=list)
segments: List[SegmentResponse] = Field(default_factory=list)
is_active: bool = True
is_favorite: bool = False
usage_count: int = 0
@@ -55,29 +50,96 @@ class TemplateResponse(BaseModel):
updated_at: datetime
class ToggleFavoriteResponse(BaseModel):
id: str
is_favorite: bool
class ListTemplatesResponse(BaseModel):
items: list[TemplateResponse]
items: List[TemplateResponse]
total: int = 0
# ── Template Request(保留给内部 _get_or_create_default_template_id 兜底创建默认模板使用)──
# ── Template Request ──
class CreateTemplateRequest(BaseModel):
name: str
mode: str
category: str = ""
tags: list[str] = Field(default_factory=list)
title_config: dict[str, Any] = Field(default_factory=dict)
subtitle_config: dict[str, Any] = Field(default_factory=dict)
bgm_config: dict[str, Any] = Field(default_factory=dict)
tags: List[str] = Field(default_factory=list)
title_config: Dict[str, Any] = Field(default_factory=dict)
subtitle_config: Dict[str, Any] = Field(default_factory=dict)
bgm_config: Dict[str, Any] = Field(default_factory=dict)
estimated_duration: float = 0.0
segments: list[SegmentRequest] = Field(default_factory=list)
segments: List[SegmentRequest] = Field(default_factory=list)
class UpdateTemplateRequest(BaseModel):
name: Optional[str] = None
mode: Optional[str] = None
category: Optional[str] = None
tags: Optional[List[str]] = None
title_config: Optional[Dict[str, Any]] = None
subtitle_config: Optional[Dict[str, Any]] = None
bgm_config: Optional[Dict[str, Any]] = None
estimated_duration: Optional[float] = None
segments: Optional[List[SegmentRequest]] = None
# ── Validate ──
class ValidateTemplateRequest(BaseModel):
voiceover_duration: Optional[float] = None # 配音实际时长(秒)
class GenerateWarningResponse(BaseModel):
"""兼容老 import(如校验逻辑内部复用);模板管理页已下线,可按需进一步清理。"""
code: str
message: str
details: dict[str, Any] = Field(default_factory=dict)
details: Dict[str, Any] = Field(default_factory=dict)
class ValidateTemplateResponse(BaseModel):
template: TemplateResponse
warnings: List[GenerateWarningResponse] = Field(default_factory=list)
# ── Category ──
class CategoryResponse(BaseModel):
id: str
user_id: str
name: str
created_at: datetime
class CreateCategoryRequest(BaseModel):
name: str
class ListCategoriesResponse(BaseModel):
items: List[CategoryResponse]
# ── Copy Template ──
class CopyTemplateRequest(BaseModel):
new_name: str
# ── Tags ──
class ListTagsResponse(BaseModel):
items: List[str]
# ── Usage Stats ──
class TemplateUsageResponse(BaseModel):
template_id: str
usage_count: int
+4 -4
View File
@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime
from typing import Optional
from typing import List, Optional
from pydantic import BaseModel, Field
@@ -15,7 +15,7 @@ class TitleLibraryItemResponse(BaseModel):
text: str
category: str = "default"
description: str = ""
tags: list[str] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
usage_count: int = 0
is_active: bool = True
created_at: datetime
@@ -32,7 +32,7 @@ class CreateTitleLibraryRequest(BaseModel):
text: str = Field(..., min_length=1, max_length=500)
category: str = "default"
description: str = ""
tags: list[str] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
class UpdateTitleLibraryRequest(BaseModel):
@@ -40,4 +40,4 @@ class UpdateTitleLibraryRequest(BaseModel):
text: Optional[str] = Field(None, min_length=1, max_length=500)
category: Optional[str] = None
description: Optional[str] = None
tags: Optional[list[str]] = None
tags: Optional[List[str]] = None
+6 -10
View File
@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime
from typing import Any, Optional
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
@@ -16,14 +16,11 @@ class TTSSynthesizeRequest(BaseModel):
output_name: str = Field("", description="输出文件名")
language: str = Field("zh-CN", description="语言")
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速")
emotion: str = Field(
"",
description="情绪(中文/英文:自然/兴奋/沉稳/亲切/开心/悲伤/愤怒/惊讶/恐惧/厌恶 等;通过 instruction 自然语言指令控制)",
)
emotion: str = Field("", description="情绪(natural/excited/calm/friendly,或中文 自然/兴奋/沉稳/亲切)")
voice_model: str = Field("", description="语音模型名称")
voice_clone_profile_id: str = Field("", description="关联的音色克隆档案 ID")
format: str = Field("mp3", description="输出格式(mp3/wav/pcm)")
metadata_: Optional[dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
metadata_: Optional[Dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
class Config:
populate_by_name = True
@@ -49,7 +46,7 @@ class TTSJobResponse(BaseModel):
error_message: str = ""
retry_count: int = 0
max_retries: int = 3
metadata_: Optional[dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
metadata_: Optional[Dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
started_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
created_at: datetime
@@ -83,7 +80,7 @@ class TTSSynthesizeResponse(BaseModel):
class ListTTSJobResponse(BaseModel):
"""TTS 任务列表响应。"""
items: list[TTSJobResponse]
items: List[TTSJobResponse]
total: int
page: int
page_size: int
@@ -113,8 +110,7 @@ class TTSPreviewRequest(BaseModel):
text: str = Field(..., min_length=1, max_length=200, description="合成文本,限制 200 字")
voice_id: str = Field(..., min_length=1, description="音色 ID")
speed: float = Field(1.0, ge=0.5, le=2.0, description="语速")
emotion: str = Field("", description="情绪(中文/英文:自然/兴奋/沉稳/亲切/开心/悲伤/愤怒/惊讶/恐惧/厌恶 等)")
language: str = Field("zh-CN", description="语言(zh-CN/en-US 等)")
emotion: str = Field("", description="情绪(natural/excited/calm/friendly,或中文)")
pitch: float = Field(1.0, ge=0.5, le=2.0, description="音调(预留,当前未使用)")
+2 -2
View File
@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime
from typing import Optional
from typing import List, Optional
from pydantic import BaseModel, Field
@@ -61,7 +61,7 @@ class ShareResponse(BaseModel):
class ShareListResponse(BaseModel):
"""分享列表响应."""
items: list[ShareResponse]
items: List[ShareResponse]
total: int = 0
skip: int = 0
limit: int = 20
+3 -3
View File
@@ -6,7 +6,7 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal, Optional
from typing import List, Literal, Optional
from pydantic import BaseModel, Field
@@ -56,7 +56,7 @@ class UnifiedVoiceItemResponse(BaseModel):
status: str = "completed"
"""状态"""
tags: list[str] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
"""标签列表"""
# 克隆音色特有字段
@@ -113,7 +113,7 @@ class PresetVoiceItemResponse(BaseModel):
preview_url: str = ""
"""预览音频 URL"""
tags: list[str] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
"""标签列表"""
+4 -4
View File
@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime
from typing import Any, Optional
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
@@ -19,7 +19,7 @@ class CreateVoiceCloneRequest(BaseModel):
language: str = Field("zh-CN", description="语言")
gender: str = Field("unknown", description="性别")
max_retries: int = Field(3, ge=1, le=10, description="最大重试次数")
metadata_: Optional[dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
metadata_: Optional[Dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
class Config:
populate_by_name = True
@@ -41,7 +41,7 @@ class VoiceCloneProfileResponse(BaseModel):
error_message: str = ""
retry_count: int = 0
max_retries: int = 3
metadata_: Optional[dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
metadata_: Optional[Dict[str, Any]] = Field(default=None, alias="metadata", description="额外元数据")
created_at: datetime
updated_at: datetime
@@ -62,7 +62,7 @@ class VoiceCloneStatusResponse(BaseModel):
class ListVoiceCloneResponse(BaseModel):
"""音色克隆列表响应。"""
items: list[VoiceCloneProfileResponse]
items: List[VoiceCloneProfileResponse]
total: int
+4 -4
View File
@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime
from typing import Optional
from typing import List, Optional
from pydantic import BaseModel, Field
@@ -21,7 +21,7 @@ class VoiceLibraryItemResponse(BaseModel):
file_size: int = 0
status: str = "completed"
project_id: Optional[str] = None
tags: list[str] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
created_at: datetime
updated_at: datetime
@@ -42,7 +42,7 @@ class CreateVoiceLibraryRequest(BaseModel):
file_size: int = 0
status: str = "completed"
project_id: Optional[str] = None
tags: list[str] = Field(default_factory=list)
tags: List[str] = Field(default_factory=list)
class UpdateVoiceLibraryRequest(BaseModel):
@@ -55,4 +55,4 @@ class UpdateVoiceLibraryRequest(BaseModel):
duration: Optional[float] = None
file_size: Optional[int] = None
status: Optional[str] = None
tags: Optional[list[str]] = None
tags: Optional[List[str]] = None
@@ -16,7 +16,7 @@ import os
import subprocess
import tempfile
import uuid
from datetime import UTC, datetime
from datetime import datetime, timezone
from typing import Any, Optional
from sqlalchemy.orm import Session
@@ -117,7 +117,7 @@ class AiAvatarRenderService:
self.db.add(job)
self.db.flush()
job.submitted_at = datetime.now(UTC)
job.submitted_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
return job
@@ -164,7 +164,7 @@ class AiAvatarRenderService:
return None
if job.status in ("pending", "submitted"):
job.status = "cancelled"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
return job
@@ -186,7 +186,7 @@ class AiAvatarRenderService:
job.output_duration = 0.0
job.started_at = None
job.completed_at = None
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
return job
@@ -215,9 +215,9 @@ class AiAvatarRenderService:
try:
# 更新状态为 processing
job.status = "processing"
job.started_at = datetime.now(UTC)
job.started_at = datetime.now(timezone.utc)
job.progress = 5
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
# 获取对口型任务信息
@@ -394,8 +394,8 @@ class AiAvatarRenderService:
# 6. 完成
job.status = "completed"
job.progress = 100
job.completed_at = datetime.now(UTC)
job.updated_at = datetime.now(UTC)
job.completed_at = datetime.now(timezone.utc)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
logger.info("渲染任务完成: %s", job_id)
@@ -406,14 +406,14 @@ class AiAvatarRenderService:
except AiAvatarRenderError as exc:
job.status = "failed"
job.error_message = str(exc)
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
logger.error("渲染任务失败 [%s]: %s", job_id, exc)
raise
except Exception as exc:
job.status = "failed"
job.error_message = f"渲染异常: {str(exc)}"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
logger.exception("渲染任务异常 [%s]", job_id)
raise
@@ -494,7 +494,7 @@ class AiAvatarRenderService:
# 同步更新 cover_config,保持 smart-cover 路径一致
if isinstance(job.cover_config, dict):
job.cover_config = {**job.cover_config, "mode": "auto_frame", "url": cover_url.strip()}
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
return self._persist_to_library(job, cover_url=cover_url)
+12 -12
View File
@@ -13,7 +13,7 @@
from __future__ import annotations
import logging
from typing import Any, Optional
from typing import Any, Dict, List, Optional
from packages.domain.ai_parsing import generate_titles_fallback as _generate_titles_fallback_base
from packages.domain.ai_parsing import keyword_match_fallback as _semantic_match_fallback_base
@@ -64,7 +64,7 @@ def _generate_titles_fallback(
description: str,
style: str = "viral",
count: int = 5,
) -> list[str]:
) -> List[str]:
"""本地降级:基于模板规则生成标题(薄包装,转发到 ai_parsing 模块)."""
style_info = TITLE_STYLES.get(style, TITLE_STYLES["viral"])
return _generate_titles_fallback_base(description, style_info, count)
@@ -74,7 +74,7 @@ def generate_smart_titles(
description: str,
style: str = "viral",
count: int = 5,
) -> dict[str, Any]:
) -> Dict[str, Any]:
"""生成智能标题.
Args:
@@ -164,16 +164,16 @@ def generate_smart_titles(
def _semantic_match_fallback(
description: str,
assets: list[dict[str, Any]],
) -> list[dict[str, Any]]:
assets: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
"""本地降级:基于关键词的简单匹配(薄包装,转发到 ai_parsing 模块)."""
return _semantic_match_fallback_base(description, assets)
def _parse_semantic_match_response(
content: str,
asset_ids: list[str],
) -> Optional[dict[str, float]]:
asset_ids: List[str],
) -> Optional[Dict[str, float]]:
"""从模型返回中解析素材匹配度(薄包装,转发到 ai_parsing 模块)."""
result = _parse_semantic_match_base(content, asset_ids)
if result is None:
@@ -183,9 +183,9 @@ def _parse_semantic_match_response(
def semantic_match_assets(
description: str,
assets: list[dict[str, Any]],
assets: List[Dict[str, Any]],
top_k: int = 0,
) -> dict[str, Any]:
) -> Dict[str, Any]:
"""智能素材语义匹配.
根据用户描述,评估每个素材的语义匹配度并排序。
@@ -336,13 +336,13 @@ class AIService:
description: str,
style: str = "viral",
count: int = 5,
) -> dict[str, Any]:
) -> Dict[str, Any]:
return generate_smart_titles(description, style, count)
def semantic_match(
self,
description: str,
assets: list[dict[str, Any]],
assets: List[Dict[str, Any]],
top_k: int = 0,
) -> dict[str, Any]:
) -> Dict[str, Any]:
return semantic_match_assets(description, assets, top_k)
@@ -28,8 +28,8 @@ from __future__ import annotations
import json
import logging
from collections.abc import Callable
from datetime import UTC, datetime
from datetime import datetime, timezone
from typing import Callable
from sqlalchemy.orm import Session
@@ -57,7 +57,7 @@ _REUSE_OVERLAP_RATIO = 0.6
def _now_iso() -> str:
return datetime.now(UTC).isoformat()
return datetime.now(timezone.utc).isoformat()
def _read_meta(model) -> dict:
@@ -156,7 +156,7 @@ def record_used_segments(
r["plan_id"] = plan_id
meta[USED_RANGES_KEY] = ranges
model.classification_result = json.dumps(meta, ensure_ascii=False)
model.updated_at = datetime.now(UTC)
model.updated_at = datetime.now(timezone.utc)
return
ranges.append(
@@ -171,7 +171,7 @@ def record_used_segments(
)
meta[USED_RANGES_KEY] = ranges
model.classification_result = json.dumps(meta, ensure_ascii=False)
model.updated_at = datetime.now(UTC)
model.updated_at = datetime.now(timezone.utc)
def remove_used_segment(
@@ -215,7 +215,7 @@ def remove_used_segment(
if removed:
meta[USED_RANGES_KEY] = remaining
model.classification_result = json.dumps(meta, ensure_ascii=False)
model.updated_at = datetime.now(UTC)
model.updated_at = datetime.now(timezone.utc)
return removed
@@ -231,7 +231,7 @@ def reset_used_segments(db: Session, asset_id: str) -> None:
if meta.get(USED_RANGES_KEY):
meta[USED_RANGES_KEY] = []
model.classification_result = json.dumps(meta, ensure_ascii=False)
model.updated_at = datetime.now(UTC)
model.updated_at = datetime.now(timezone.utc)
logger.info("[片段追踪] 素材区间记录手动清空: asset_id=%s", asset_id)
+9 -9
View File
@@ -7,7 +7,7 @@
from __future__ import annotations
import logging
from typing import Any, Optional
from typing import Any, Dict, List, Optional
from app.services.asset_segment_tracker import (
REUSE_RATIO_LIMIT,
@@ -206,7 +206,7 @@ class EditPlanService:
status: Optional[EditPlanClipStatus] = None,
skip: int = 0,
limit: int = 100,
) -> list[EditPlanClip]:
) -> List[EditPlanClip]:
"""列出计划的片段"""
# 确保计划存在
self.get_plan_or_raise(plan_id)
@@ -503,7 +503,7 @@ class EditPlanService:
source = self.get_plan_or_raise(source_plan_id)
# 分页读取源 plan 全部片段
clips: list[EditPlanClip] = []
clips: List[EditPlanClip] = []
skip, page = 0, 500
while True:
batch = self._clip_repo.list_by_plan(source_plan_id, skip=skip, limit=page)
@@ -665,7 +665,7 @@ class EditPlanService:
source = self.get_plan_or_raise(source_plan_id)
# 分页读取源 plan 全部片段
clips: list[EditPlanClip] = []
clips: List[EditPlanClip] = []
skip, page = 0, 500
while True:
batch = self._clip_repo.list_by_plan(source_plan_id, skip=skip, limit=page)
@@ -797,7 +797,7 @@ class EditPlanService:
except Exception:
pass
clips: list[EditPlanClip] = []
clips: List[EditPlanClip] = []
skip, page = 0, 500
while True:
batch = self._clip_repo.list_by_plan(plan_id, skip=skip, limit=page)
@@ -1063,7 +1063,7 @@ class EditPlanService:
# ── 片段分割与合并 ──────────────────────────────────────────────────────
def split_clip(self, clip_id: str, split_time: float) -> dict[str, Any]:
def split_clip(self, clip_id: str, split_time: float) -> Dict[str, Any]:
"""将一个片段从指定位置分割为两个片段
Args:
@@ -1151,7 +1151,7 @@ class EditPlanService:
"right_clip": created_right,
}
def merge_clips(self, clip_ids: list[str]) -> EditPlanClip:
def merge_clips(self, clip_ids: List[str]) -> EditPlanClip:
"""合并多个连续片段为一个片段
Args:
@@ -1217,7 +1217,7 @@ class EditPlanService:
# ── 渲染生成流程 ────────────────────────────────────────────────────────
def get_generation_status(self, plan_id: str) -> dict[str, Any]:
def get_generation_status(self, plan_id: str) -> Dict[str, Any]:
"""获取渲染进度状态
Returns:
@@ -1364,7 +1364,7 @@ class EditPlanService:
)
return count
def update_plan_config(self, plan_id: str, config_updates: dict[str, Any]) -> EditPlan:
def update_plan_config(self, plan_id: str, config_updates: Dict[str, Any]) -> EditPlan:
"""更新计划配置(合并更新)
Args:
@@ -7,7 +7,7 @@
from __future__ import annotations
import logging
from typing import Any, Optional
from typing import Any, List, Optional
from sqlalchemy.orm import Session
@@ -76,7 +76,7 @@ class EditTemplateService:
active_only: bool = False,
skip: int = 0,
limit: int = 50,
) -> list[EditTemplate]:
) -> List[EditTemplate]:
"""列出模板
Args:
@@ -227,7 +227,7 @@ class EditTemplateService:
clip_type: Optional[ClipType] = None,
skip: int = 0,
limit: int = 100,
) -> list[TemplateClipConfig]:
) -> List[TemplateClipConfig]:
"""列出模板的片段配置
注意:本方法要求模板存在于新表 ``edit_templates``(全局模板库),
@@ -253,7 +253,7 @@ class EditTemplateService:
clip_type: Optional[ClipType] = None,
skip: int = 0,
limit: int = 100,
) -> list[TemplateClipConfig]:
) -> List[TemplateClipConfig]:
"""编辑器读取模板片段配置的单一数据源入口.
片段配置主表是 ``template_clip_configs``(直接读取,不抛异常、不降级)。
@@ -404,8 +404,8 @@ class EditTemplateService:
def reorder_clip_configs(
self,
template_id: str,
config_ids: list[str],
) -> list[TemplateClipConfig]:
config_ids: List[str],
) -> List[TemplateClipConfig]:
"""重新排序片段配置
Args:
@@ -560,7 +560,7 @@ class EditTemplateService:
)
# 5. 转换每个片段为模板片段配置
created_configs: list[TemplateClipConfig] = []
created_configs: List[TemplateClipConfig] = []
for clip_config_obj in clips_to_template_clip_configs(created_template.id, clips):
created = self._clip_config_repo.create(clip_config_obj)
created_configs.append(created)
+41 -56
View File
@@ -17,7 +17,7 @@ from __future__ import annotations
import io
import logging
import uuid
from datetime import UTC, datetime
from datetime import datetime, timezone
from typing import Optional
from urllib.parse import urlparse
@@ -35,7 +35,7 @@ from app.tasks.lipsync_tts import tts_synthesize_and_submit
from sqlalchemy.orm import Session
from packages.adapters.sqlalchemy_impl.models import LipsyncJobModel
from packages.application.cosyvoice_service import CosyVoiceError
from packages.application.cosyvoice_service import CosyVoiceError, normalize_emotion
from packages.domain.sentence_timings import (
compute_sentence_timings,
probe_audio_duration,
@@ -121,8 +121,7 @@ class LipsyncService:
text=script_text,
voice_id=actual_voice_id,
speed=speed,
emotion=emotion, # normalize 在 CosyVoiceService 内部完成
language="zh",
emotion=normalize_emotion(emotion),
)
except CosyVoiceError as exc:
raise MediaKitError(f"TTS 合成失败: {exc}", code="TTSSynthesisFailed") from exc
@@ -229,7 +228,7 @@ class LipsyncService:
)
job.mediakit_task_id = result["task_id"]
job.status = "submitted"
job.submitted_at = datetime.now(UTC)
job.submitted_at = datetime.now(timezone.utc)
self.db.commit()
logger.info(
"[lipsync] 直传音频已提交 MediaKit: job_id=%s task_id=%s",
@@ -305,7 +304,7 @@ class LipsyncService:
voice_id=voice_id or "",
script_text=script_text or "",
speed=speed,
emotion=emotion or "",
emotion=normalize_emotion(emotion) if is_tts_mode else (emotion or ""),
# 音频直传(含预合成)直接进入 pending(后续同步改为 submitted);TTS 模式进入 tts_processing
status="tts_processing" if is_tts_mode else "pending",
)
@@ -326,7 +325,7 @@ class LipsyncService:
voice_id,
script_text,
speed,
emotion or "",
normalize_emotion(emotion),
)
)
except Exception as exc:
@@ -338,7 +337,7 @@ class LipsyncService:
job.status = "failed"
job.error_message = f"Celery 任务投递失败: {exc}"
job.error_code = "AsyncDispatchFailed"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
else:
# 2b/2c. 直接音频 / 预合成音频:同步路径
@@ -360,7 +359,7 @@ class LipsyncService:
voice_id: str,
script_text: str,
speed: float = 1.0,
emotion: str = "neutral",
emotion: str = "natural",
) -> dict:
"""同步做 TTS 合成 + 下载 + ffprobe + 句子时间戳计算.
@@ -383,8 +382,7 @@ class LipsyncService:
text=script_text,
voice_id=actual_voice_id,
speed=speed,
emotion=emotion, # normalize 在 CosyVoiceService 内部完成
language="zh",
emotion=normalize_emotion(emotion),
)
except CosyVoiceError as exc:
raise MediaKitError(f"TTS 合成失败: {exc}", code="TTSSynthesisFailed") from exc
@@ -491,52 +489,39 @@ class LipsyncService:
mk_status = status_data.get("status", STATUS_RUNNING)
logger.info("MediaKit 对口型状态 [%s]: %s", job_id, mk_status)
try:
if mk_status == STATUS_COMPLETED:
result = status_data.get("result", {})
job.status = STATUS_COMPLETED
temp_url = result.get("video_url", "")
job.output_video_url = temp_url
job.output_duration = result.get("duration", 0.0)
job.completed_at = datetime.now(UTC)
job.updated_at = datetime.now(UTC)
self.db.commit()
# 异步转存自家 OSS
try:
from app.tasks.lipsync_tts import persist_output_video_task
persist_output_video_task.apply_async(args=(job_id, user_id, temp_url))
except Exception as exc:
logger.warning(
"提交输出视频异步转存任务失败,保留临时 URL: job_id=%s err=%s",
job_id,
exc,
)
elif mk_status == STATUS_FAILED:
error = status_data.get("error", {})
job.status = "failed"
job.error_message = error.get("message", "任务执行失败")
job.error_code = error.get("code", "TaskFailed")
job.completed_at = datetime.now(UTC)
else:
# 中间状态(running/processing/queued 等)同步到 DB,避免前端永远卡在 submitted
if isinstance(mk_status, str) and mk_status:
job.status = mk_status
job.updated_at = datetime.now(UTC)
if mk_status == STATUS_COMPLETED:
result = status_data.get("result", {})
job.status = STATUS_COMPLETED
temp_url = result.get("video_url", "")
job.output_video_url = temp_url
job.output_duration = result.get("duration", 0.0)
job.completed_at = datetime.now(timezone.utc)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
except Exception as exc: # noqa: BLE001 - DB 提交失败必须记录日志并重试,否则后台任务静默失败
logger.error(
"refresh_job_status 提交 DB 失败 job_id=%s mk_status=%s err=%s",
job_id,
mk_status,
exc,
exc_info=True,
)
# 异步转存自家 OSS
try:
self.db.rollback()
except Exception:
pass
# DB commit 失败不 raise,返回当前 job 对象让下次轮询再试
from app.tasks.lipsync_tts import persist_output_video_task
persist_output_video_task.apply_async(args=(job_id, user_id, temp_url))
except Exception as exc:
logger.warning(
"提交输出视频异步转存任务失败,保留临时 URL: job_id=%s err=%s",
job_id,
exc,
)
self.db.refresh(job)
return job
elif mk_status == STATUS_FAILED:
error = status_data.get("error", {})
job.status = "failed"
job.error_message = error.get("message", "任务执行失败")
job.error_code = error.get("code", "TaskFailed")
job.completed_at = datetime.now(timezone.utc)
else:
if isinstance(mk_status, str) and mk_status:
job.status = mk_status
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
return job
@@ -589,7 +574,7 @@ class LipsyncService:
if job.status in ("pending", "tts_processing", "submitted"):
job.status = "cancelled"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(job)
+13 -13
View File
@@ -14,7 +14,7 @@ from __future__ import annotations
import logging
import random
from typing import Any
from typing import Any, List
from sqlalchemy.orm import Session
@@ -62,8 +62,8 @@ class PlanGeneratorService:
def generate_from_template(
self,
template: EditTemplate,
clip_configs: list[TemplateClipConfig],
asset_ids: list[str],
clip_configs: List[TemplateClipConfig],
asset_ids: List[str],
*,
project_id: str = "",
created_by_user_id: str = "",
@@ -135,7 +135,7 @@ class PlanGeneratorService:
)
# 5. 持久化所有 clips 并计算总时长
created_clips: list[EditPlanClip] = []
created_clips: List[EditPlanClip] = []
total_duration = 0.0
for clip in clips:
saved = self._clip_repo.create(clip)
@@ -184,15 +184,15 @@ class PlanGeneratorService:
def _create_clips_from_configs(
self,
plan_id: str,
clip_configs: list[TemplateClipConfig],
) -> list[EditPlanClip]:
clip_configs: List[TemplateClipConfig],
) -> List[EditPlanClip]:
"""从 TemplateClipConfig 列表创建 EditPlanClip 列表(未持久化).
委托给 plan_generator_utils.create_clips_from_configs 纯函数。
"""
return create_clips_from_configs(plan_id, clip_configs)
def _map_clip_types_for_mode(self, clips: list[EditPlanClip], editing_mode: str) -> None:
def _map_clip_types_for_mode(self, clips: List[EditPlanClip], editing_mode: str) -> None:
"""将 MAIN 类型片段按 editing_mode 映射为对应角色类型.
委托给 plan_generator_utils.map_clip_types_for_mode 纯函数。
@@ -204,7 +204,7 @@ class PlanGeneratorService:
plan_id: str,
editing_mode: str,
asset_count: int,
) -> list[EditPlanClip]:
) -> List[EditPlanClip]:
"""无 clip_configs 时,根据 editing_mode 生成默认 clip 结构.
委托给 plan_generator_utils.generate_default_clips 纯函数。
@@ -213,8 +213,8 @@ class PlanGeneratorService:
def _distribute_assets(
self,
clips: list[EditPlanClip],
asset_ids: list[str],
clips: List[EditPlanClip],
asset_ids: List[str],
editing_mode: str,
*,
random_selection: bool = False,
@@ -259,7 +259,7 @@ class PlanGeneratorService:
external_used_segments=external_used_segments,
)
def _fetch_asset_scene_points(self, asset_ids: list[str]) -> dict[str, list[float]]:
def _fetch_asset_scene_points(self, asset_ids: List[str]) -> dict[str, list[float]]:
"""从素材 metadata 读取场景切换点缓存(无缓存的素材不包含在结果中)。"""
points_map: dict[str, list[float]] = {}
if not self._asset_repo:
@@ -272,7 +272,7 @@ class PlanGeneratorService:
points_map[asset_id] = points
return points_map
def _sort_assets_by_smart_score(self, asset_ids: list[str]) -> list[str]:
def _sort_assets_by_smart_score(self, asset_ids: List[str]) -> List[str]:
"""按 smart_match 综合评分降序排列素材 ID(注入随机噪声)。
评分高的素材(质量好、时长合适、新鲜、使用次数少)倾向排在前面;
@@ -295,7 +295,7 @@ class PlanGeneratorService:
)
return [aid for aid, _ in scored]
def _fetch_asset_durations(self, asset_ids: list[str]) -> dict[str, float]:
def _fetch_asset_durations(self, asset_ids: List[str]) -> dict[str, float]:
"""从数据库获取素材时长信息.
Args:
@@ -1,59 +0,0 @@
"""文案提取 ASR 服务封装 — Issue #1893.
将已有的 ASR 服务工厂封装为面向文案提取场景的简单接口:
- transcribe_to_text(video_path) -> str:将视频/音频转写为纯文本
- 未配置 ASR 时抛 ASRNotConfiguredError(路由层映射为 503)
- ASR 调用失败时抛 ASRTranscriptionError(路由层映射为 502)
"""
from __future__ import annotations
import logging
from pathlib import Path
from packages.ports.asr_service import ASRServiceError
logger = logging.getLogger(__name__)
class ASRNotConfiguredError(Exception):
"""ASR 服务未配置."""
class ASRTranscriptionError(Exception):
"""ASR 转写失败."""
def transcribe_to_text(media_path: str | Path) -> str:
"""将视频/音频文件转写为纯文本.
Args:
media_path: 媒体文件路径
Returns:
转写出的文本
Raises:
ASRNotConfiguredError: ASR 服务未配置
ASRTranscriptionError: ASR 调用失败
"""
# 延迟导入,避免循环依赖和启动时副作用
from apps.worker.services.asr_service_factory import get_asr_service
asr = get_asr_service()
if asr is None:
raise ASRNotConfiguredError("ASR 服务未配置,请联系管理员配置火山 MediaKit 或阿里云 ASR 密钥")
try:
timeline = asr.transcribe(Path(media_path))
# 拼接所有分段的文本
text = "".join(seg.text for seg in timeline.segments)
return text.strip()
except ASRNotConfiguredError:
raise
except ASRServiceError as exc:
logger.error("ASR 转写失败: %s", exc)
raise ASRTranscriptionError(f"语音识别失败: {exc}") from exc
except Exception as exc:
logger.error("ASR 转写异常: %s", exc)
raise ASRTranscriptionError(f"语音识别失败: {exc}") from exc
+2 -2
View File
@@ -6,7 +6,7 @@
from __future__ import annotations
import uuid
from datetime import UTC, datetime
from datetime import datetime, timezone
from typing import Optional
from sqlalchemy.orm import Session
@@ -93,7 +93,7 @@ class ScriptService:
script.segments = segments
if tags is not None:
script.tags = tags
script.updated_at = datetime.now(UTC)
script.updated_at = datetime.now(timezone.utc)
self.db.commit()
self.db.refresh(script)
return script
+7 -113
View File
@@ -20,7 +20,7 @@ _estimate_sentence_timings_by_chars 等内部函数已复用共享实现,避
import io
import logging
from datetime import UTC, datetime
from datetime import datetime, timezone
from urllib.parse import urlparse
from celery import shared_task
@@ -160,14 +160,13 @@ def tts_synthesize_and_submit(
voice_id=voice_id,
speed=speed,
emotion=emotion,
language="zh",
)
except CosyVoiceError as exc:
logger.error("[lipsync_tts] TTS 合成失败: job_id=%s err=%s", job_id, exc)
job.status = "failed"
job.error_message = f"TTS 合成失败: {exc}"
job.error_code = "TTSSynthesisFailed"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
db.commit()
return
except ValueError as exc:
@@ -175,7 +174,7 @@ def tts_synthesize_and_submit(
job.status = "failed"
job.error_message = f"TTS 参数错误: {exc}"
job.error_code = "TTSInvalidParam"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
db.commit()
return
@@ -185,7 +184,7 @@ def tts_synthesize_and_submit(
job.status = "failed"
job.error_message = "TTS 未返回音频 URL"
job.error_code = "TTSNoAudio"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
db.commit()
return
@@ -271,7 +270,7 @@ def tts_synthesize_and_submit(
)
job.mediakit_task_id = mk_result["task_id"]
job.status = "submitted"
job.submitted_at = datetime.now(UTC)
job.submitted_at = datetime.now(timezone.utc)
logger.info(
"[lipsync_tts] 已提交 MediaKit: job_id=%s task_id=%s",
job_id,
@@ -283,18 +282,6 @@ def tts_synthesize_and_submit(
job.error_code = exc.code
logger.error("[lipsync_tts] 提交 MediaKit 失败: job_id=%s err=%s", job_id, exc)
# 三层防御 ③:链式触发 Celery 兜底轮询——MediaKit 提交成功后由 worker
# 主动拉取状态到终态,不依赖前端轮询触发的 FastAPI background task
# (background task 可能静默失败导致永久卡 running)。
if job.status == "submitted" and job.mediakit_task_id:
try:
poll_mediakit_status.apply_async(
kwargs={"job_id": job_id, "user_id": user_id},
countdown=10, # 10 秒后开始轮询,给 MediaKit 一点处理时间
)
except Exception as exc: # noqa: BLE001
logger.warning("[lipsync_tts] 提交兜底轮询任务失败(不影响主流程): job_id=%s err=%s", job_id, exc)
db.commit()
except Exception:
@@ -305,7 +292,7 @@ def tts_synthesize_and_submit(
job.status = "failed"
job.error_message = "TTS 异步任务执行异常"
job.error_code = "AsyncTaskError"
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
db.commit()
except Exception:
logger.exception("[lipsync_tts] 回写失败状态时异常: job_id=%s", job_id)
@@ -313,99 +300,6 @@ def tts_synthesize_and_submit(
db.close()
@shared_task(
bind=True,
name="lipsync_tts.poll_mediakit_status",
max_retries=60, # 最多轮询 60 次
default_retry_delay=10, # 每次间隔 10 秒(总兜底时长 10 分钟)
)
def poll_mediakit_status(self, job_id: str, user_id: str):
"""Celery 兜底轮询:TTS 提交 MediaKit 后,由 worker 主动拉取状态直到终态。
不依赖前端轮询,避免 background task 静默失败导致任务永久卡 running/submitted。
"""
from sqlalchemy.orm import Session as DBSession
from packages.adapters.sqlalchemy_impl.models import LipsyncJobModel
try:
from worker_app.db import SessionLocal # type: ignore
except Exception: # noqa: BLE001
from app.db import SessionLocal # type: ignore
db: DBSession = SessionLocal()
try:
job = db.query(LipsyncJobModel).filter(LipsyncJobModel.id == job_id, LipsyncJobModel.user_id == user_id).first()
if job is None:
logger.warning("[lipsync_poll] Job not found: job_id=%s", job_id)
return
# 已终态,不需要再轮询
if job.status in ("completed", "failed", "cancelled"):
return
if not job.mediakit_task_id:
logger.warning("[lipsync_poll] Job has no mediakit_task_id: job_id=%s status=%s", job_id, job.status)
return
from app.services.lipsync_service import STATUS_COMPLETED as _SC
from app.services.lipsync_service import STATUS_FAILED as _SF
from app.services.lipsync_service import LipsyncService
from app.services.mediakit_client import MediaKitError, get_mediakit_client
client = get_mediakit_client()
try:
status_data = client.get_task_status(job.mediakit_task_id)
except MediaKitError as exc:
logger.warning("[lipsync_poll] 拉取 MediaKit 状态失败,将重试: job_id=%s err=%s", job_id, exc)
raise self.retry(exc=exc) from exc
mk_status = status_data.get("status", "running")
if mk_status in ("succeeded", _SC):
svc = LipsyncService(db)
result = status_data.get("result", {})
job.status = "completed"
output_url = result.get("video_url", "")
try:
job.output_video_url = svc._persist_output_video(output_url, job_id, user_id)
except Exception as exc: # noqa: BLE001
logger.warning("[lipsync_poll] 转存 OSS 失败,保留临时 URL: job_id=%s err=%s", job_id, exc)
job.output_video_url = output_url
job.output_duration = result.get("duration", 0.0)
job.completed_at = datetime.now(UTC)
job.updated_at = datetime.now(UTC)
db.commit()
logger.info("[lipsync_poll] 任务完成: job_id=%s", job_id)
elif mk_status in ("failed", "error", _SF):
error = status_data.get("error", {})
job.status = "failed"
job.error_message = error.get("message", "任务执行失败")
job.error_code = error.get("code", "TaskFailed")
job.completed_at = datetime.now(UTC)
job.updated_at = datetime.now(UTC)
db.commit()
logger.info("[lipsync_poll] 任务失败: job_id=%s err=%s", job_id, job.error_message)
else:
# 中间状态,更新时间戳,继续重试
job.updated_at = datetime.now(UTC)
if isinstance(mk_status, str) and mk_status:
job.status = mk_status
db.commit()
logger.debug("[lipsync_poll] 任务仍在 %s,继续轮询: job_id=%s", mk_status, job_id)
raise self.retry()
except Exception as exc:
logger.exception("[lipsync_poll] 未预期异常: job_id=%s", job_id)
try:
db.rollback()
except Exception:
pass
raise self.retry(exc=exc) from exc
finally:
db.close()
@shared_task(
name="lipsync_tts.persist_output_video",
max_retries=2,
@@ -446,7 +340,7 @@ def persist_output_video_task(job_id: str, user_id: str, temp_url: str):
permanent_url = storage.upload_file(io.BytesIO(data), storage_key, content_type="video/mp4")
final_url = _sign_media_url(permanent_url) if permanent_url else temp_url
job.output_video_url = final_url
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
db.commit()
logger.info("[lipsync_tts.persist] 输出视频已转存 OSS: job_id=%s key=%s", job_id, storage_key)
except Exception as exc:
+23 -11
View File
@@ -40,6 +40,7 @@ async function loginWithRetry(
type ProjectResponse = { id: string }
type LibraryResponse = { id: string }
type TemplateResponse = { id: string }
type AssetListResponse = {
items: Array<{
id: string
@@ -125,17 +126,28 @@ test.describe("Core generation flow", () => {
)
.toBe("ready")
// #1926 P0 fix: POST /templates CRUD endpoint removed; GET /templates
// now auto-creates a default template for new users. Use the first one.
const templatesResp = await request.get(`${apiBase}/templates`, { headers })
expect(templatesResp.status(), await templatesResp.text()).toBe(200)
const templatesData = (await templatesResp.json()) as {
items: Array<{ id: string }>
}
expect(Array.isArray(templatesData.items)).toBe(true)
expect(templatesData.items.length).toBeGreaterThan(0)
const templateId = templatesData.items[0].id
expect(templateId).toBeTruthy()
// Create an editing template so the generate page has at least one template
// (templates are now loaded from API; new users have none by default)
const template = await request.post(`${apiBase}/templates`, {
headers,
data: {
name: `E2E 测试模板 ${suffix}`,
mode: "pip",
estimated_duration: 30,
segments: [
{
segment_order: 1,
duration_min: 5,
duration_max: 30,
material_type: "video",
},
],
tags: ["e2e"],
},
})
expect(template.status(), await template.text()).toBe(201)
const templateData = (await template.json()) as TemplateResponse
expect(templateData.id).toBeTruthy()
// Set auth in localStorage
await page.addInitScript(
-477
View File
@@ -1,477 +0,0 @@
/**
* 积分 & 会员 API 封装(v2 两档会员制)
* 后端接口未就绪阶段使用 mock 数据;接口就绪后把 MOCK 开关关掉即可。
*/
import apiClient from "../client"
import type {
PointsBalance,
PointsTransaction,
PointsTransactionsParams,
PointsTransactionsResponse,
PointsPackagesResponse,
PointsRechargeRequest,
PointsOrder,
PointsRulesResponse,
PointsCheckRequest,
PointsCheckResponse,
SubscriptionCurrent,
SubscribeRequest,
SubscriptionPlan,
} from "./types"
/**
* 是否启用 mock 数据(后端 PR 合入前为 true;对接真实接口后改为 false)
*/
export const POINTS_API_MOCK = true
// ==================== Mock 数据 ====================
const MOCK_BALANCE: PointsBalance = {
balance: 580,
total_earned: 1200,
total_spent: 620,
is_member: false,
member_type: null,
member_expires_at: null,
free_clips_used: 1,
free_clips_limit: 2,
free_clips_remaining: 1,
}
const MOCK_PACKAGES: PointsPackagesResponse = {
packages: [
{
id: "starter_pack",
name: "体验包",
points: 100,
price: 990,
discounted_price_for_free: 990,
discounted_price_for_monthly: 891,
discounted_price_for_quarterly: 861,
discounted_price_for_yearly: 792,
},
{
id: "basic_pack",
name: "基础包",
points: 500,
price: 3900,
discounted_price_for_free: 3900,
discounted_price_for_monthly: 3510,
discounted_price_for_quarterly: 3393,
discounted_price_for_yearly: 3120,
},
{
id: "pro_pack",
name: "专业包",
points: 2000,
price: 12900,
discounted_price_for_free: 12900,
discounted_price_for_monthly: 11610,
discounted_price_for_quarterly: 11223,
discounted_price_for_yearly: 10320,
},
],
user_member_type: "free",
unit_price_yuan: 0.1,
}
const MOCK_RULES: PointsRulesResponse = {
rules: [
{
scene_key: "ai_voice",
scene_name: "AI 配音",
points_per_use: 1,
unit: "分钟",
description: "每生成 1 分钟配音",
},
{
scene_key: "ai_video",
scene_name: "智能混剪",
points_per_use: 3,
unit: "条",
extra_per_30s: 1,
description: "每条 ≤30s 3 积分,每加 30s +1",
},
{
scene_key: "ai_digital_human",
scene_name: "AI 数字人",
points_per_use: 15,
unit: "分钟",
description: "每生成 1 分钟口播",
},
{
scene_key: "voice_clone_train",
scene_name: "声音克隆训练",
points_per_use: 0,
unit: "次",
description: "训练免费",
},
{ scene_key: "voice_clone_synth", scene_name: "声音克隆合成", points_per_use: 1, unit: "分钟" },
{ scene_key: "douyin_extract", scene_name: "抖音链接提取", points_per_use: 1, unit: "次" },
{ scene_key: "ai_rewrite", scene_name: "AI 改写文案", points_per_use: 1, unit: "次" },
{ scene_key: "ai_title", scene_name: "AI 标题生成", points_per_use: 1, unit: "次" },
{ scene_key: "ai_cover", scene_name: "AI 封面生成", points_per_use: 1, unit: "张" },
],
free_user_multiplier: 1.15,
note: "免费用户消耗 = 会员消耗 × 1.15,向上取整",
}
function genMockTransactions(): PointsTransactionsResponse {
const now = new Date()
const list = [
{
src: "ai_voice",
name: "AI 配音",
type: "spend" as const,
amt: 1,
desc: "生成配音 1 分钟",
days: 0,
hours: 0,
mins: 30,
},
{
src: "ai_video",
name: "智能混剪",
type: "spend" as const,
amt: 5,
desc: "生成 1 分钟视频(基础3+30s*2)",
days: 0,
hours: 1,
mins: 15,
},
{
src: "task_reward",
name: "任务奖励",
type: "earn" as const,
amt: 20,
desc: "首次生成视频奖励",
days: 1,
hours: 0,
mins: 0,
},
{
src: "recharge",
name: "充值",
type: "earn" as const,
amt: 500,
desc: "基础包充值",
days: 15,
hours: 0,
mins: 0,
},
{
src: "ai_rewrite",
name: "AI 改写文案",
type: "spend" as const,
amt: 2,
desc: "免费用户价(1×1.15 向上取整)",
days: 16,
hours: 2,
mins: 10,
},
{
src: "ai_title",
name: "AI 标题生成",
type: "spend" as const,
amt: 2,
desc: "免费用户价",
days: 16,
hours: 3,
mins: 0,
},
{
src: "douyin_extract",
name: "抖音链接提取",
type: "spend" as const,
amt: 2,
desc: "提取 3 分钟文案",
days: 18,
hours: 0,
mins: 0,
},
{
src: "ai_digital_human",
name: "AI 数字人",
type: "spend" as const,
amt: 18,
desc: "数字人口播 1 分钟(免费用户价)",
days: 20,
hours: 0,
mins: 0,
},
{
src: "task_reward",
name: "任务奖励",
type: "earn" as const,
amt: 50,
desc: "注册赠送",
days: 30,
hours: 0,
mins: 0,
},
]
let bal = MOCK_BALANCE.balance
const items = list
.map((t, i) => {
const signed = t.type === "earn" ? t.amt : -t.amt
const balance_after = bal // 按时间倒序:earliest 先算
// adjust running bal
bal = t.type === "earn" ? bal - t.amt : bal + t.amt
const d = new Date(now)
d.setDate(d.getDate() - t.days)
d.setHours(d.getHours() - t.hours)
d.setMinutes(d.getMinutes() - t.mins)
return {
id: `tx_${i + 1}`,
type: t.type,
source: t.src as PointsBalance extends never ? never : string,
source_name: t.name,
amount: t.amt,
signed_amount: signed,
balance_after,
description: t.desc,
ref_id: null,
created_at: d.toISOString(),
}
})
.reverse()
// Rebuild balance_after going forward
let running = 50 + 0 // after registration gift
for (let i = items.length - 1; i >= 0; i--) {
const it = items[i] as PointsTransaction & { balance_after?: number }
if (it.source === "task_reward" && it.description.includes("注册")) running = 50
}
running = 50
const fwd = [...items].reverse() as Array<PointsTransaction & { balance_after?: number }>
for (const it of fwd) {
running += it.signed_amount
it.balance_after = running
}
return { items: fwd, total: fwd.length, page: 1, page_size: 20 } as PointsTransactionsResponse
}
// ==================== 真实 API ====================
/** 查询积分余额 + 会员状态 */
export async function getPointsBalance(): Promise<PointsBalance> {
if (POINTS_API_MOCK) {
return new Promise((r) => setTimeout(() => r({ ...MOCK_BALANCE }), 180))
}
const res = await apiClient.get("/points/balance")
return res.data
}
/** 查询积分流水(分页) */
export async function getPointsTransactions(
params: PointsTransactionsParams = {},
): Promise<PointsTransactionsResponse> {
if (POINTS_API_MOCK) {
return new Promise((r) => setTimeout(() => r(genMockTransactions()), 200))
}
const res = await apiClient.get("/points/transactions", { params })
return res.data
}
/** 查询积分包列表 */
export async function getPointsPackages(): Promise<PointsPackagesResponse> {
if (POINTS_API_MOCK) {
return new Promise((r) => setTimeout(() => r({ ...MOCK_PACKAGES }), 150))
}
const res = await apiClient.get("/points/packages")
return res.data
}
/** 创建积分充值订单(mock 阶段返回 "pending" 订单,前端弹"支付开发中") */
export async function createPointsOrder(req: PointsRechargeRequest): Promise<PointsOrder> {
if (POINTS_API_MOCK) {
const pkg = MOCK_PACKAGES.packages.find((p) => p.id === req.package_id)
const mt = MOCK_PACKAGES.user_member_type
type DiscountKey =
| "discounted_price_for_free"
| "discounted_price_for_monthly"
| "discounted_price_for_quarterly"
| "discounted_price_for_yearly"
const discountKey = `discounted_price_for_${mt}` as DiscountKey
const price = pkg?.[discountKey] ?? pkg?.price ?? 0
return new Promise((r) =>
setTimeout(
() =>
r({
id: `mock_order_${Date.now()}`,
package_id: req.package_id,
package_name: pkg?.name ?? "",
points_amount: pkg?.points ?? 0,
price_cents: price,
original_price_cents: pkg?.price ?? 0,
discount: price / (pkg?.price || 1),
currency: "CNY",
status: "pending",
payment_method: null,
payment_id: null,
paid_at: null,
expire_at: null,
created_at: new Date().toISOString(),
}),
300,
),
)
}
const res = await apiClient.post("/points/recharge", req)
return res.data
}
/** 查询积分消耗规则 */
export async function getPointsRules(): Promise<PointsRulesResponse> {
if (POINTS_API_MOCK) {
return new Promise((r) => setTimeout(() => r({ ...MOCK_RULES }), 120))
}
const res = await apiClient.get("/points/rules")
return res.data
}
/** 消费前余额预检查 */
export async function checkPoints(req: PointsCheckRequest): Promise<PointsCheckResponse> {
if (POINTS_API_MOCK) {
const rule = MOCK_RULES.rules.find((r) => r.scene_key === req.scene_key)
if (!rule) {
return {
allowed: false,
required_points: 0,
current_balance: MOCK_BALANCE.balance,
remaining_after: MOCK_BALANCE.balance,
is_free_quota: false,
code: "SCENE_NOT_FOUND",
message: "未知场景",
recharge_url: "/app/points",
}
}
const units = req.units ?? 1
let base = rule.points_per_use * units
if (rule.extra_per_30s && units > 1) {
// ai_video extra_per_30s: base already covers first 30s, subtract
base = rule.points_per_use + rule.extra_per_30s * (units - 1)
}
const isFree =
MOCK_BALANCE.is_member === false &&
req.scene_key === "ai_video" &&
(MOCK_BALANCE.free_clips_remaining ?? 0) > 0
const needed = isFree
? 0
: MOCK_BALANCE.is_member
? base
: Math.ceil(base * MOCK_RULES.free_user_multiplier)
const allowed = isFree || MOCK_BALANCE.balance >= needed
return {
allowed,
required_points: needed,
current_balance: MOCK_BALANCE.balance,
remaining_after: MOCK_BALANCE.balance - needed,
is_free_quota: isFree,
code: allowed ? undefined : "INSUFFICIENT_POINTS",
message: allowed
? undefined
: `积分不足,需要 ${needed} 积分,当前余额 ${MOCK_BALANCE.balance}`,
recharge_url: "/app/points",
}
}
const res = await apiClient.post("/points/check", req)
return res.data
}
// ==================== 订阅相关 ====================
/** 订阅套餐(定价常量,前端硬编码;折扣由后端会员类型决定) */
export const SUBSCRIPTION_PLANS: SubscriptionPlan[] = [
{
id: "monthly",
name: "月卡",
price_cents: 1990,
price_yuan: 19.9,
per_month_yuan: 19.9,
billing_label: "/月",
},
{
id: "quarterly",
name: "季卡",
price_cents: 3990,
price_yuan: 39.9,
per_month_yuan: 13.3,
savings_percent: 33,
recommended: true,
billing_label: "/季",
},
{
id: "yearly",
name: "年卡",
price_cents: 15900,
price_yuan: 159,
per_month_yuan: 13.25,
savings_percent: 33,
billing_label: "/年",
},
]
/** 查询当前订阅 */
export async function getCurrentSubscription(): Promise<SubscriptionCurrent> {
if (POINTS_API_MOCK) {
return new Promise((r) =>
setTimeout(
() =>
r({
is_member: MOCK_BALANCE.is_member,
member_type: MOCK_BALANCE.member_type,
member_type_name: MOCK_BALANCE.is_member ? "付费会员" : "免费会员",
status: MOCK_BALANCE.is_member ? "active" : "none",
current_period_start: null,
current_period_end: MOCK_BALANCE.member_expires_at,
auto_renew: false,
points_discount: MOCK_BALANCE.is_member ? 0.9 : 1.0,
}),
150,
),
)
}
const res = await apiClient.get("/subscription/current")
return res.data
}
/** 开通/续费会员 */
export async function subscribe(req: SubscribeRequest): Promise<PointsOrder> {
if (POINTS_API_MOCK) {
const plan = SUBSCRIPTION_PLANS.find((p) => p.id === req.member_type)!
return new Promise((r) =>
setTimeout(
() =>
r({
id: `mock_sub_${Date.now()}`,
package_id: plan.id,
package_name: plan.name,
points_amount: 0,
price_cents: plan.price_cents,
original_price_cents: plan.price_cents,
discount: 1,
currency: "CNY",
status: "pending",
payment_method: null,
payment_id: null,
paid_at: null,
expire_at: null,
created_at: new Date().toISOString(),
}),
300,
),
)
}
const res = await apiClient.post("/subscription/subscribe", req)
return res.data
}
/** 取消自动续费 */
export async function cancelAutoRenew(): Promise<{ success: boolean; message: string }> {
if (POINTS_API_MOCK) {
return new Promise((r) =>
setTimeout(() => r({ success: true, message: "已取消自动续费" }), 200),
)
}
const res = await apiClient.post("/subscription/cancel")
return res.data
}
-217
View File
@@ -1,217 +0,0 @@
/**
* 积分 & 会员系统 API 类型定义(v2 两档会员制)
* 接口契约对齐后端设计文档 membership-points-design-v2.md
*/
/** 会员类型 */
export type MemberType = "free" | "monthly" | "quarterly" | "yearly"
/** 积分流水类型 */
export type PointsTxType = "earn" | "spend" | "refund"
/** 积分来源/消耗场景 */
export type PointsSource =
| "recharge" // 充值
| "task_reward" // 任务奖励
| "ai_voice" // AI 配音
| "ai_digital_human" // AI 数字人
| "ai_video" // 智能混剪
| "voice_clone_train" // 声音克隆训练
| "voice_clone_synth" // 声音克隆合成
| "douyin_extract" // 抖音链接提取
| "ai_rewrite" // AI 改写文案
| "ai_title" // AI 标题生成
| "ai_cover" // AI 封面生成
| "subscription_bonus" // 会员赠送
| "admin_adjust" // 管理员调整
| "refund" // 失败退还
/** 会员 & 积分余额响应 */
export interface PointsBalance {
/** 当前可用积分 */
balance: number
/** 累计获得 */
total_earned: number
/** 累计消耗 */
total_spent: number
/** 是否付费会员(free 用户为 false) */
is_member: boolean
/** 会员类型:monthly / quarterly / yearly;free 用户为 null */
member_type: Extract<MemberType, "monthly" | "quarterly" | "yearly"> | null
/** 会员到期时间 ISO 字符串 */
member_expires_at: string | null
/** 今日免费混剪已用次数 */
free_clips_used?: number
/** 今日免费混剪额度上限 */
free_clips_limit?: number
/** 今日免费混剪剩余 */
free_clips_remaining?: number
}
/** 积分流水记录 */
export interface PointsTransaction {
id: string
/** earn / spend / refund */
type: PointsTxType
/** 来源场景 */
source: PointsSource
/** 场景中文名称 */
source_name: string
/** 变动数量(正数) */
amount: number
/** 带符号的变动数(收入+,支出-) */
signed_amount: number
/** 变动后余额 */
balance_after: number
/** 备注描述 */
description: string
/** 关联业务 ID */
ref_id: string | null
created_at: string
}
/** 积分流水分页响应 */
export interface PointsTransactionsResponse {
items: PointsTransaction[]
total: number
page: number
page_size: number
}
/** 积分流水查询参数 */
export interface PointsTransactionsParams {
page?: number
page_size?: number
type?: PointsTxType
source?: PointsSource
start_date?: string
end_date?: string
}
/** 积分包 */
export interface PointsPackage {
id: "starter_pack" | "basic_pack" | "pro_pack" | string
/** 中文名称 */
name: string
/** 积分数量 */
points: number
/** 原价(分) */
price: number
/** 各会员类型折扣价(分) */
discounted_price_for_free: number
discounted_price_for_monthly: number
discounted_price_for_quarterly: number
discounted_price_for_yearly: number
}
/** 积分包列表响应 */
export interface PointsPackagesResponse {
packages: PointsPackage[]
/** 当前用户会员类型,用于前端计算折后价 */
user_member_type: MemberType
/** 积分单价(元/积分,按会员价计) */
unit_price_yuan: number
}
/** 创建充值订单请求 */
export interface PointsRechargeRequest {
package_id: string
payment_method?: "wechat_pay" | "alipay"
}
/** 订单状态 */
export type OrderStatus = "pending" | "paid" | "failed" | "refunded" | "expired"
/** 充值订单响应 */
export interface PointsOrder {
id: string
package_id: string
package_name: string
points_amount: number
price_cents: number
original_price_cents: number
discount: number
currency: "CNY"
status: OrderStatus
payment_method: string | null
payment_id: string | null
paid_at: string | null
expire_at: string | null
created_at: string
/** 微信/支付宝支付参数(mock 阶段前端自行处理) */
pay_params?: Record<string, string>
}
/** 订阅套餐(月/季/年) */
export interface SubscriptionPlan {
id: "monthly" | "quarterly" | "yearly"
name: string
price_cents: number
price_yuan: number
per_month_yuan: number
savings_percent?: number
recommended?: boolean
billing_label: string
}
/** 当前订阅详情 */
export interface SubscriptionCurrent {
is_member: boolean
member_type: Extract<MemberType, "monthly" | "quarterly" | "yearly"> | null
member_type_name: string
status: "active" | "expired" | "cancelled" | "none"
current_period_start: string | null
current_period_end: string | null
auto_renew: boolean
/** 订阅会员对应的积分折扣 */
points_discount: number
}
/** 开通/续费订阅请求 */
export interface SubscribeRequest {
member_type: "monthly" | "quarterly" | "yearly"
payment_method?: "wechat_pay" | "alipay"
}
/** 积分消耗规则 */
export interface PointsRule {
scene_key: PointsSource
scene_name: string
/** 每次消耗基础积分(会员价) */
points_per_use: number
/** 计量单位:条/分钟/次/张 */
unit: string
/** 额外每 30s 加积分(ai_video 用) */
extra_per_30s?: number
/** 说明文案 */
description?: string
}
export interface PointsRulesResponse {
rules: PointsRule[]
/** 免费用户消耗倍率 */
free_user_multiplier: number
note: string
}
/** 消费前余额检查请求 */
export interface PointsCheckRequest {
scene_key: PointsSource
/** 单位数量(时长/条数),默认 1 */
units?: number
}
/** 消费前余额检查响应 */
export interface PointsCheckResponse {
allowed: boolean
required_points: number
current_balance: number
remaining_after: number
/** 是否走免费额度(混剪场景) */
is_free_quota: boolean
/** 拒绝原因代码 */
code?: "INSUFFICIENT_POINTS" | "FREE_QUOTA_EXCEEDED" | "SCENE_NOT_FOUND"
message?: string
/** 充值页跳转 URL */
recharge_url?: string
}
-1
View File
@@ -1,3 +1,2 @@
export * from "./scripts"
export * from "./types"
export * from "./scripts-ai"
-87
View File
@@ -1,87 +0,0 @@
/**
* 文案库 AI 能力 API(#1893)
* 三个端点均走真实后端,不参与 SCRIPTS_API_MOCK 开关。
*/
import apiClient from "../client"
/** ── 1. 从抖音视频提取文案(下载 + ASR) */
export interface ExtractFromDouyinRequest {
url: string
}
export interface ExtractFromDouyinResponse {
text: string
duration_seconds?: number
source_url?: string
}
export async function extractScriptFromDouyin(
body: ExtractFromDouyinRequest,
opts?: { signal?: AbortSignal },
): Promise<ExtractFromDouyinResponse> {
const res = await apiClient.post<ExtractFromDouyinResponse>(
"/scripts/extract-from-douyin",
body,
{
// ASR 可能较慢,给足超时
timeout: 60_000,
signal: opts?.signal,
},
)
return res.data
}
/** ── 2. AI 改写文案 */
export type RewriteStyle = "口语化" | "正式" | "活泼" | "治愈" | "励志"
export const REWRITE_STYLE_OPTIONS: { value: RewriteStyle; label: string }[] = [
{ value: "口语化", label: "口语化" },
{ value: "正式", label: "正式" },
{ value: "活泼", label: "活泼" },
{ value: "治愈", label: "治愈" },
{ value: "励志", label: "励志" },
]
export interface AiRewriteRequest {
content: string
style?: RewriteStyle
}
export interface AiRewriteResponse {
original: string
rewritten: string
style: RewriteStyle
}
export async function aiRewriteScript(
body: AiRewriteRequest,
opts?: { signal?: AbortSignal },
): Promise<AiRewriteResponse> {
const res = await apiClient.post<AiRewriteResponse>("/scripts/ai-rewrite", body, {
timeout: 60_000,
signal: opts?.signal,
})
return res.data
}
/** ── 3. AI 生成标题 */
export interface AiGenerateTitlesRequest {
content: string
count?: number
}
export interface AiGenerateTitlesResponse {
titles: string[]
}
export async function aiGenerateTitles(
body: AiGenerateTitlesRequest,
opts?: { signal?: AbortSignal },
): Promise<AiGenerateTitlesResponse> {
const res = await apiClient.post<AiGenerateTitlesResponse>(
"/scripts/ai-generate-titles",
{ content: body.content, count: body.count ?? 3 },
{
timeout: 30_000,
signal: opts?.signal,
},
)
return res.data
}
+18 -176
View File
@@ -1,195 +1,37 @@
/**
* 文案库 API(#1811 v2)
* CRUD + 搜索/分类/分页;后端未就绪时使用 mock 数据(SCRIPTS_API_MOCK=true)
* 文案库 API
* 对接后端 /api/v1/scripts(CRUD + 列表解包)
*/
import apiClient from "../client"
import type {
ScriptItem,
ScriptListParams,
ScriptListResponse,
ScriptUpsertRequest,
ScriptCategory,
CreateScriptRequest,
UpdateScriptRequest,
} from "./types"
/** 是否启用 mock(后端合入后改为 false) */
export const SCRIPTS_API_MOCK = true
// ==================== Mock 数据 ====================
const MOCK_CATEGORIES: ScriptCategory[] = [
"promo",
"vlog",
"knowledge",
"story",
"emotion",
"other",
]
const SAMPLE_TITLES: Record<ScriptCategory, string[]> = {
promo: ["新品上市限时特惠", "618大促开场", "品牌故事宣传片"],
vlog: ["周末citywalk记录", "打工人的一天", "探店vlog"],
knowledge: ["3分钟学会XX", "冷知识科普", "行业深度解读"],
story: ["励志小故事", "情感故事一则", "反转剧情"],
emotion: ["深夜emo时刻", "治愈系文案", "朋友圈金句"],
other: ["通用开场白", "节日祝福", "万能结尾"],
}
const SAMPLE_TAGS = ["热门", "新品", "节日", "情感", "干货", "搞笑", "治愈", "励志"]
function genMockScripts(): ScriptItem[] {
const items: ScriptItem[] = []
const now = Date.now()
let idx = 0
for (const cat of MOCK_CATEGORIES) {
const titles = SAMPLE_TITLES[cat]
for (let i = 0; i < titles.length; i++) {
idx++
const title = titles[i]
const content = `这是一条【${cat}】分类下的示例文案,标题为「${title}」。\n\n正文可以包含多段落,每段对应一个片段(segments)。\n\n此为 mock 数据,后端接口就绪后会自动切换为真实数据。`
const segments = content.split(/\n\n+/).filter(Boolean)
const tagCount = 1 + (idx % 3)
const tags: string[] = []
for (let t = 0; t < tagCount; t++) {
tags.push(SAMPLE_TAGS[(idx + t) % SAMPLE_TAGS.length])
}
items.push({
id: `mock_${idx}`,
title,
content,
segments,
tags,
title_text: title,
title_category: cat,
title_config: {
font: "default",
color: "#ffffff",
stroke: "#000000",
position: (["top", "center", "bottom"] as const)[idx % 3],
size: 48,
bold: idx % 2 === 0,
italic: false,
},
char_count: content.length,
use_count: Math.floor(Math.random() * 50),
created_at: new Date(now - idx * 86400_000 * 2).toISOString(),
updated_at: new Date(now - idx * 86400_000).toISOString(),
})
}
}
return items
}
const MOCK_SCRIPTS = genMockScripts()
// ==================== 真实 API ====================
/** 获取文案列表(支持分页/搜索/分类) */
export async function getScripts(params: ScriptListParams = {}): Promise<ScriptListResponse> {
if (SCRIPTS_API_MOCK) {
const page = params.page ?? 1
const pageSize = params.page_size ?? 20
let items = [...MOCK_SCRIPTS]
if (params.keyword) {
const kw = params.keyword.toLowerCase()
items = items.filter(
(s) => s.title.toLowerCase().includes(kw) || s.content.toLowerCase().includes(kw),
)
}
if (params.category && params.category !== "all") {
items = items.filter((s) => s.title_category === params.category)
}
if (params.tag) {
items = items.filter((s) => s.tags?.includes(params.tag as string))
}
const total = items.length
const start = (page - 1) * pageSize
const pageItems = items.slice(start, start + pageSize)
return new Promise((r) =>
setTimeout(() => r({ items: pageItems, total, page, page_size: pageSize }), 200),
)
}
const res = await apiClient.get<ScriptListResponse>("/scripts", { params })
return res.data
}
/** 获取单条文案详情 */
export async function getScript(id: string): Promise<ScriptItem> {
if (SCRIPTS_API_MOCK) {
const item = MOCK_SCRIPTS.find((s) => s.id === id)
return new Promise((r) => setTimeout(() => r(item ?? MOCK_SCRIPTS[0]), 120))
}
const res = await apiClient.get<ScriptItem>(`/scripts/${id}`)
return res.data
/** 获取文案列表 — 必须解包 items(后端返回 {items,total})*/
export const getScripts = async (): Promise<ScriptItem[]> => {
const response = await apiClient.get<ScriptListResponse | ScriptItem[]>("/scripts")
const data = response.data as unknown
if (Array.isArray(data)) return data
const items = (data as { items?: ScriptItem[] })?.items
return Array.isArray(items) ? items : []
}
/** 新建文案 */
export async function createScript(data: ScriptUpsertRequest): Promise<ScriptItem> {
if (SCRIPTS_API_MOCK) {
const segments =
data.segments && data.segments.length > 0
? data.segments
: data.content.split(/\n\n+/).filter(Boolean)
const item: ScriptItem = {
id: `mock_${Date.now()}`,
...data,
segments,
char_count: data.content.length,
use_count: 0,
tags: data.tags ?? [],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}
MOCK_SCRIPTS.unshift(item)
return new Promise((r) => setTimeout(() => r(item), 200))
}
const res = await apiClient.post<ScriptItem>("/scripts", data)
return res.data
export const createScript = async (data: CreateScriptRequest): Promise<ScriptItem> => {
const response = await apiClient.post<ScriptItem>("/scripts", data)
return response.data
}
/** 更新文案 */
export async function updateScript(id: string, data: ScriptUpsertRequest): Promise<ScriptItem> {
if (SCRIPTS_API_MOCK) {
const idx = MOCK_SCRIPTS.findIndex((s) => s.id === id)
const segments =
data.segments && data.segments.length > 0
? data.segments
: data.content.split(/\n\n+/).filter(Boolean)
const updated: ScriptItem = {
...MOCK_SCRIPTS[idx],
...data,
segments,
char_count: data.content.length,
tags: data.tags ?? MOCK_SCRIPTS[idx]?.tags ?? [],
updated_at: new Date().toISOString(),
}
if (idx >= 0) MOCK_SCRIPTS[idx] = updated
return new Promise((r) => setTimeout(() => r(updated), 200))
}
const res = await apiClient.put<ScriptItem>(`/scripts/${id}`, data)
return res.data
export const updateScript = async (id: string, data: UpdateScriptRequest): Promise<ScriptItem> => {
const response = await apiClient.put<ScriptItem>(`/scripts/${id}`, data)
return response.data
}
/** 删除文案 */
export async function deleteScript(id: string): Promise<void> {
if (SCRIPTS_API_MOCK) {
const idx = MOCK_SCRIPTS.findIndex((s) => s.id === id)
if (idx >= 0) MOCK_SCRIPTS.splice(idx, 1)
return new Promise((r) => setTimeout(r, 150))
}
export const deleteScript = async (id: string): Promise<void> => {
await apiClient.delete(`/scripts/${id}`)
}
/** 复制文案(返回新副本) */
export async function duplicateScript(id: string): Promise<ScriptItem> {
const orig = await getScript(id)
const copy = await createScript({
title: `${orig.title}(副本)`,
content: orig.content,
segments: orig.segments,
tags: orig.tags,
title_text: orig.title_text,
title_category: orig.title_category,
title_config: orig.title_config,
})
return copy
}
+6 -79
View File
@@ -1,97 +1,24 @@
/**
* 文案库 API — 类型定义(#1811 v2 完整字段版)
* 字段对齐后端契约:title / content / segments / tags / title_text / title_category / title_config
* 同时保留 char_count / use_count / timestamps 等展示字段
* 文案库 API — 类型定义
* 对接后端 /api/v1/scripts
*/
/** 标题配置(字体、颜色、位置、字号) */
export interface ScriptTitleConfig {
/** 字体预设 key,如 "default" / "bold" / "handwritten" */
font?: string
/** 文字颜色(CSS color) */
color?: string
/** 描边色 */
stroke?: string
/** 位置:top / center / bottom */
position?: "top" | "center" | "bottom"
/** 字号(px) */
size?: number
/** 是否加粗 */
bold?: boolean
/** 是否斜体 */
italic?: boolean
}
/** 文案分类(可枚举,也支持自定义) */
export type ScriptCategory =
| "promo" // 营销推广
| "vlog" // Vlog/日常
| "knowledge" // 知识科普
| "story" // 故事剧情
| "emotion" // 情感语录
| "other" // 其他
export const SCRIPT_CATEGORY_LABEL: Record<ScriptCategory, string> = {
promo: "营销推广",
vlog: "Vlog 日常",
knowledge: "知识科普",
story: "故事剧情",
emotion: "情感语录",
other: "其他",
}
/** 文案条目 */
export interface ScriptItem {
id: string
/** 名称(标题) */
title: string
/** 正文 */
content: string
/** 分段(按段落切分,供后端/生成步骤逐段使用) */
segments?: string[]
/** 标签(逗号分隔或数组,列表展示用 Tag) */
tags?: string[]
/** 配套标题文本(选填,"使用"跳创作页时会预填到标题) */
title_text?: string
/** 分类 */
title_category?: ScriptCategory
/** 标题样式配置(字体/颜色/位置/字号) */
title_config?: ScriptTitleConfig
/** 正文字符数(后端返回,前端用于展示) */
char_count?: number
/** 使用次数(后端返回) */
use_count?: number
char_count: number
created_at: string
updated_at?: string
}
/** 列表查询参数(支持搜索/分类/分页) */
export interface ScriptListParams {
page?: number
page_size?: number
/** 标题/正文模糊搜索 */
keyword?: string
/** 分类筛选 */
category?: ScriptCategory | "all"
/** 标签筛选 */
tag?: string
}
/** 列表响应 */
export interface ScriptListResponse {
items: ScriptItem[]
total: number
page: number
page_size: number
}
/** 创建/编辑请求 */
export interface ScriptUpsertRequest {
export interface CreateScriptRequest {
title: string
content: string
segments?: string[]
tags?: string[]
title_text?: string
title_category?: ScriptCategory
title_config?: ScriptTitleConfig
}
export type UpdateScriptRequest = Partial<CreateScriptRequest>
+12 -15
View File
@@ -85,13 +85,9 @@ export async function batchDeleteEditPlanClips(
return response.data
}
/**
* 从素材批量创建片段(追加到时间线末尾)。
* #1921 修复:templateId 为空时调用新端点 POST /clips/from-assets,避免拼出双斜杠
* `/templates//editor/clips/from-assets` 导致 404;有 templateId 时保持原路径向后兼容。
*/
/** 从素材批量创建片段(追加到时间线末尾) */
export async function createClipsFromAssets(
templateId: string | undefined | null,
templateId: string,
assetIds: string[],
clipType = "main",
requiredClipsCount?: number,
@@ -104,15 +100,16 @@ export async function createClipsFromAssets(
if (requiredClipsCount !== undefined) {
body.required_clips_count = requiredClipsCount
}
// 新端点(#1921):templateId 为空时,body 不传 template_id,由后端兜底创建默认模板
const hasTid = !!templateId
const url = hasTid ? `/templates/${templateId}/editor/clips/from-assets` : "/clips/from-assets"
// from-assets 后端会调用 MediaKit 智能选片(最长 60s),单独延长超时
const response = await apiClient.post<ClipsFromAssetsResponse>(url, body, {
timeout: 60000,
signal: opts?.signal,
// _silentErrorToast 由 api/client.ts 响应拦截器读取(抑制全局错误 toast,#1777)
...(opts?.silentErrorToast ? ({ _silentErrorToast: true } as Record<string, unknown>) : {}),
})
const response = await apiClient.post<ClipsFromAssetsResponse>(
`/templates/${templateId}/editor/clips/from-assets`,
body,
{
timeout: 60000,
signal: opts?.signal,
// _silentErrorToast 由 api/client.ts 响应拦截器读取(抑制全局错误 toast,#1777)
...(opts?.silentErrorToast ? ({ _silentErrorToast: true } as Record<string, unknown>) : {}),
},
)
return response.data
}
+1 -1
View File
@@ -105,7 +105,7 @@ export interface TTSPreviewRequest {
speed?: number
pitch?: number
language?: string
emotion?: string // 情绪参数:neutral/happy/sad/angry/surprised/fearful/disgusted(后端 normalize_emotion() 兼容旧 natural/excited/calm/friendly 与中文标签)
emotion?: string // 情绪参数:natural/excited/calm/friendly
}
/** TTS 试听响应 */
@@ -1,116 +0,0 @@
/* Header 积分徽章 & Popover 样式 */
.xx-points-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
border-radius: 999px;
border: 1px solid var(--border-color, #e5e7eb);
background: var(--bg-tertiary, #fafafa);
color: var(--text-secondary, #6b7280);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s ease;
line-height: 1;
height: 32px;
}
.xx-points-badge:hover {
border-color: var(--primary-color, #6366f1);
color: var(--primary-color, #6366f1);
background: var(--primary-soft, #eef2ff);
}
.xx-points-badge.zero {
color: #faad14;
border-color: #ffe58f;
background: #fffbe6;
}
.xx-points-badge.low {
color: #fa8c16;
border-color: #ffd591;
background: #fff7e6;
}
.xx-points-badge-icon {
font-size: 14px;
color: #8b5cf6;
}
.xx-points-badge.zero .xx-points-badge-icon,
.xx-points-badge.low .xx-points-badge-icon {
color: inherit;
}
.xx-points-badge-val {
font-variant-numeric: tabular-nums;
}
/* Popover */
.xx-points-popover {
width: 260px;
display: flex;
flex-direction: column;
gap: 12px;
}
.xx-points-popover-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.xx-points-popover-balance {
display: flex;
align-items: baseline;
gap: 6px;
}
.xx-points-icon {
color: #8b5cf6;
font-size: 18px;
}
.xx-points-value {
font-size: 22px;
font-weight: 800;
color: var(--text-primary, #111827);
font-variant-numeric: tabular-nums;
}
.xx-points-unit {
color: var(--text-secondary, #6b7280);
font-size: 12px;
}
.xx-points-warn {
margin: 0 !important;
padding: 8px 10px;
background: #fffbe6;
border-radius: var(--radius-sm, 6px);
font-size: 12px;
}
.xx-points-expire {
font-size: 12px;
display: block;
}
.xx-points-popover-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
padding: 10px;
background: var(--bg-tertiary, #fafafa);
border-radius: var(--radius-sm, 6px);
}
.xx-points-stat-val {
font-size: 16px;
font-weight: 700;
color: var(--text-primary, #111827);
margin-top: 2px;
font-variant-numeric: tabular-nums;
}
@@ -1,115 +0,0 @@
/**
* Header 右上角的积分徽章(💎 580)
* - 余额 <10 时橙色告警
* - 点击弹出 Popover:余额、会员信息、充值入口、积分明细入口
*/
import React, { useEffect } from "react"
import { Popover, Button, Tag, Space, Typography, Badge } from "antd"
import {
ThunderboltOutlined,
CrownOutlined,
RightOutlined,
WarningOutlined,
} from "@ant-design/icons"
import { useNavigate } from "react-router-dom"
import { usePointsStore } from "@/store/pointsStore"
import "./PointsBadge.css"
const { Text, Paragraph } = Typography
const PointsBadge: React.FC = () => {
const navigate = useNavigate()
const { balance, init, loading } = usePointsStore()
useEffect(() => {
if (!balance) init()
}, [balance, init])
const bal = balance?.balance ?? 0
const lowBalance = bal > 0 && bal < 10
const zero = bal === 0
const isMember = !!balance?.is_member
const memberLabel = isMember
? balance?.member_type === "yearly"
? "年卡会员"
: balance?.member_type === "quarterly"
? "季卡会员"
: balance?.member_type === "monthly"
? "月卡会员"
: "付费会员"
: "免费会员"
const popContent = (
<div className="xx-points-popover">
<div className="xx-points-popover-top">
<div className="xx-points-popover-balance">
<ThunderboltOutlined className="xx-points-icon" />
<span className="xx-points-value">{loading ? "…" : bal}</span>
<span className="xx-points-unit">积分</span>
</div>
<Tag color={isMember ? "gold" : "default"} icon={<CrownOutlined />}>
{memberLabel}
</Tag>
</div>
{(zero || lowBalance) && (
<Paragraph type="warning" className="xx-points-warn">
<WarningOutlined /> 积分不足,充值后可继续使用 AI 功能
</Paragraph>
)}
{balance?.member_expires_at && (
<Text type="secondary" className="xx-points-expire">
会员到期:{new Date(balance.member_expires_at).toLocaleDateString("zh-CN")}
</Text>
)}
<div className="xx-points-popover-stats">
<div>
<Text type="secondary">累计获得</Text>
<div className="xx-points-stat-val">+{balance?.total_earned ?? 0}</div>
</div>
<div>
<Text type="secondary">累计消耗</Text>
<div className="xx-points-stat-val">-{balance?.total_spent ?? 0}</div>
</div>
</div>
<Space direction="vertical" style={{ width: "100%" }} size={8}>
<Button
type="primary"
icon={<ThunderboltOutlined />}
block
onClick={() => navigate("/app/points")}
>
充值积分
</Button>
<Button block onClick={() => navigate("/app/points/transactions")}>
积分明细
<RightOutlined />
</Button>
{!isMember && (
<Button block type="link" onClick={() => navigate("/app/subscription")}>
<CrownOutlined /> 升级会员享折扣
</Button>
)}
</Space>
</div>
)
return (
<Popover content={popContent} placement="bottomRight" trigger="click" arrow={false}>
<button
type="button"
className={`xx-points-badge ${zero ? "zero" : lowBalance ? "low" : ""}`}
>
<Badge dot={zero} color={zero ? "#faad14" : undefined}>
<ThunderboltOutlined className="xx-points-badge-icon" />
</Badge>
<span className="xx-points-badge-val">{loading ? "…" : bal}</span>
</button>
</Popover>
)
}
export default PointsBadge
@@ -1,67 +0,0 @@
/* 功能按钮旁的积分消耗提示 */
.xx-points-cost {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--text-secondary, #6b7280);
font-weight: 500;
white-space: nowrap;
}
.xx-points-cost.free,
.xx-points-cost.free-quota {
color: #10b981;
}
.xx-points-cost.insufficient {
color: #ef4444;
}
.xx-points-gem {
font-size: 12px;
line-height: 1;
}
.xx-points-num {
font-weight: 700;
font-variant-numeric: tabular-nums;
color: inherit;
}
.xx-points-unit-label {
color: var(--text-tertiary, #9ca3af);
}
.xx-points-tag-free {
display: inline-flex;
align-items: center;
padding: 1px 6px;
border-radius: 4px;
background: #d1fae5;
color: #059669;
font-size: 11px;
font-weight: 600;
}
.xx-points-free-user-tag {
display: inline-flex;
align-items: center;
padding: 1px 6px;
border-radius: 4px;
background: #fef3c7;
color: #d97706;
font-size: 10px;
font-weight: 600;
margin-left: 4px;
}
.xx-points-desc {
color: var(--text-tertiary, #9ca3af);
font-size: 11px;
}
.xx-points-warn-icon {
color: #ef4444;
margin-left: 2px;
}
@@ -1,162 +0,0 @@
/**
* 功能操作按钮旁的"消耗积分"提示
* 例:[生成配音] 💎 -1 积分
* - 根据 scene_key 自动读取规则
* - 免费用户自动计算 ×1.15 向上取整
* - 余额不足时显示红色告警 + 充值提示
*
* 使用:<PointsCost scene="ai_voice" units={1} />
*/
import React, { useEffect, useMemo, useState } from "react"
import { Tooltip } from "antd"
import { WarningOutlined } from "@ant-design/icons"
import { usePointsStore } from "@/store/pointsStore"
import type { PointsSource } from "@/api/points/types"
import { getPointsRules } from "@/api/points"
import "./PointsCost.css"
interface Props {
/** 消耗场景 key */
scene: PointsSource
/** 单位数(分钟数/条数/张数),默认 1 */
units?: number
/** 是否显示为紧凑模式(仅图标+数字,不显示单位文字) */
compact?: boolean
/** 余额不足时,是否显示充值提示 */
showRechargeHint?: boolean
/** 自定义 class */
className?: string
}
/** 单位中文 */
const UNIT_LABEL: Record<string, string> = {
分钟: "分钟",
条: "条",
次: "次",
张: "张",
}
const PointsCost: React.FC<Props> = ({
scene,
units = 1,
compact = false,
showRechargeHint = true,
className = "",
}) => {
const { balance, init } = usePointsStore()
const [rules, setRules] = useState<Awaited<ReturnType<typeof getPointsRules>> | null>(null)
useEffect(() => {
if (!balance) init()
if (!rules) {
getPointsRules()
.then(setRules)
.catch(() => {})
}
}, [balance, init, rules])
const { cost, isFreeQuota, rule, isFreeUser, insufficient } = useMemo(() => {
if (!rules || !balance) {
return {
cost: 0,
isFreeQuota: false,
rule: null,
isFreeUser: !balance?.is_member,
insufficient: false,
}
}
const rule = rules.rules.find((r) => r.scene_key === scene)
if (!rule)
return {
cost: 0,
isFreeQuota: false,
rule: null,
isFreeUser: !balance.is_member,
insufficient: false,
}
// 免费训练不扣费
if (rule.points_per_use === 0) {
return {
cost: 0,
isFreeQuota: false,
rule,
isFreeUser: !balance.is_member,
insufficient: false,
}
}
// 智能混剪:首条30s=3分,每+30s +1
let baseCost: number
if (scene === "ai_video" && rule.extra_per_30s) {
// units 当作"30s 段数"更简单;按分钟算:minutes 个 30s 段 - 1
const segments = Math.max(1, Math.ceil(units * 2)) // 1min = 2 segments
baseCost = rule.points_per_use + rule.extra_per_30s * (segments - 1)
} else {
baseCost = rule.points_per_use * Math.max(1, units)
}
// 混剪 + 免费用户 + 今日有免费额度 → 免费
const isFree =
scene === "ai_video" && !balance.is_member && (balance.free_clips_remaining ?? 0) > 0
const isFreeUser = !balance.is_member
const cost = isFree
? 0
: isFreeUser
? Math.ceil(baseCost * rules.free_user_multiplier)
: baseCost
const insufficient = !isFree && balance.balance < cost
return { cost, isFreeQuota: isFree, rule, isFreeUser, insufficient }
}, [rules, balance, scene, units])
if (!rule || !balance) {
return <span className={`xx-points-cost ${className}`} />
}
if (rule.points_per_use === 0) {
return (
<span className={`xx-points-cost free ${className}`}>
<span className="xx-points-tag-free">免费</span>
</span>
)
}
if (isFreeQuota) {
return (
<Tooltip title={`今日免费额度剩余 ${balance.free_clips_remaining} 条,不扣积分`}>
<span className={`xx-points-cost free-quota ${className}`}>
<span className="xx-points-tag-free">免费</span>
{!compact && (
<span className="xx-points-desc">(今日剩余 {balance.free_clips_remaining} 条)</span>
)}
</span>
</Tooltip>
)
}
const unitLabel = compact
? ""
: ` /${units > 1 ? `${units}${UNIT_LABEL[rule.unit] ?? rule.unit}` : rule.unit}`
return (
<span
className={`xx-points-cost ${insufficient ? "insufficient" : ""} ${isFreeUser ? "free-user" : ""} ${className}`}
>
<span className="xx-points-gem">💎</span>
<span className="xx-points-num">-{cost}</span>
{unitLabel && <span className="xx-points-unit-label">积分{unitLabel}</span>}
{isFreeUser && !compact && (
<Tooltip title={`免费用户消耗 ×${rules?.free_user_multiplier ?? 1.15},开通会员享标准价`}>
<span className="xx-points-free-user-tag">非会员</span>
</Tooltip>
)}
{insufficient && showRechargeHint && (
<Tooltip title={`积分不足,需 ${cost} 积分,当前余额 ${balance.balance}`}>
<WarningOutlined className="xx-points-warn-icon" />
</Tooltip>
)}
</span>
)
}
export default PointsCost
+2 -80
View File
@@ -1,5 +1,4 @@
/* Phase 1 Header 样式 + 手机端适配
v2: 增加积分徽章、升级按钮、会员金冠 */
/* Phase 1 Header 样式 + 手机端适配 */
.xx-top-nav {
height: 68px;
position: sticky;
@@ -78,40 +77,10 @@
.xx-right-section {
display: flex;
align-items: center;
gap: 10px;
gap: 12px;
flex-shrink: 0;
}
/* v2: 顶部积分徽章 */
.xx-header-points {
cursor: pointer;
}
/* v2: 升级会员按钮 */
.xx-upgrade-btn {
background: linear-gradient(135deg, #f59e0b, #ef4444);
border: none;
font-weight: 700;
box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
}
.xx-upgrade-btn:hover {
background: linear-gradient(135deg, #d97706, #dc2626) !important;
color: #fff !important;
}
/* v2: 会员金冠按钮 */
.xx-crown-btn {
font-weight: 700;
color: #d97706;
padding: 2px 8px;
}
.xx-crown-btn:hover {
background: rgba(245, 158, 11, 0.1) !important;
color: #b45309 !important;
}
.xx-user-menu {
display: flex;
align-items: center;
@@ -146,29 +115,6 @@
gap: var(--space-xs);
}
/* v2: 手机端积分/会员行 */
.xx-mobile-points-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 8px 4px 16px;
margin-bottom: 12px;
border-bottom: 1px solid var(--border-color);
}
.xx-mobile-member-tag {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
font-weight: 700;
color: #d97706;
background: #fef3c7;
padding: 4px 10px;
border-radius: 999px;
}
.xx-mobile-nav-item {
display: flex;
align-items: center;
@@ -211,7 +157,6 @@
.xx-top-nav-inner {
padding: 0 12px;
gap: 6px;
}
.xx-brand-text {
@@ -232,19 +177,6 @@
.xx-username {
display: none;
}
/* 手机端:缩小升级按钮文字 */
.xx-upgrade-btn {
padding: 0 8px !important;
font-size: 12px !important;
height: 28px;
}
.xx-crown-btn {
padding: 0 4px !important;
font-size: 12px !important;
height: 28px;
}
}
/* 小屏幕平板:导航文字缩小 */
@@ -256,14 +188,4 @@
.xx-nav-links button {
font-size: 13px;
}
.xx-header-points {
transform: scale(0.9);
transform-origin: center;
}
.xx-upgrade-btn {
font-size: 12px !important;
padding: 0 10px !important;
}
}
+4 -117
View File
@@ -1,27 +1,15 @@
/**
* Phase 1 Header 重构
* 扁平化导航菜单 + 手机端汉堡菜单
* v2: 增加积分徽章、升级会员入口、我的积分入口
*/
import React, { useEffect, useState } from "react"
import { Avatar, Badge, Button, Dropdown, Drawer, Space, Tooltip } from "antd"
import {
LogoutOutlined,
SettingOutlined,
UserOutlined,
MenuOutlined,
CrownFilled,
ThunderboltOutlined,
HistoryOutlined,
WalletOutlined,
} from "@ant-design/icons"
import React, { useState } from "react"
import { Avatar, Dropdown, Drawer, Space } from "antd"
import { LogoutOutlined, SettingOutlined, UserOutlined, MenuOutlined } from "@ant-design/icons"
import { useLocation, useNavigate } from "react-router-dom"
import { useAuthStore } from "@/store/authStore"
import { useLogout } from "@/hooks/useAuth"
import type { MenuProps } from "antd"
import { NAV_ITEMS } from "@/config/navigation"
import PointsBadge from "@/components/common/PointsBadge"
import { usePointsStore } from "@/store/pointsStore"
import "./Header.css"
const Header: React.FC = () => {
@@ -31,18 +19,6 @@ const Header: React.FC = () => {
const logoutMutation = useLogout()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
// 积分与会员状态
const { balance, init } = usePointsStore()
useEffect(() => {
init()
// 每 60s 刷新一次(余额可能变化)
const t = setInterval(() => init(), 60_000)
return () => clearInterval(t)
}, [init])
const isMember = !!balance?.is_member
const memberType = balance?.member_type ?? null
/** 用户下拉菜单 */
const menuItems: MenuProps["items"] = [
{
@@ -57,30 +33,6 @@ const Header: React.FC = () => {
label: "订阅管理",
onClick: () => navigate("/app/subscription"),
},
// v2: 我的积分入口
{
key: "points-center",
icon: <ThunderboltOutlined />,
label: (
<Space>
我的积分
{balance && <span style={{ color: "#8b5cf6", fontWeight: 700 }}>{balance.balance}</span>}
</Space>
),
onClick: () => navigate("/app/points"),
},
{
key: "points-history",
icon: <HistoryOutlined />,
label: "积分明细",
onClick: () => navigate("/app/points/transactions"),
},
{
key: "recharge",
icon: <WalletOutlined />,
label: "充值积分",
onClick: () => navigate("/app/points/recharge"),
},
{ type: "divider" },
{
key: "logout",
@@ -92,6 +44,7 @@ const Header: React.FC = () => {
/** 判断导航项是否激活 */
const isActive = (path: string) => {
// 首页特殊处理:/ 和 /app/dashboard 都算激活
if (path === "/app/dashboard") {
return (
location.pathname === "/" ||
@@ -125,47 +78,6 @@ const Header: React.FC = () => {
</nav>
<div className="xx-right-section">
{/* v2: 积分徽章(顶部显示) */}
<PointsBadge />
{/* v2: 升级会员入口(仅免费用户显示) */}
{!isMember && (
<Tooltip title="升级会员解锁无限混剪、批量导出,积分 8 折起">
<Button
type="primary"
size="small"
icon={<CrownFilled />}
className="xx-upgrade-btn"
onClick={() => navigate("/app/subscription")}
>
升级会员
</Button>
</Tooltip>
)}
{/* v2: 付费会员显示金冠标识 */}
{isMember && (
<Tooltip
title={`${memberType === "yearly" ? "年卡" : memberType === "quarterly" ? "季卡" : "月卡"}会员${
balance?.member_expires_at
? ` · 到期 ${new Date(balance.member_expires_at).toLocaleDateString("zh-CN")}`
: ""
}`}
>
<Badge dot color="gold">
<Button
type="text"
size="small"
icon={<CrownFilled style={{ color: "#f59e0b" }} />}
className="xx-crown-btn"
onClick={() => navigate("/app/subscription")}
>
{memberType === "yearly" ? "年卡" : memberType === "quarterly" ? "季卡" : "会员"}
</Button>
</Badge>
</Tooltip>
)}
{/* 手机端汉堡菜单按钮 */}
<button className="xx-hamburger" type="button" onClick={() => setMobileMenuOpen(true)}>
<MenuOutlined />
@@ -191,31 +103,6 @@ const Header: React.FC = () => {
width={260}
className="xx-mobile-drawer"
>
{/* 手机端积分/会员入口 */}
{balance && (
<div className="xx-mobile-points-row">
<PointsBadge />
{!isMember ? (
<Button
size="small"
type="primary"
icon={<CrownFilled />}
onClick={() => {
navigate("/app/subscription")
setMobileMenuOpen(false)
}}
>
升级
</Button>
) : (
<span className="xx-mobile-member-tag">
<CrownFilled style={{ color: "#f59e0b" }} />{" "}
{memberType === "yearly" ? "年卡" : memberType === "quarterly" ? "季卡" : "月卡"}
</span>
)}
</div>
)}
<div className="xx-mobile-nav">
{NAV_ITEMS.map((item) => (
<button
+1 -20
View File
@@ -15,7 +15,6 @@ import {
ScanOutlined,
ControlOutlined,
CrownOutlined,
ThunderboltOutlined,
UnorderedListOutlined,
UserOutlined,
} from "@ant-design/icons"
@@ -106,18 +105,6 @@ export const NAV_ITEMS: NavItem[] = [
path: "/app/tasks",
icon: React.createElement(UnorderedListOutlined),
},
{
key: "subscription",
label: "会员订阅",
path: "/app/subscription",
icon: React.createElement(CrownOutlined),
},
{
key: "points",
label: "积分中心",
path: "/app/points",
icon: React.createElement(ThunderboltOutlined),
},
]
/** 侧边栏导航分组(Sidebar 分组列表使用) */
@@ -209,16 +196,10 @@ export const NAV_GROUPS: NavGroup[] = [
},
{
key: "subscription",
label: "会员订阅",
label: "订阅管理",
path: "/app/subscription",
icon: React.createElement(CrownOutlined),
},
{
key: "points",
label: "积分中心",
path: "/app/points",
icon: React.createElement(ThunderboltOutlined),
},
],
},
]
+1 -1
View File
@@ -55,7 +55,7 @@ export const createLipsyncJob = async (data: {
script_text?: string
/** 语速 0.5~2.0,默认 1.0(TTS 直生模式用) */
speed?: number
/** 情绪英文枚举:neutral/happy/sad/angry/surprised/fearful/disgusted(TTS 直生模式用;前端经 normalizeEmotion 归一化) */
/** 情绪英文枚举:natural/excited/calm/friendly(TTS 直生模式用) */
emotion?: string
enable_video_loop?: boolean
project_id?: string
@@ -13,8 +13,7 @@ import {
type VoiceEmotion,
type VoiceLanguage,
VOICE_EMOTION_OPTIONS,
PRESET_VOICE_LANGUAGE_OPTIONS,
CLONE_VOICE_LANGUAGE_OPTIONS,
VOICE_LANGUAGE_OPTIONS,
} from "../types"
interface PanelVoiceSelectorProps {
@@ -92,12 +91,6 @@ export function PanelVoiceSelector({
const NO_PREVIEW_TIP = "该音色暂无试听音频,请先用此音色生成一段配音后再试听"
// 系统预置音色仅支持 zh/en;克隆音色支持全语言
const languageOptions =
voiceSource === "clone" ? CLONE_VOICE_LANGUAGE_OPTIONS : PRESET_VOICE_LANGUAGE_OPTIONS
// 当前语言不在可选列表(切回预置时 ja/ko/cantonese/mandarin 失效)→ 自动回退到中文
const effectiveLanguage = languageOptions.some((o) => o.value === language) ? language : "zh"
/** 用指定 URL 真实播放(抽取公共) */
const playAudioUrl = (voiceId: string, url: string) => {
// 临时兼容:后端 /tts/preview 返回 HTTP URL,staging 是 HTTPS,Mixed Content 会阻止加载
@@ -296,12 +289,10 @@ export function PanelVoiceSelector({
<select
id="aa-voice-language"
className="aa-select"
value={effectiveLanguage}
onChange={(e) => {
onLanguageChange(e.target.value as VoiceLanguage)
}}
value={language}
onChange={(e) => onLanguageChange(e.target.value as VoiceLanguage)}
>
{languageOptions.map((opt) => (
{VOICE_LANGUAGE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
@@ -34,9 +34,9 @@ export function useAiAvatar() {
/* ── 面板2:配音库 ── */
const [voiceSource, setVoiceSource] = useState<VoiceSource>("preset")
const [selectedVoice, setSelectedVoice] = useState<UnifiedVoiceItem | null>(null)
const [emotion, setEmotion] = useState<VoiceEmotion>("neutral")
const [emotion, setEmotion] = useState<VoiceEmotion>("natural")
const [speed, setSpeed] = useState(1.0)
const [language, setLanguage] = useState<VoiceLanguage>("zh")
const [language, setLanguage] = useState<VoiceLanguage>("mandarin")
/* ── 面板3:文案 & 对口型 ── */
const [script, setScript] = useState<Script | null>(null)
+11 -28
View File
@@ -6,42 +6,25 @@ import type { AssetItem } from "@/api/assets"
/* ── 音色来源切换 ── */
export type VoiceSource = "preset" | "clone"
/* ── 情绪(对齐 CosyVoice 7 种情绪) ── */
export type VoiceEmotion =
"neutral" | "happy" | "sad" | "angry" | "surprised" | "fearful" | "disgusted"
/* ── 情绪 ── */
export type VoiceEmotion = "natural" | "excited" | "calm" | "friendly"
export const VOICE_EMOTION_OPTIONS: { value: VoiceEmotion; label: string }[] = [
{ value: "neutral", label: "自然" },
{ value: "happy", label: "开心" },
{ value: "sad", label: "难过" },
{ value: "angry", label: "生气" },
{ value: "surprised", label: "惊讶" },
{ value: "fearful", label: "恐惧" },
{ value: "disgusted", label: "厌恶" },
{ value: "natural", label: "自然" },
{ value: "excited", label: "兴奋" },
{ value: "calm", label: "沉稳" },
{ value: "friendly", label: "亲切" },
]
/* ── 语言 ── */
/** 系统预置音色支持的语言(zh/en) */
export type PresetVoiceLanguage = "zh" | "en"
/** 克隆音色支持的完整语言列表 */
export type CloneVoiceLanguage = "zh" | "en" | "ja" | "ko"
export type VoiceLanguage = PresetVoiceLanguage | CloneVoiceLanguage
export type VoiceLanguage = "mandarin" | "english" | "cantonese"
export const PRESET_VOICE_LANGUAGE_OPTIONS: { value: PresetVoiceLanguage; label: string }[] = [
{ value: "zh", label: "中文" },
{ value: "en", label: "English" },
export const VOICE_LANGUAGE_OPTIONS: { value: VoiceLanguage; label: string }[] = [
{ value: "mandarin", label: "普通话" },
{ value: "english", label: "English" },
{ value: "cantonese", label: "粤语" },
]
export const CLONE_VOICE_LANGUAGE_OPTIONS: { value: CloneVoiceLanguage; label: string }[] = [
{ value: "zh", label: "中文" },
{ value: "en", label: "English" },
{ value: "ja", label: "日本語" },
{ value: "ko", label: "한국어" },
]
/** 默认(预置音色)语言选项 */
export const VOICE_LANGUAGE_OPTIONS = PRESET_VOICE_LANGUAGE_OPTIONS
/* ── 对口型任务状态 ── */
export type LipsyncStatus = "idle" | "pending" | "processing" | "completed" | "failed"
+10 -39
View File
@@ -6,50 +6,21 @@
*/
import type { AiAvatarTitleConfig, AiAvatarCoverConfig, VoiceEmotion } from "../types"
/* ── 情绪:中文/旧枚举 → CosyVoice 7 种英文枚举 ── */
const EMOTION_ALIAS: Record<string, VoiceEmotion> = {
// 新英文枚举
neutral: "neutral",
happy: "happy",
sad: "sad",
angry: "angry",
surprised: "surprised",
fearful: "fearful",
disgusted: "disgusted",
// 旧英文枚举(4 种,向前兼容)
natural: "neutral",
excited: "happy",
calm: "neutral",
friendly: "happy",
// 中文
自然: "neutral",
开心: "happy",
难过: "sad",
生气: "angry",
惊讶: "surprised",
恐惧: "fearful",
厌恶: "disgusted",
// 旧中文
兴奋: "happy",
沉稳: "neutral",
亲切: "happy",
/* ── 情绪:中文 → 英文(防御性映射;state 默认已是英文) ── */
const EMOTION_ZH_TO_EN: Record<string, VoiceEmotion> = {
自然: "natural",
兴奋: "excited",
沉稳: "calm",
亲切: "friendly",
}
const VALID_EMOTIONS: VoiceEmotion[] = [
"neutral",
"happy",
"sad",
"angry",
"surprised",
"fearful",
"disgusted",
]
const VALID_EMOTIONS: VoiceEmotion[] = ["natural", "excited", "calm", "friendly"]
/** 归一化为后端英文枚举 neutral/happy/sad/angry/surprised/fearful/disgusted;非法/空值回退 neutral。 */
/** 归一化为后端英文枚举 natural/excited/calm/friendly;非法/空值回退 natural。 */
export function normalizeEmotion(raw: string | undefined | null): VoiceEmotion {
if (!raw) return "neutral"
if (!raw) return "natural"
const v = raw.trim()
if ((VALID_EMOTIONS as string[]).includes(v)) return v as VoiceEmotion
return EMOTION_ALIAS[v] ?? "neutral"
return EMOTION_ZH_TO_EN[v] ?? "natural"
}
/* ── 标题:前端 state → 后端 build_title_drawtext_filter 字段(单个 title_config dict) ── */
@@ -28,21 +28,13 @@ import { useVariantVoicePreview } from "./hooks/useVariantVoicePreview"
import { useTitleStyleUpdaters } from "./hooks/useStep4Title/useTitleStyleUpdaters"
import { getAssetsByKind } from "@/api/assets"
import { previewTts } from "@/api/tts"
import { usePointsStore } from "@/store/pointsStore"
import { hasEnoughPoints } from "./hooks/pointsCost"
import "./generate.css"
import "./generate-points.css"
const GeneratePage: React.FC = () => {
const navigate = useNavigate()
/* ── 表单状态 ── */
const formState = useGenerateFormState()
/* ── 积分状态 ── */
const { balance, init: initPoints } = usePointsStore()
useEffect(() => {
initPoints()
}, [initPoints])
const {
currentStep,
setCurrentStep,
@@ -356,13 +348,6 @@ const GeneratePage: React.FC = () => {
/* ── 步骤3「确认生成视频」:校验通过 → 创建正式生成任务 → 跳步骤4看实时进展 ── */
const handleConfirmGenerate = useCallback(async () => {
// 积分预检查
const units = isBatch ? Math.max(selectedVariantIds.length, 1) : 1
const check = hasEnoughPoints(balance ?? null, units)
if (!check.sufficient) {
message.error(check.reason ?? "积分不足,请充值")
return
}
if (isBatch) {
if (selectedVariantIds.length === 0) {
message.warning("请至少勾选一个视频")
@@ -396,7 +381,6 @@ const GeneratePage: React.FC = () => {
previewReady,
handleGenerate,
setCurrentStep,
balance,
])
/* ── 步骤导航 ── */
@@ -420,14 +404,6 @@ const GeneratePage: React.FC = () => {
return "xx-generate-layout full-width"
}, [currentStep])
/* ── 积分消耗估算(步骤3确认生成展示用) ── */
const unitsForCost = isBatch ? Math.max(selectedVariantIds.length, 1) : 1
const pointsEstimate = useMemo(
() => hasEnoughPoints(balance ?? null, unitsForCost),
[unitsForCost, balance],
)
const insufficientPoints = !pointsEstimate.sufficient
/* ================================================================
渲染
================================================================ */
@@ -510,7 +486,6 @@ const GeneratePage: React.FC = () => {
onSelectedMaterialsChange={setSelectedMaterials}
smartSelectedIds={smartSelectedIds}
onSmartSelectedIdsChange={setSmartSelectedIds}
selectedTemplate={selectedTemplate}
titleSettings={titleSettings}
onTitleSettingsChange={setTitleSettings}
onUpdatePosition={styleUpdaters.updatePosition}
@@ -631,11 +606,6 @@ const GeneratePage: React.FC = () => {
generated={generated}
generateError={generateError}
selectedCount={isBatch ? selectedVariantIds.length : 1}
estimatedCost={pointsEstimate.cost}
freeClipsUsedThisTime={pointsEstimate.freeClipsUsed ?? 0}
pointsInsufficient={insufficientPoints}
insufficientReason={pointsEstimate.reason}
onRecharge={() => navigate("/app/points/recharge")}
/>
</div>
</div>
@@ -1,38 +1,24 @@
/**
* GeneratePage 步骤底部操作按钮(Issue #1677 修正:固定 6 步)
* v2: 步骤4 按钮前显示本次积分消耗;积分不足时禁用按钮并提示充值
* GeneratePage 步骤底部操作按钮(#1899 简化为 5 步)
*
* 步骤 1~3:上一步 / 下一步
* 步骤 4(选择标题):「✨ 确认生成视频 / 确认生成 N 个视频」→ 创建正式生成任务,成功后跳步骤5
* 步骤 5(确认生成):渲染进度页,全部完成后「下一步:选择封面」;仅上一步
* 步骤 6(选择封面):仅上一步
* 步骤 1~2:上一步 / 下一步
* 步骤 3(选择标题):「✨ 确认生成视频 / 确认生成 N 个视频」→ 创建正式生成任务,成功后跳步骤4
* 步骤 4(确认生成):渲染进度页,全部完成后「下一步:选择封面」;仅上一步
* 步骤 5(选择封面):仅上一步
*/
import React from "react"
import { Tooltip } from "antd"
import { ThunderboltOutlined, ExclamationCircleOutlined } from "@ant-design/icons"
export interface GenerateStepActionsProps {
currentStep: number
onPrev: () => void
onNext: () => void
/** 步骤4:确认生成视频(校验 + 创建渲染任务) */
/** 步骤3:确认生成视频(校验 + 创建渲染任务) */
onConfirmGenerate: () => void | Promise<void>
generating: boolean
generated: boolean
generateError: string | null
/** 批量模式下勾选的视频数量(N=1 时为1) */
selectedCount?: number
// v2: 积分相关
/** 本次预估消耗积分 */
estimatedCost?: number
/** 是否积分不足 */
pointsInsufficient?: boolean
/** 积分不足原因 */
insufficientReason?: string
/** 剩余免费混剪次数 */
freeClipsUsedThisTime?: number
/** 前往充值 */
onRecharge?: () => void
}
const GenerateStepActions: React.FC<GenerateStepActionsProps> = ({
@@ -44,15 +30,10 @@ const GenerateStepActions: React.FC<GenerateStepActionsProps> = ({
generated,
generateError,
selectedCount = 1,
estimatedCost,
pointsInsufficient,
insufficientReason,
freeClipsUsedThisTime,
onRecharge,
}) => {
const renderPrimaryButton = () => {
/* 步骤 1~3:上一步 / 下一步 */
if (currentStep < 4) {
/* 步骤 1~2:上一步 / 下一步 */
if (currentStep < 3) {
return (
<button className="xx-btn xx-btn-primary" onClick={onNext}>
下一步 →
@@ -60,20 +41,8 @@ const GenerateStepActions: React.FC<GenerateStepActionsProps> = ({
)
}
/* 步骤 4:选择标题 — 确认生成 */
if (currentStep === 4) {
const costTag =
typeof estimatedCost === "number" && estimatedCost > 0 ? (
<span className="xx-step-cost-tag">
<ThunderboltOutlined />
{estimatedCost}
</span>
) : typeof estimatedCost === "number" &&
estimatedCost === 0 &&
typeof freeClipsUsedThisTime === "number" ? (
<span className="xx-step-cost-tag free">使用 {freeClipsUsedThisTime} 次免费额度</span>
) : null
/* 步骤 3:选择标题 — 确认生成 */
if (currentStep === 3) {
if (generating) {
return (
<button className="xx-btn xx-btn-primary" disabled>
@@ -85,41 +54,18 @@ const GenerateStepActions: React.FC<GenerateStepActionsProps> = ({
return (
<button className="xx-btn xx-btn-primary" onClick={onConfirmGenerate}>
🔄 重新生成
{costTag}
</button>
)
}
if (pointsInsufficient) {
return (
<Tooltip title={insufficientReason ?? "积分不足,请充值"}>
<span>
<button className="xx-btn xx-btn-primary" disabled>
<ExclamationCircleOutlined /> 积分不足
{costTag}
</button>
{onRecharge && (
<button
className="xx-btn xx-btn-ghost"
style={{ marginLeft: 8, color: "#7c3aed", borderColor: "#c4b5fd" }}
onClick={onRecharge}
>
去充值 →
</button>
)}
</span>
</Tooltip>
)
}
return (
<button className="xx-btn xx-btn-primary" onClick={onConfirmGenerate}>
{selectedCount > 1 ? `✨ 确认生成 ${selectedCount} 个视频` : "✨ 确认生成视频"}
{costTag}
</button>
)
}
/* 步骤 5:确认生成进度页 — 全部完成后下一步进封面 */
if (currentStep === 5) {
/* 步骤 4:确认生成进度页 — 全部完成后下一步进封面 */
if (currentStep === 4) {
if (generated) {
return (
<button className="xx-btn xx-btn-primary" onClick={onNext}>
@@ -134,7 +80,7 @@ const GenerateStepActions: React.FC<GenerateStepActionsProps> = ({
)
}
/* 步骤 6(封面,最后一步):无主按钮 */
/* 步骤 5(封面,最后一步):无主按钮 */
return null
}
@@ -1,5 +1,5 @@
/**
* GeneratePage 步骤内容渲染(#1899 简化为 5 步,#1913 传递 selectedTemplate)
* GeneratePage 步骤内容渲染(#1899 简化为 5 步)
* 步骤顺序:素材(1) → 配音(2) → 标题(3) → 确认生成(4) → 封面(5)
* 步骤3预览(Canvas 网格)与步骤4进度(批量渲染网格)由 GeneratePage 直接渲染在左侧大区域。
*/
@@ -27,8 +27,6 @@ export interface GenerateStepContentProps {
onSelectedMaterialsChange: (ids: string[]) => void
smartSelectedIds: string[]
onSmartSelectedIdsChange: (ids: string[]) => void
/* 当前选中的模板/草稿 ID;空串时由后端自动兜底(#1913) */
selectedTemplate?: string
/* 标题 */
titleSettings: TitleSettings
onTitleSettingsChange: (settings: TitleSettings) => void
@@ -89,7 +87,6 @@ export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) =
onSelectedMaterialsChange,
smartSelectedIds,
onSmartSelectedIdsChange,
selectedTemplate,
titleSettings,
onTitleSettingsChange,
onUpdatePosition,
@@ -127,15 +124,6 @@ export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) =
selectedVariantIds,
} = props
// #1913:包装 onServerClipsChange,适配 hook 的 (clips, templateId?) 签名
// 如果 hook 传回了后端兜底创建的 templateId,同时通知外层更新 selectedTemplate
const handleClipsChange = React.useCallback(
(clips: EditPlanClip[], _templateId?: string) => {
onServerClipsChange(clips)
},
[onServerClipsChange],
)
switch (currentStep) {
case 1:
return (
@@ -148,8 +136,7 @@ export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) =
onSmartSelectedIdsChange={onSmartSelectedIdsChange}
clipCount={clipCount}
onClipCountChange={onClipCountChange}
selectedTemplate={selectedTemplate}
onServerClipsChange={handleClipsChange}
onServerClipsChange={onServerClipsChange}
/>
)
case 2:
@@ -1,5 +1,5 @@
/**
* Step 1 素材选择组件(#1899 原 Step2 前移,#1913 传递 selectedTemplate)
* Step 1 素材选择组件(#1899 原 Step2 前移)
* 新增片段数量输入,系统按片段数智能匹配素材
*/
import React from "react"
@@ -20,24 +20,12 @@ interface Step2MaterialSelectProps {
/** 片段数量(#1899) */
clipCount: number
onClipCountChange: (n: number) => void
/** 当前选中的模板/草稿 ID;空字符串时由后端自动兜底(#1913) */
selectedTemplate?: string
/** 服务端 clips 创建成功后的回调 */
onServerClipsChange?: (clips: EditPlanClip[]) => void
}
const Step2MaterialSelect: React.FC<Step2MaterialSelectProps> = (props) => {
const m = useStep2Materials({
materialMode: props.materialMode,
onMaterialModeChange: props.onMaterialModeChange,
selectedMaterials: props.selectedMaterials,
onSelectedMaterialsChange: props.onSelectedMaterialsChange,
smartSelectedIds: props.smartSelectedIds,
onSmartSelectedIdsChange: props.onSmartSelectedIdsChange,
clipCount: props.clipCount,
selectedTemplate: props.selectedTemplate,
onServerClipsChange: props.onServerClipsChange,
})
const m = useStep2Materials(props)
return (
<div className="xx-form-section">
@@ -1,31 +0,0 @@
/* Points cost badge inside step actions button */
.xx-step-cost-tag {
display: inline-flex;
align-items: center;
gap: 4px;
margin-left: 8px;
padding: 2px 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 999px;
font-size: 12px;
font-weight: 600;
vertical-align: middle;
}
.xx-step-cost-tag.free {
background: rgba(16, 185, 129, 0.2);
color: #d1fae5;
}
.xx-step-cost-warning {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 14px;
background: #fef3c7;
border: 1px solid #fde68a;
border-radius: 10px;
color: #92400e;
font-size: 13px;
margin-bottom: 10px;
}
@@ -1,83 +0,0 @@
/**
* 生成前积分消耗估算与余额校验
* 用于步骤4「确认生成」按钮前展示本次消耗,积分不足时禁用并提示充值
*/
import type { PointsBalance } from "@/api/points/types"
/** 生成单条视频消耗积分(基准) */
export const BASE_VIDEO_POINTS = 3
/**
* 估算生成任务的积分消耗
* @param videoCount 视频条数(批量模式)
* @param memberMultiplier 会员倍率(免费用户 1.15)
*/
export function estimateGenerateCost(videoCount: number, memberMultiplier = 1): number {
const raw = BASE_VIDEO_POINTS * videoCount * memberMultiplier
// 向上取整,避免小数
return Math.ceil(raw)
}
/**
* 判断积分是否充足(含每日免费额度)
* @returns sufficient=true 表示可以继续生成;false 需要提示充值
*/
export function hasEnoughPoints(
balance: PointsBalance | null,
videoCount: number,
): {
sufficient: boolean
cost: number
reason?: string
freeClipsUsed?: number
freeClipsRemaining?: number
} {
if (!balance) {
// 未登录或未加载:不拦截,后端会校验
return { sufficient: true, cost: estimateGenerateCost(videoCount) }
}
const isMember = balance.is_member
const multiplier = isMember ? 1 : 1.15
const cost = estimateGenerateCost(videoCount, multiplier)
// 免费用户优先使用每日免费额度
if (!isMember && balance.free_clips_remaining && balance.free_clips_remaining > 0) {
const freeUsed = Math.min(balance.free_clips_remaining, videoCount)
const remainingAfterFree = videoCount - freeUsed
const paidCost = estimateGenerateCost(remainingAfterFree, multiplier)
if (paidCost === 0) {
// 完全用免费额度
return {
sufficient: true,
cost: 0,
freeClipsUsed: freeUsed,
freeClipsRemaining: balance.free_clips_remaining - freeUsed,
}
}
if (balance.balance >= paidCost) {
return {
sufficient: true,
cost: paidCost,
freeClipsUsed: freeUsed,
freeClipsRemaining: balance.free_clips_remaining - freeUsed,
}
}
return {
sufficient: false,
cost: paidCost,
reason: `积分不足:本次需 ${paidCost} 积分(使用 ${freeUsed} 次免费额度后),当前余额 ${balance.balance},还差 ${paidCost - balance.balance} 积分`,
freeClipsUsed: freeUsed,
}
}
// 付费会员或免费额度用完
if (balance.balance >= cost) {
return { sufficient: true, cost }
}
return {
sufficient: false,
cost,
reason: `积分不足:本次需 ${cost} 积分,当前余额 ${balance.balance},还差 ${cost - balance.balance} 积分`,
}
}
@@ -1,5 +1,5 @@
/**
* 批量变体真实片段 Hook(#1744 接入 / #1750 修正,#1913 空模板兜底)
* 批量变体真实片段 Hook(#1744 接入 / #1750 修正)
*
* 批量预览(N>1)进入第 4 步时,向后端轻量接口 POST /generation/variant-plans
* 申请 N 个变体的独立剪辑计划片段:
@@ -10,16 +10,14 @@
* 因此前端按这些 clips 播放的预览与最终成片一致;
* - 正式生成时把 plan_ids 回传,后端直接关联预览 plan 渲染,不再重新选片。
*
* 错误处理(#1750 基线:严禁用前端假数据冒充预览,#1913 加友好提示):
* 错误处理(#1750 基线:严禁用前端假数据冒充预览):
* - 端点 404(后端未上线)/ 400 / 网络错误 / 超时:一律置 error 状态,
* UI 显示「预览加载失败,请重试」,不再静默降级本地模拟;
* - 单个变体 clips 为空同样视为加载失败(计划数据不完整)。
*
* N=1 不调用本 hook(单视频零回归)。
* #1913:templateId 为空时不再硬拦截,仍发 variant-plans 请求(后端自动兜底)。
*/
import { useCallback, useEffect, useRef, useState } from "react"
import { message } from "antd"
import type { EditPlanClip } from "@/api/template-editor"
import { createBatchVariantPlans, type VariantPlan } from "@/api/generation/variantPlans"
@@ -44,7 +42,6 @@ interface UseBatchVariantPlansOptions {
/** 是否启用:仅批量(count>1)且素材已选时为 true */
enabled: boolean
count: number
/** 模板 ID;空字符串时由后端自动兜底默认模板(#1913) */
templateId: string
assetIds: string[]
/** 源剪辑计划 ID(草稿/预览关联),无则空串由后端兜底最新 plan */
@@ -79,16 +76,11 @@ export function useBatchVariantPlans({
// 重试计数(state):用户点重试时 +1,驱动 effect 重新发起请求(即使指纹相同);
// 用 state 而非 ref + 直接调 load,避免 retry 与 effect 双发导致 seq 竞争
const [retryNonce, setRetryNonce] = useState(0)
/** 记录后端兜底返回的新 template_id(#1913) */
const fallbackTidRef = useRef<string>("")
/** 防止错误 toast 重复弹出 */
const errorToastShownRef = useRef(false)
const load = useCallback(async () => {
const seq = ++requestSeqRef.current
setLoading(true)
setError(false)
errorToastShownRef.current = false
try {
// 配音参数:与 useGenerateVideo 保持一致的传参逻辑
// - 独立配音模式 + voiceLibraryIds 非空:传 voice_library_ids
@@ -101,10 +93,8 @@ export function useBatchVariantPlans({
voiceParam.voice_library_id = voiceLibraryId
}
// #1913:templateId 为空时使用空串,后端自动兜底默认模板
const effectiveTid = templateId || fallbackTidRef.current
const resp = await createBatchVariantPlans({
template_id: effectiveTid,
template_id: templateId,
asset_ids: assetIds,
count,
...(sourcePlanId ? { source_edit_plan_id: sourcePlanId } : {}),
@@ -112,15 +102,6 @@ export function useBatchVariantPlans({
})
if (seq !== requestSeqRef.current) return
// #1913:从响应中提取后端可能返回的 template_id(兜底创建的新模板)
const respTid =
(resp as { template_id?: string })?.template_id ||
(resp as { edit_plan_id?: string })?.edit_plan_id ||
""
if (respTid && !templateId) {
fallbackTidRef.current = respTid
}
const items: VariantPlan[] = Array.isArray(resp.items) ? resp.items : []
const clips: EditPlanClip[][] = Array.from({ length: count }, () => [])
const planIds: string[] = Array.from({ length: count }, () => "")
@@ -144,10 +125,6 @@ export function useBatchVariantPlans({
setPlanIdsByVariant([])
setVoiceDurationsByVariant([])
setError(true)
if (!errorToastShownRef.current) {
errorToastShownRef.current = true
message.error("预览加载失败,素材可能不足,请尝试选择更多素材后重试")
}
} else {
setClipsByVariant(clips)
setPlanIdsByVariant(planIds)
@@ -162,15 +139,6 @@ export function useBatchVariantPlans({
setPlanIdsByVariant([])
setVoiceDurationsByVariant([])
setError(true)
if (!errorToastShownRef.current) {
errorToastShownRef.current = true
const code = (err as { code?: string })?.code
if (code === "ECONNABORTED" || /timeout/i.test((err as Error)?.message || "")) {
message.error("预览加载超时,请检查网络后重试")
} else {
message.error("预览加载失败,请稍后重试")
}
}
} finally {
if (seq === requestSeqRef.current) setLoading(false)
}
@@ -190,8 +158,7 @@ export function useBatchVariantPlans({
}, [])
useEffect(() => {
// #1913:移除 !templateId 的硬拦截;只要 enabled=true、有素材、count>1 就发请求
if (!enabled || assetIds.length === 0 || count <= 1) {
if (!enabled || !templateId || assetIds.length === 0 || count <= 1) {
requestSeqRef.current += 1
// 函数式更新:已是目标值时返回 prev(Object.is 相等 React 跳过渲染),
// 避免父组件传入内联字面量数组导致 effect 每次 render 触发 → 无限 setState 循环
@@ -206,8 +173,7 @@ export function useBatchVariantPlans({
const voiceKey = voiceModePerVideo
? `per:${[...voiceLibraryIds].sort().join(",")}`
: `one:${voiceLibraryId}`
const effectiveTid = templateId || fallbackTidRef.current || "__auto__"
const key = `${retryNonce}|${effectiveTid}|${count}|${sourcePlanId}|${[...assetIds]
const key = `${retryNonce}|${templateId}|${count}|${sourcePlanId}|${[...assetIds]
.sort()
.join(",")}|${voiceKey}`
if (key === lastKeyRef.current) return
@@ -1,7 +1,6 @@
/**
* Step 1 素材选择 Hook(#1899 原 Step2 前移,#1913 修复空模板兜底)
* Step 1 素材选择 Hook(#1899 原 Step2 前移)
* 使用 clipCount 替代 templateSegments 决定片段数量
* #1913:selectedTemplate 为空时不再硬拦截,仍发 from-assets 请求(后端自动兜底创建默认模板)
*/
import { useCallback, useEffect, useRef } from "react"
import { message } from "antd"
@@ -20,10 +19,10 @@ interface UseStep2MaterialsProps {
onSmartSelectedIdsChange: (ids: string[]) => void
/** 片段数量(#1899) */
clipCount: number
/** 当前选中的模板/草稿 ID;空字符串时由后端自动兜底创建默认模板(#1913) */
/** 当前选中的模板/草稿 ID,用于自动保存 */
selectedTemplate?: string
/** 服务端 clips 创建成功后的回调,用于通知预览播放器;#1913 新增第二个参数回传实际使用的 template_id */
onServerClipsChange?: (clips: EditPlanClip[], templateId?: string) => void
/** 服务端 clips 创建成功后的回调,用于通知预览播放器 */
onServerClipsChange?: (clips: EditPlanClip[]) => void
}
export function useStep2Materials({
@@ -34,7 +33,7 @@ export function useStep2Materials({
smartSelectedIds,
onSmartSelectedIdsChange,
clipCount,
selectedTemplate = "",
selectedTemplate,
onServerClipsChange,
}: UseStep2MaterialsProps) {
const {
@@ -77,20 +76,16 @@ export function useStep2Materials({
handleSmartMatch,
])
/* ── 自动保存草稿 asset_ids(防抖 500ms,失败静默) ──
* #1913:selectedTemplate 为空时不发草稿保存请求(后端需要 template_id)
*/
const { scheduleSave } = useDraftAutoSave(selectedTemplate || undefined)
/* ── 选择素材后自动保存草稿 asset_ids(防抖 500ms,失败静默) ── */
const { scheduleSave } = useDraftAutoSave(selectedTemplate)
useEffect(() => {
if (!selectedTemplate) return
const ids = materialMode === "auto" ? smartSelectedIds : selectedMaterials
scheduleSave({ asset_ids: ids }, 500)
}, [selectedTemplate, materialMode, selectedMaterials, smartSelectedIds, scheduleSave])
/* ── 选择素材后同步写入 edit_plan_clips(防抖 800ms)──
* 调用后端 POST /clips/from-assets,传递 clipCount 作为片段数量。
* #1913:selectedTemplate 为空时仍发请求,后端自动兜底创建默认 voice_over 模板。
* 后端返回后若实际创建了新模板,通过 onServerClipsChange 回传 template_id。
/* ── 选择素材后同步写入 edit_plan_clips(防抖 800ms,失败静默)──
* 调用后端 POST /clips/from-assets,传递 clipCount 作为片段数量
*/
const clipsTimerRef = useRef<ReturnType<typeof setTimeout>>()
const clipsAbortRef = useRef<AbortController | null>(null)
@@ -100,11 +95,10 @@ export function useStep2Materials({
selectedTemplateRef.current = selectedTemplate
const onServerClipsChangeRef = useRef(onServerClipsChange)
onServerClipsChangeRef.current = onServerClipsChange
/** 记录后端兜底返回的新 template_id(#1913) */
const fallbackTidRef = useRef<string>("")
useEffect(() => {
const tid = selectedTemplateRef.current || fallbackTidRef.current
const tid = selectedTemplateRef.current
if (!tid) return
const ids = materialMode === "auto" ? smartSelectedIds : selectedMaterials
if (!ids.length) {
onServerClipsChangeRef.current?.([])
@@ -118,59 +112,31 @@ export function useStep2Materials({
clipsAbortRef.current = controller
const requiredClipsCount = clipCountRef.current
const hasTid = !!tid
const requestTid = tid // 空串时由后端兜底
try {
if (hasTid) {
// 已有模板:先清空旧片段
await updateEditPlanClips(requestTid, [], controller.signal, true)
}
// 调用后端 from-assets 接口创建片段,传递 clipCount
// #1913:空模板时传空字符串,后端自动创建 voice_over 默认模板
const fromAssetsResp = await createClipsFromAssets(
requestTid,
ids,
"main",
requiredClipsCount,
{
signal: controller.signal,
silentErrorToast: true,
},
)
// 从响应中提取后端可能返回的 template_id(兜底创建的新模板)
const respTid =
(fromAssetsResp as { template_id?: string })?.template_id ||
(fromAssetsResp as { edit_plan_id?: string })?.edit_plan_id ||
""
const finalTid = respTid || requestTid
if (respTid && !selectedTemplateRef.current) {
fallbackTidRef.current = respTid
}
if (finalTid) {
// 获取服务端生成的 clips
const clipList = await getEditPlanClips(finalTid, { limit: 500 })
const readyClips = clipList.items
.filter((c) => c.status === "ready")
.sort((a, b) => a.order - b.order)
onServerClipsChangeRef.current?.(readyClips, finalTid)
} else {
// 无有效 template_id(后端未返回),无法获取 clips
console.warn("[useStep2Materials] 后端未返回 template_id,无法获取 clips")
message.error("素材匹配失败,请刷新页面重试")
}
// 1. 清空旧片段
await updateEditPlanClips(tid, [], controller.signal, true)
// 2. 调用后端 from-assets 接口创建片段,传递 clipCount
await createClipsFromAssets(tid, ids, "main", requiredClipsCount, {
signal: controller.signal,
silentErrorToast: true,
})
// 3. 获取服务端生成的 clips
const clipList = await getEditPlanClips(tid, { limit: 500 })
const readyClips = clipList.items
.filter((c) => c.status === "ready")
.sort((a, b) => a.order - b.order)
onServerClipsChangeRef.current?.(readyClips)
} catch (err) {
const name = (err as { name?: string })?.name
if (name === "CanceledError" || name === "AbortError") return
const code = (err as { code?: string })?.code
if (code === "ECONNABORTED" || /timeout/i.test((err as Error)?.message || "")) {
console.warn("[useStep2Materials] 智能选片超时:", err)
message.error("智能选片超时,请检查网络后重试")
message.error("智能选片失败,请重试")
return
}
console.warn("[useStep2Materials] 写入 clips 失败:", err)
message.error("素材匹配失败,请稍后重试")
}
}, 800)
-309
View File
@@ -1,309 +0,0 @@
/**
* 积分中心主页(/app/points)
* 展示余额、会员信息、本月统计、快捷入口
*/
import React, { useEffect } from "react"
import {
Card,
Col,
Row,
Statistic,
Button,
Space,
Tag,
Typography,
Progress,
List,
Avatar,
Empty,
} from "antd"
import {
ThunderboltOutlined,
CrownFilled,
ArrowUpOutlined,
ArrowDownOutlined,
HistoryOutlined,
WalletOutlined,
FileTextOutlined,
InfoCircleOutlined,
} from "@ant-design/icons"
import { useNavigate } from "react-router-dom"
import PageHead from "@/components/layout/PageHead"
import { usePointsStore } from "@/store/pointsStore"
import "./Points.css"
const { Text } = Typography
const SOURCE_NAME: Record<string, string> = {
recharge: "充值",
task_reward: "任务奖励",
ai_voice: "AI 配音",
ai_digital_human: "AI 数字人",
ai_video: "智能混剪",
voice_clone_train: "声音克隆训练",
voice_clone_synth: "声音克隆合成",
douyin_extract: "抖音提取",
ai_rewrite: "AI 改写",
ai_title: "AI 标题",
ai_cover: "AI 封面",
subscription_bonus: "会员赠送",
admin_adjust: "管理员调整",
refund: "失败退还",
}
const PointsCenter: React.FC = () => {
const navigate = useNavigate()
const { balance, subscription, init, loading } = usePointsStore()
useEffect(() => {
init()
}, [init])
const bal = balance?.balance ?? 0
const earned = balance?.total_earned ?? 0
const spent = balance?.total_spent ?? 0
const isMember = !!balance?.is_member
const freeUsed = balance?.free_clips_used ?? 0
const freeLimit = balance?.free_clips_limit ?? 2
const freeRemain = balance?.free_clips_remaining ?? (isMember ? 0 : 2)
// 近 5 条流水 mock(实际从 transactions 页加载)
const recentTx = [
{ type: "spend", source: "ai_voice", amount: 1, time: "今天 10:30" },
{ type: "spend", source: "ai_video", amount: 3, time: "今天 09:15" },
{ type: "earn", source: "task_reward", amount: 20, time: "昨天" },
]
return (
<div className="xx-points-page">
<PageHead
title="积分中心"
description="管理积分余额、查看流水、充值使用"
actions={
<Space>
<Button icon={<FileTextOutlined />} onClick={() => navigate("/app/points/rules")}>
积分规则
</Button>
<Button
type="primary"
icon={<WalletOutlined />}
onClick={() => navigate("/app/subscription")}
>
充值积分
</Button>
</Space>
}
/>
{/* 顶部大卡 */}
<Card className="xx-points-hero" bordered={false}>
<Row gutter={24} align="middle">
<Col xs={24} md={14}>
<Space direction="vertical" size={8} style={{ width: "100%" }}>
<Text type="secondary" style={{ color: "rgba(255,255,255,0.8)" }}>
当前可用积分
</Text>
<div className="xx-points-hero-balance">
<ThunderboltOutlined />
<span>{loading ? "…" : bal.toLocaleString()}</span>
</div>
<Space size={8} wrap>
{isMember ? (
<Tag color="gold" icon={<CrownFilled />} style={{ padding: "4px 10px" }}>
{subscription?.member_type === "yearly"
? "年卡"
: subscription?.member_type === "quarterly"
? "季卡"
: "月卡"}
会员
</Tag>
) : (
<Tag
style={{
padding: "4px 10px",
background: "rgba(255,255,255,0.2)",
color: "#fff",
border: "none",
}}
>
免费会员
</Tag>
)}
{balance?.member_expires_at && (
<Text style={{ color: "rgba(255,255,255,0.85)", fontSize: 12 }}>
到期 {new Date(balance.member_expires_at).toLocaleDateString("zh-CN")}
</Text>
)}
{!isMember && (
<Button
size="small"
type="primary"
onClick={() => navigate("/app/subscription")}
style={{ background: "#fff", color: "#7c3aed", borderColor: "#fff" }}
>
<CrownFilled /> 升级会员享 8 折
</Button>
)}
</Space>
</Space>
</Col>
<Col xs={24} md={10}>
{/* 今日免费额度 */}
{!isMember && freeLimit > 0 && (
<div className="xx-points-free-quota">
<div className="xx-points-free-title">
<InfoCircleOutlined /> 今日免费混剪
</div>
<Progress
percent={Math.round((freeUsed / freeLimit) * 100)}
strokeColor={{ "0%": "#f59e0b", "100%": "#ef4444" }}
format={() => `${freeUsed}/${freeLimit} 条`}
/>
<Text style={{ color: "rgba(255,255,255,0.8)", fontSize: 12 }}>
剩余 {freeRemain} 条免费混剪,超出部分消耗积分
</Text>
</div>
)}
</Col>
</Row>
</Card>
{/* 统计 */}
<Row gutter={[16, 16]} style={{ marginTop: 16 }}>
<Col xs={12} md={6}>
<Card>
<Statistic
title="累计获得"
value={earned}
prefix={<ArrowUpOutlined style={{ color: "#10b981" }} />}
valueStyle={{ color: "#10b981" }}
/>
</Card>
</Col>
<Col xs={12} md={6}>
<Card>
<Statistic
title="累计消耗"
value={spent}
prefix={<ArrowDownOutlined style={{ color: "#ef4444" }} />}
valueStyle={{ color: "#ef4444" }}
/>
</Card>
</Col>
<Col xs={12} md={6}>
<Card>
<Statistic
title="可用积分"
value={bal}
prefix={<ThunderboltOutlined style={{ color: "#8b5cf6" }} />}
valueStyle={{ color: "#8b5cf6" }}
/>
</Card>
</Col>
<Col xs={12} md={6}>
<Card>
<Statistic
title={isMember ? "会员等级" : "今日免费剩余"}
value={isMember ? (subscription?.member_type_name ?? "付费会员") : `${freeRemain} 条`}
prefix={<CrownFilled style={{ color: "#f59e0b" }} />}
valueStyle={{ color: "#f59e0b" }}
/>
</Card>
</Col>
</Row>
{/* 快捷入口 & 最近流水 */}
<Row gutter={[16, 16]} style={{ marginTop: 8 }}>
<Col xs={24} md={14}>
<Card
title={
<Space>
<HistoryOutlined />
最近流水
</Space>
}
extra={<a onClick={() => navigate("/app/points/transactions")}>查看全部 →</a>}
>
{recentTx.length === 0 ? (
<Empty description="暂无积分流水" />
) : (
<List
dataSource={recentTx}
renderItem={(item) => (
<List.Item>
<List.Item.Meta
avatar={
<Avatar
size="small"
style={{
background: item.type === "earn" ? "#d1fae5" : "#fee2e2",
color: item.type === "earn" ? "#059669" : "#dc2626",
}}
icon={item.type === "earn" ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
/>
}
title={SOURCE_NAME[item.source] ?? item.source}
description={item.time}
/>
<div
style={{
color: item.type === "earn" ? "#10b981" : "#ef4444",
fontWeight: 700,
}}
>
{item.type === "earn" ? "+" : "-"}
{item.amount}
</div>
</List.Item>
)}
/>
)}
</Card>
</Col>
<Col xs={24} md={10}>
<Card title="快捷操作">
<Space direction="vertical" style={{ width: "100%" }} size={10}>
<Button
block
size="large"
type="primary"
icon={<WalletOutlined />}
onClick={() => navigate("/app/subscription")}
>
充值积分
</Button>
<Button
block
size="large"
icon={<HistoryOutlined />}
onClick={() => navigate("/app/points/transactions")}
>
积分明细
</Button>
<Button
block
size="large"
icon={<CrownFilled />}
onClick={() => navigate("/app/subscription")}
>
{isMember ? "续费/升级会员" : "升级付费会员"}
</Button>
<Button
block
size="large"
icon={<FileTextOutlined />}
onClick={() => navigate("/app/points/rules")}
>
积分消耗规则
</Button>
</Space>
</Card>
</Col>
</Row>
</div>
)
}
export default PointsCenter
export const Component = PointsCenter
-174
View File
@@ -1,174 +0,0 @@
/**
* 积分充值页(/app/points/recharge)
* 单独展示积分包,供入口直接跳转使用
* 主 Plans 页面也有充值区,这里提供独立 URL 方便从"积分不足"弹窗跳转
*/
import React, { useEffect, useState } from "react"
import { Card, Col, Row, Button, Tag, Typography, Space, Modal, message, Tooltip } from "antd"
import { ThunderboltOutlined, SafetyCertificateOutlined, CrownFilled } from "@ant-design/icons"
import { useNavigate } from "react-router-dom"
import PageHead from "@/components/layout/PageHead"
import { usePointsStore } from "@/store/pointsStore"
import { createPointsOrder, getPointsPackages } from "@/api/points"
import type { PointsPackage } from "@/api/points/types"
import "./Points.css"
const { Title, Text, Paragraph } = Typography
const PointsRecharge: React.FC = () => {
const navigate = useNavigate()
const { balance, init } = usePointsStore()
const [packages, setPackages] = useState<PointsPackage[]>([])
const [buying, setBuying] = useState<string | null>(null)
useEffect(() => {
init()
getPointsPackages()
.then((r) => setPackages(r.packages))
.catch(() => {})
}, [init])
const getPackPrice = (pkg: PointsPackage): number => {
const mt = balance?.member_type ?? "free"
type DiscountKey =
| "discounted_price_for_free"
| "discounted_price_for_monthly"
| "discounted_price_for_quarterly"
| "discounted_price_for_yearly"
const key = `discounted_price_for_${mt}` as DiscountKey
return pkg[key] ?? pkg.price
}
const handleBuy = async (pkg: PointsPackage) => {
try {
setBuying(pkg.id)
const order = await createPointsOrder({ package_id: pkg.id })
Modal.info({
title: "支付功能开发中",
icon: <SafetyCertificateOutlined />,
content: (
<div>
<Paragraph>
订单已创建({order.id.slice(0, 16)}…),金额{" "}
<b>¥{(order.price_cents / 100).toFixed(2).replace(/\.00$/, "")}</b>。
微信/支付宝支付正在接入中。
</Paragraph>
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
现阶段所有功能免费体验,积分仅为演示数据。
</Paragraph>
</div>
),
okText: "知道了",
})
} catch (e) {
const err = e as { message?: string }
message.error(err?.message ?? "下单失败")
} finally {
setBuying(null)
}
}
return (
<div className="xx-points-page">
<PageHead
title="积分充值"
description="积分永久有效,可用于全部 AI 功能;付费会员享折扣"
actions={
<Space>
{!balance?.is_member && (
<Button icon={<CrownFilled />} onClick={() => navigate("/app/subscription")}>
升级会员 8 折起
</Button>
)}
<Button onClick={() => navigate("/app/points/transactions")}>积分明细</Button>
</Space>
}
/>
{balance && (
<Card
bordered={false}
style={{ marginBottom: 16, background: "linear-gradient(135deg,#ede9fe,#fce7f3)" }}
>
<Space size="large">
<div>
<Text type="secondary">当前可用积分</Text>
<div style={{ fontSize: 28, fontWeight: 800, color: "#7c3aed" }}>
<ThunderboltOutlined /> {balance.balance.toLocaleString()}
</div>
</div>
</Space>
</Card>
)}
<Title level={4}>选择积分包</Title>
<Row gutter={[20, 20]}>
{packages.map((pkg) => {
const price = getPackPrice(pkg)
const discount = price < pkg.price ? Math.round((1 - price / pkg.price) * 100) : 0
const unit = price / pkg.points
return (
<Col xs={24} sm={12} md={8} key={pkg.id}>
<Card
className={`xx-pkg-card ${pkg.id === "basic_pack" ? "recommended" : ""} ${discount > 0 ? "has-discount" : ""}`}
hoverable
>
{pkg.id === "basic_pack" && <div className="xx-pkg-badge">热门</div>}
{discount > 0 && (
<Tag color="gold" className="xx-pkg-discount">
会员{10 - discount / 10}折
</Tag>
)}
<div className="xx-pkg-name">{pkg.name}</div>
<div className="xx-pkg-points">
<ThunderboltOutlined /> {pkg.points.toLocaleString()} 积分
</div>
<div className="xx-pkg-price">
<span className="currency">¥</span>
<span className="amount">
{(price / 100).toFixed(price % 100 === 0 ? 0 : 1).replace(/\.0$/, "")}
</span>
{discount > 0 && (
<span className="xx-pkg-origin">¥{(pkg.price / 100).toFixed(0)}</span>
)}
</div>
<div className="xx-pkg-unit">≈¥{unit.toFixed(3)}/积分 · 永久有效</div>
<Button
block
type={pkg.id === "basic_pack" ? "primary" : "default"}
loading={buying === pkg.id}
onClick={() => handleBuy(pkg)}
size="large"
style={{ marginTop: 16 }}
>
立即购买
</Button>
</Card>
</Col>
)
})}
</Row>
<Card style={{ marginTop: 24 }}>
<Title level={5}>积分消耗说明</Title>
<ul style={{ paddingLeft: 20, color: "var(--text-secondary)", lineHeight: 2 }}>
<li>智能混剪:3 积分/条(≤30s),每加 30s +1 积分</li>
<li>AI 配音 / 声音克隆合成:1 积分/分钟</li>
<li>AI 数字人:15 积分/分钟</li>
<li>抖音链接提取 / AI 改写 / AI 标题 / AI 封面:1~2 积分/次</li>
<li>声音克隆训练:免费</li>
<li>免费用户每日 2 条混剪免费,其余 AI 功能消耗为会员价 ×1.15</li>
</ul>
<Tooltip title="具体规则以系统实际计算为准">
<Text type="secondary" style={{ fontSize: 12 }}>
<SafetyCertificateOutlined /> 所有规则以页面实际显示为准
</Text>
</Tooltip>
</Card>
</div>
)
}
export default PointsRecharge
export const Component = PointsRecharge
-88
View File
@@ -1,88 +0,0 @@
/* 积分中心 & 明细 & 规则页通用样式 */
.xx-points-page {
max-width: 1100px;
margin: 0 auto;
padding: 24px 0;
}
/* Hero 渐变卡 */
.xx-points-hero {
background: linear-gradient(135deg, #7c3aed 0%, #ec4899 50%, #f59e0b 100%);
border-radius: 16px;
color: #fff;
overflow: hidden;
position: relative;
}
.xx-points-hero::before {
content: "";
position: absolute;
right: -60px;
top: -60px;
width: 220px;
height: 220px;
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, transparent 70%);
border-radius: 50%;
}
.xx-points-hero .ant-card-body {
padding: 28px 32px;
position: relative;
}
.xx-points-hero-balance {
display: flex;
align-items: baseline;
gap: 10px;
font-size: 52px;
font-weight: 800;
color: #fff;
line-height: 1;
font-variant-numeric: tabular-nums;
}
.xx-points-hero-balance .anticon {
font-size: 32px;
}
.xx-points-free-quota {
background: rgba(255, 255, 255, 0.15);
border-radius: 12px;
padding: 14px 16px;
backdrop-filter: blur(8px);
}
.xx-points-free-title {
font-size: 13px;
margin-bottom: 8px;
color: rgba(255, 255, 255, 0.95);
}
.xx-points-free-quota .ant-progress-text {
color: #fff !important;
}
/* 列表筛选 */
.xx-points-filters {
margin-bottom: 16px;
}
.xx-tx-earn {
color: #10b981;
font-weight: 700;
}
.xx-tx-spend {
color: #ef4444;
font-weight: 700;
}
.xx-tx-refund {
color: #3b82f6;
font-weight: 700;
}
/* 规则页 */
.xx-rules-table .ant-descriptions-item-label {
width: 160px;
}
-137
View File
@@ -1,137 +0,0 @@
/**
* 积分规则页(/app/points/rules)
* 静态展示积分获得、消耗、会员折扣等规则
*/
import React from "react"
import { Card, Descriptions, Table, Tag, Typography, Divider, Space } from "antd"
import { InfoCircleOutlined, CrownFilled, ThunderboltOutlined } from "@ant-design/icons"
import PageHead from "@/components/layout/PageHead"
import "./Points.css"
const { Title, Text, Paragraph } = Typography
const costData = [
{ key: "ai_video", scene: "智能混剪", unit: "条(≤30s)", cost: 3, remark: "每加 30s +1" },
{ key: "ai_voice", scene: "AI 配音(含克隆合成)", unit: "分钟", cost: 1, remark: "" },
{ key: "ai_digital_human", scene: "AI 数字人", unit: "分钟", cost: 15, remark: "" },
{ key: "douyin_extract", scene: "抖音文案提取", unit: "次", cost: 1, remark: "" },
{ key: "ai_rewrite", scene: "AI 文案改写", unit: "次", cost: 1, remark: "" },
{ key: "ai_title", scene: "AI 标题生成", unit: "次", cost: 1, remark: "" },
{ key: "ai_cover", scene: "AI 封面生成", unit: "次", cost: 2, remark: "" },
{ key: "voice_clone_train", scene: "声音克隆训练", unit: "次", cost: 0, remark: "免费" },
]
const earnData = [
{ key: "new_user", channel: "注册奖励", points: "50~100", remark: "新人专享" },
{ key: "recharge", channel: "充值购买", points: "100~2000/次", remark: "永久有效" },
{ key: "subscribe", channel: "开通会员赠送", points: "见会员方案", remark: "季/年卡赠送更多" },
{ key: "task", channel: "任务/活动奖励", points: "不定额", remark: "运营活动发放" },
]
const PointsRules: React.FC = () => {
return (
<div className="xx-points-page">
<PageHead title="积分规则" description="了解如何获得、使用与管理积分" />
<Card>
<Title level={5}>
<InfoCircleOutlined style={{ color: "#3b82f6" }} /> 什么是积分?
</Title>
<Paragraph>
积分是小虾 SaaS 用于结算 AI 能力的虚拟货币。每次使用智能混剪、AI 配音、AI 数字人、AI
改写等功能时,
会根据使用量扣除相应积分。积分永久有效,可通过充值、任务奖励、注册礼包等方式获取。
</Paragraph>
<Divider />
<Title level={5}>
<ThunderboltOutlined style={{ color: "#8b5cf6" }} /> 积分消耗规则
</Title>
<Table
className="xx-rules-table"
dataSource={costData}
pagination={false}
columns={[
{ title: "功能场景", dataIndex: "scene", width: 200 },
{ title: "计费单位", dataIndex: "unit", width: 140 },
{
title: "消耗积分",
dataIndex: "cost",
width: 110,
render: (v: number) =>
v === 0 ? (
<Tag color="green">免费</Tag>
) : (
<Text strong style={{ color: "#ef4444" }}>
{v}
</Text>
),
},
{ title: "备注", dataIndex: "remark" },
]}
/>
<Paragraph type="secondary" style={{ marginTop: 12, fontSize: 12 }}>
免费用户每日享有 2 条免费混剪额度,超出后按上述标准 ×1.15 扣除积分;
付费会员按上述标准原价扣除积分。
</Paragraph>
<Divider />
<Title level={5} style={{ color: "#10b981" }}>
↑ 积分获得方式
</Title>
<Table
className="xx-rules-table"
dataSource={earnData}
pagination={false}
columns={[
{ title: "渠道", dataIndex: "channel", width: 200 },
{ title: "积分数量", dataIndex: "points", width: 180 },
{ title: "备注", dataIndex: "remark" },
]}
/>
<Divider />
<Title level={5}>
<CrownFilled style={{ color: "#f59e0b" }} /> 会员折扣
</Title>
<Descriptions bordered column={1} size="small">
<Descriptions.Item label="免费用户">
所有 AI 功能按标准价 ×1.15 消耗积分;每日 2 条免费混剪
</Descriptions.Item>
<Descriptions.Item label="付费会员(月/季/年卡)">
所有 AI 功能按标准价消耗;积分购买享 8~9 折;解锁批量导出、多平台发布等高级功能
</Descriptions.Item>
<Descriptions.Item label="年卡会员">
积分购买最低 8 折;开通即赠送 2000 积分
</Descriptions.Item>
<Descriptions.Item label="季卡会员">
积分购买 8.5 折;开通即赠送 400 积分
</Descriptions.Item>
</Descriptions>
<Divider />
<Title level={5}>退款与有效期</Title>
<ul style={{ paddingLeft: 20, lineHeight: 2 }}>
<li>充值积分永久有效,不会过期</li>
<li>AI 生成失败或服务异常时,已扣除的积分将自动退还</li>
<li>会员为虚拟商品,一经开通不支持退款;到期后会员权益自动失效,积分余额不受影响</li>
<li>如需发票请联系客服</li>
</ul>
<Space style={{ marginTop: 16 }}>
<Text type="secondary" style={{ fontSize: 12 }}>
规则更新时间:2026-09-15
</Text>
</Space>
</Card>
</div>
)
}
export default PointsRules
export const Component = PointsRules
-252
View File
@@ -1,252 +0,0 @@
/**
* 积分明细页(/app/points/transactions)
* 分页展示积分流水,支持按类型/来源筛选
*/
import React, { useEffect, useState, useCallback } from "react"
import {
Card,
Table,
Tag,
Select,
DatePicker,
Space,
Typography,
Empty,
Input,
Spin,
Button,
} from "antd"
import { SearchOutlined, ReloadOutlined } from "@ant-design/icons"
import type { ColumnsType } from "antd/es/table"
import { useNavigate } from "react-router-dom"
import dayjs from "dayjs"
import PageHead from "@/components/layout/PageHead"
import { getPointsTransactions } from "@/api/points"
import type { PointsTransaction, PointsTxType } from "@/api/points/types"
import "./Points.css"
const { Text } = Typography
const { RangePicker } = DatePicker
const TYPE_LABEL: Record<PointsTxType, { text: string; color: string }> = {
earn: { text: "获得", color: "green" },
spend: { text: "消耗", color: "red" },
refund: { text: "退还", color: "blue" },
}
const SOURCE_LABEL: Record<string, string> = {
recharge: "充值",
task_reward: "任务奖励",
ai_voice: "AI 配音",
ai_digital_human: "AI 数字人",
ai_video: "智能混剪",
voice_clone_train: "声音克隆训练",
voice_clone_synth: "克隆合成",
douyin_extract: "抖音链接提取",
ai_rewrite: "AI 文案改写",
ai_title: "AI 标题生成",
ai_cover: "AI 封面生成",
subscription_bonus: "会员赠送",
admin_adjust: "管理员调整",
refund: "失败退还",
}
const PointsTransactions: React.FC = () => {
const navigate = useNavigate()
const [loading, setLoading] = useState(false)
const [data, setData] = useState<PointsTransaction[]>([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
const [type, setType] = useState<PointsTxType | "all">("all")
const [source, setSource] = useState<string>("all")
const [dateRange, setDateRange] = useState<[dayjs.Dayjs | null, dayjs.Dayjs | null] | null>(null)
const [keyword, setKeyword] = useState("")
const load = useCallback(async () => {
setLoading(true)
try {
const params: Record<string, string | number> = { page, page_size: pageSize }
if (type !== "all") params.type = type
if (source !== "all") params.source = source
if (dateRange && dateRange[0] && dateRange[1]) {
params.start_date = dateRange[0].format("YYYY-MM-DD")
params.end_date = dateRange[1].format("YYYY-MM-DD")
}
const res = await getPointsTransactions(params)
let items = res.items
if (keyword) {
const k = keyword.toLowerCase()
items = items.filter(
(it) =>
it.description.toLowerCase().includes(k) ||
(SOURCE_LABEL[it.source] ?? it.source).includes(keyword),
)
}
setData(items)
setTotal(res.total)
} finally {
setLoading(false)
}
}, [page, pageSize, type, source, dateRange, keyword])
useEffect(() => {
load()
}, [load])
const columns: ColumnsType<PointsTransaction> = [
{
title: "时间",
dataIndex: "created_at",
width: 180,
render: (v: string) => dayjs(v).format("YYYY-MM-DD HH:mm"),
},
{
title: "类型",
dataIndex: "type",
width: 90,
render: (t: PointsTxType) => {
const cfg = TYPE_LABEL[t]
return <Tag color={cfg.color}>{cfg.text}</Tag>
},
},
{
title: "来源/场景",
dataIndex: "source",
width: 150,
render: (s: string, r: PointsTransaction) => (
<Space>
<Text>{r.source_name || SOURCE_LABEL[s] || s}</Text>
</Space>
),
},
{
title: "说明",
dataIndex: "description",
ellipsis: true,
},
{
title: "变动",
dataIndex: "signed_amount",
width: 110,
align: "right",
render: (v: number, r: PointsTransaction) => (
<span
className={
r.type === "earn" ? "xx-tx-earn" : r.type === "refund" ? "xx-tx-refund" : "xx-tx-spend"
}
>
{v > 0 ? "+" : ""}
{v}
</span>
),
},
{
title: "余额",
dataIndex: "balance_after",
width: 110,
align: "right",
render: (v: number) => (
<Text strong style={{ fontVariantNumeric: "tabular-nums" }}>
{v}
</Text>
),
},
]
return (
<div className="xx-points-page">
<PageHead
title="积分明细"
description="查看积分收入、消耗与退还记录"
actions={
<Space>
<Button icon={<ReloadOutlined />} onClick={load}>
刷新
</Button>
<Button type="primary" onClick={() => navigate("/app/subscription")}>
充值积分
</Button>
</Space>
}
/>
<Card>
<div className="xx-points-filters">
<Space wrap size={12}>
<Select
value={type}
onChange={(v) => {
setType(v)
setPage(1)
}}
style={{ width: 120 }}
options={[
{ value: "all", label: "全部类型" },
{ value: "earn", label: "获得" },
{ value: "spend", label: "消耗" },
{ value: "refund", label: "退还" },
]}
/>
<Select
value={source}
onChange={(v) => {
setSource(v)
setPage(1)
}}
style={{ width: 160 }}
showSearch
options={[
{ value: "all", label: "全部来源" },
...Object.entries(SOURCE_LABEL).map(([k, v]) => ({ value: k, label: v })),
]}
/>
<RangePicker
value={dateRange ?? undefined}
onChange={(dates) => {
setDateRange(dates as [dayjs.Dayjs | null, dayjs.Dayjs | null] | null)
setPage(1)
}}
/>
<Input
placeholder="搜索说明"
prefix={<SearchOutlined />}
allowClear
style={{ width: 200 }}
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
onPressEnter={() => {
setPage(1)
load()
}}
/>
</Space>
</div>
<Spin spinning={loading}>
<Table<PointsTransaction>
rowKey="id"
columns={columns}
dataSource={data}
locale={{ emptyText: <Empty description="暂无积分流水,快去使用 AI 功能吧" /> }}
pagination={{
current: page,
pageSize,
total,
showSizeChanger: true,
showTotal: (t) => `共 ${t} 条记录`,
onChange: (p, ps) => {
setPage(p)
setPageSize(ps)
},
}}
/>
</Spin>
</Card>
</div>
)
}
export default PointsTransactions
export const Component = PointsTransactions
File diff suppressed because it is too large Load Diff
+81 -204
View File
@@ -1,260 +1,137 @@
/**
* 文案库页面样式(#1811 v2)
* 风格对齐标题库,新增卡片 hover 阴影、标签/meta 行、筛选栏样式
* 文案库页面 - V21 设计系统样式
* 单列卡片列表,风格对齐标题库(xx-titles-page)
*/
@import "../../styles/global.css";
/* ============================================================
页面容器
============================================================ */
.xx-scripts-page {
padding: 24px;
max-width: 1200px;
margin: 0 auto;
min-height: 100%;
padding: var(--space-xl);
}
.xx-scripts-layout {
display: flex;
flex-direction: column;
gap: 16px;
gap: var(--space-lg);
max-width: 960px;
margin: 0 auto;
}
/* ── 筛选栏 ── */
/* ============================================================
顶部筛选栏
============================================================ */
.xx-scripts-filters {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
gap: var(--space-md);
flex-wrap: wrap;
padding: 12px 16px;
background: var(--bg-card, #fff);
border-radius: 12px;
border: 1px solid var(--border-color, #f0f0f0);
}
.xx-scripts-filters-left {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
gap: var(--space-sm);
}
.xx-scripts-filters-right {
display: flex;
gap: 8px;
align-items: center;
gap: var(--space-sm);
}
/* ── 列表 ── */
/* ============================================================
列表
============================================================ */
.xx-scripts-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
gap: 16px;
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.xx-scripts-loading {
text-align: center;
color: var(--text-secondary);
padding: var(--space-xl);
font-size: var(--font-size-sm);
}
/* ============================================================
文案卡片(对齐标题卡片风格,单列)
============================================================ */
.xx-script-card {
transition:
box-shadow 0.2s,
transform 0.2s;
border: 1px solid var(--border-color);
background: var(--bg-primary);
border-radius: var(--radius-md);
padding: var(--space-md);
transition: var(--transition-all);
display: flex;
flex-direction: column;
gap: 10px;
}
.xx-script-card:hover {
box-shadow: 0 4px 16px rgba(124, 58, 237, 0.1);
transform: translateY(-2px);
}
.xx-script-card .ant-card-body {
padding: 16px;
border-color: var(--primary-color);
background: var(--bg-secondary);
box-shadow: var(--shadow-sm);
}
.xx-script-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 8px;
margin-bottom: 10px;
gap: var(--space-md);
}
.xx-script-title-row {
.xx-script-title {
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--text-primary);
line-height: 1.5;
word-break: break-word;
flex: 1;
}
.xx-script-actions {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.xx-script-title {
font-size: 15px;
font-weight: 600;
color: var(--text-primary, #1f1f1f);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.xx-script-actions {
gap: var(--space-xxs);
flex-shrink: 0;
opacity: 0;
transition: var(--transition-opacity, opacity 0.2s);
}
.xx-script-actions .ant-btn {
padding: 0 6px;
.xx-script-card:hover .xx-script-actions {
opacity: 1;
}
.xx-script-preview {
font-size: 13px;
color: var(--text-secondary, #666);
font-size: var(--font-size-sm);
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 8px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.xx-script-title-text {
font-size: 12px;
color: var(--text-tertiary, #999);
margin-bottom: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.xx-script-label {
color: var(--text-tertiary, #999);
}
.xx-script-tags {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 8px;
word-break: break-word;
}
.xx-script-meta {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-tertiary, #999);
}
.xx-script-meta-sep {
opacity: 0.5;
gap: var(--space-xs);
font-size: var(--font-size-xs, 12px);
color: var(--text-tertiary);
}
/* ── 分页 ── */
.xx-scripts-pagination {
/* ============================================================
弹窗表单
============================================================ */
.xx-script-form {
display: flex;
justify-content: center;
padding: 16px 0;
flex-direction: column;
gap: var(--space-md);
}
.xx-scripts-loading {
padding: 48px;
text-align: center;
color: var(--text-tertiary, #999);
}
/* ── 弹窗表单 ── */
.xx-script-form .ant-form-item {
margin-bottom: 12px;
}
/* ── 响应式 ── */
@media (max-width: 768px) {
.xx-scripts-page {
padding: 16px 12px;
}
.xx-scripts-list {
grid-template-columns: 1fr;
}
.xx-scripts-filters {
flex-direction: column;
align-items: stretch;
}
.xx-scripts-filters-left {
width: 100%;
}
.xx-scripts-filters-left .ant-input,
.xx-scripts-filters-left .ant-select {
flex: 1;
}
}
/* ── #1893 AI 能力样式 ── */
/* 抖音提取 / AI 按钮与主按钮间距 */
.xx-scripts-filters-right .ant-btn + .ant-btn {
margin-left: 8px;
}
/* AI 改写工具条(贴在正文 TextArea 下方) */
.xx-ai-rewrite-bar {
display: flex;
align-items: center;
margin-top: -8px;
margin-bottom: 16px;
padding: 8px 12px;
background: linear-gradient(90deg, #f7f5ff 0%, #fff 100%);
border: 1px dashed #d3c6ff;
border-radius: 8px;
}
/* AI 生成标题候选 */
.xx-ai-title-candidates {
margin-top: -8px;
margin-bottom: 12px;
padding: 10px 12px;
background: #fafaff;
border-radius: 8px;
border: 1px solid #eee6ff;
}
.xx-ai-title-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 6px;
}
.xx-ai-title-tag {
cursor: pointer;
font-size: 13px;
padding: 4px 12px;
border-radius: 16px;
transition: transform 0.15s;
margin: 0;
}
.xx-ai-title-tag:hover {
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(114, 46, 209, 0.18);
}
/* loading 提示文本 */
.xx-ai-loading-hint {
display: flex;
align-items: center;
margin-top: 12px;
padding: 10px 12px;
color: var(--text-secondary, #666);
font-size: 13px;
background: #fafafa;
border-radius: 6px;
}
/* AI 改写对比块 */
.xx-ai-rewrite-item {
border-bottom: 1px solid var(--border-color, #f0f0f0) !important;
padding: 12px 0 !important;
}
.xx-ai-rewrite-item:last-child {
border-bottom: none !important;
}
.xx-ai-rewrite-block {
width: 100%;
}
.xx-ai-rewrite-label {
margin-bottom: 6px;
}
.xx-ai-rewrite-text {
font-size: 13px;
line-height: 1.7;
color: var(--text-primary, #1f1f1f);
padding: 8px 12px;
background: #fafafa;
border-radius: 6px;
max-height: 180px;
overflow-y: auto;
}
.xx-ai-rewrite-item:first-child .xx-ai-rewrite-text {
color: var(--text-secondary, #666);
background: #f7f7f7;
}
.xx-ai-rewrite-item:last-child .xx-ai-rewrite-text {
background: linear-gradient(180deg, #faf5ff 0%, #ffffff 100%);
border: 1px solid #eee6ff;
.xx-script-form textarea.ant-input {
resize: vertical;
}
+165 -218
View File
@@ -1,286 +1,233 @@
/* 会员订阅 & 积分充值页样式 v2 */
/* V21 定价页面 - CSS变量体系 */
@import "../../styles/global.css";
.xx-plans-page {
max-width: 1100px;
max-width: 1200px;
margin: 0 auto;
padding: 24px 0;
padding: var(--space-3xl) var(--space-lg);
}
/* 当前状态卡片 */
.xx-current-status {
background: linear-gradient(135deg, #ede9fe 0%, #fce7f3 100%);
border-radius: 14px;
margin-bottom: 16px;
}
.xx-current-status .ant-card-body {
padding: 20px 24px;
}
.xx-current-balance {
display: flex;
align-items: center;
gap: 6px;
font-size: 20px;
font-weight: 800;
color: var(--text-primary, #111827);
}
.xx-current-balance-val {
font-variant-numeric: tabular-nums;
}
/* 计费周期切换 */
.xx-billing-switch {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-top: 12px;
position: relative;
}
.xx-billing-btn {
position: relative;
border: 2px solid var(--border-color, #e5e7eb);
background: var(--bg-primary, #fff);
border-radius: 12px;
padding: 16px 12px;
cursor: pointer;
transition: all 0.15s;
/* 页面头部 */
.xx-page-head {
text-align: center;
margin-bottom: var(--space-3xl);
}
.xx-billing-btn:hover {
border-color: var(--primary-soft, #c4b5fd);
.xx-page-head h2 {
font-size: var(--font-size-5xl);
font-weight: var(--font-weight-extrabold);
color: var(--text-primary);
margin: 0 0 var(--space-md);
letter-spacing: var(--letter-spacing-tight);
}
.xx-billing-btn.active {
border-color: #7c3aed;
background: #f5f3ff;
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.15);
.xx-page-head p {
font-size: var(--font-size-lg);
color: var(--text-secondary);
margin: 0;
max-width: 560px;
margin-left: auto;
margin-right: auto;
}
.xx-billing-name {
font-weight: 700;
font-size: 15px;
color: var(--text-primary, #111827);
/* 定价卡片网格 - 4列 */
.xx-plans-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-lg);
margin-top: var(--space-xl);
}
.xx-billing-price {
margin: 6px 0;
@media (max-width: 1200px) {
.xx-plans-grid {
grid-template-columns: repeat(2, 1fr);
}
}
.xx-billing-yuan {
font-size: 14px;
color: var(--text-secondary, #6b7280);
@media (max-width: 768px) {
.xx-plans-grid {
grid-template-columns: 1fr;
max-width: 400px;
margin-left: auto;
margin-right: auto;
}
}
.xx-billing-amount {
font-size: 28px;
font-weight: 800;
color: var(--text-primary, #111827);
margin: 0 2px;
}
.xx-billing-sub {
font-size: 12px;
color: var(--text-tertiary, #9ca3af);
}
.xx-billing-save {
display: inline-block;
margin-left: 6px;
background: #fef3c7;
color: #d97706;
padding: 1px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
}
/* 方案卡片 */
/* V21 定价卡片 */
.xx-plan-card {
border-radius: 14px;
background: var(--bg-elevated);
border: 1px solid var(--border-color);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-card);
padding: var(--space-xl) var(--space-lg);
position: relative;
height: 100%;
transition: var(--transition-base);
display: flex;
flex-direction: column;
}
.xx-plan-card:hover {
border-color: var(--primary-color);
box-shadow: 0 32px 90px color-mix(in srgb, var(--primary-color) 15%, transparent);
transform: translateY(-4px);
}
.xx-plan-card.featured {
border: 2px solid #7c3aed;
box-shadow: 0 8px 24px rgba(124, 58, 237, 0.12);
border: 2px solid var(--primary-color);
box-shadow:
0 0 0 4px color-mix(in srgb, var(--primary-color) 10%, transparent),
0 32px 90px color-mix(in srgb, var(--primary-color) 15%, transparent);
transform: scale(1.02);
}
.xx-plan-recommend-badge {
.xx-plan-card.featured:hover {
transform: scale(1.02) translateY(-4px);
}
/* 推荐标签 */
.xx-badge {
position: absolute;
top: -12px;
right: 20px;
background: linear-gradient(135deg, #f59e0b, #ef4444);
color: #fff;
padding: 4px 12px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
left: 50%;
transform: translateX(-50%);
background: var(--gradient-primary);
color: var(--text-inverse);
padding: 6px 16px;
border-radius: var(--radius-full);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-extrabold);
white-space: nowrap;
}
.xx-plan-head {
/* 企业版定制标签 */
.xx-badge.enterprise {
background: linear-gradient(135deg, var(--accent-color), var(--accent-dark));
}
/* 套餐名称 */
.xx-plan-card h3 {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-extrabold);
color: var(--text-primary);
margin: 0 0 var(--space-lg);
text-align: center;
}
/* 价格 */
.xx-plan-price {
margin: 12px 0 6px;
display: flex;
align-items: baseline;
justify-content: center;
text-align: center;
margin-bottom: 12px;
}
.xx-plan-price .currency {
font-size: 18px;
color: var(--text-secondary, #6b7280);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-bold);
color: var(--text-secondary);
vertical-align: top;
}
.xx-plan-price .amount {
font-size: 44px;
font-weight: 800;
color: var(--text-primary, #111827);
font-size: var(--font-size-5xl);
font-weight: var(--font-weight-extrabold);
color: var(--primary-color);
line-height: 1;
margin: 0 4px;
}
.xx-plan-card.featured .xx-plan-price .amount {
color: #7c3aed;
}
.xx-plan-price .period {
color: var(--text-secondary, #6b7280);
font-size: 14px;
font-size: var(--font-size-md);
color: var(--text-secondary);
font-weight: var(--font-weight-medium);
}
.xx-feature-list {
list-style: none;
padding: 0;
margin: 0 0 20px 0;
/* 企业版价格 */
.xx-plan-price.custom .amount {
font-size: var(--font-size-3xl);
}
.xx-feature-list li {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 0;
font-size: 14px;
color: var(--text-primary, #111827);
}
.xx-feature-list li.disabled {
color: var(--text-tertiary, #9ca3af);
text-decoration: line-through;
}
.xx-feature-no {
width: 16px;
display: inline-flex;
justify-content: center;
color: var(--text-tertiary, #d1d5db);
}
.xx-plan-tip {
/* 描述 */
.xx-plan-description {
color: var(--text-secondary);
font-size: var(--font-size-base);
text-align: center;
font-size: 12px;
color: var(--text-tertiary, #9ca3af);
margin-top: 10px;
margin-bottom: var(--space-lg);
padding-bottom: var(--space-lg);
border-bottom: 1px solid var(--border-color);
}
/* 积分包卡片 */
.xx-pkg-card {
position: relative;
border-radius: 14px;
text-align: center;
transition: all 0.2s;
overflow: hidden;
/* 功能列表 */
.xx-features {
margin-top: var(--space-sm);
padding-top: var(--space-lg);
flex: 1;
}
.xx-pkg-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 28px rgba(139, 92, 246, 0.18);
}
.xx-pkg-card.recommended {
border: 2px solid #8b5cf6;
}
.xx-pkg-badge {
position: absolute;
top: 12px;
left: 12px;
background: linear-gradient(135deg, #8b5cf6, #ec4899);
color: #fff;
padding: 2px 10px;
border-radius: 999px;
font-size: 11px;
font-weight: 700;
}
.xx-pkg-discount {
position: absolute;
top: 12px;
right: 12px;
margin: 0;
}
.xx-pkg-name {
font-size: 18px;
font-weight: 700;
color: var(--text-primary, #111827);
margin-top: 8px;
}
.xx-pkg-points {
font-size: 16px;
color: #8b5cf6;
font-weight: 600;
margin: 8px 0;
}
.xx-pkg-price {
.xx-feature {
display: flex;
align-items: baseline;
justify-content: center;
gap: 6px;
margin-top: 12px;
gap: 12px;
align-items: flex-start;
color: var(--text-secondary);
font-size: var(--font-size-base);
margin-bottom: var(--space-md);
line-height: var(--line-height-base);
}
.xx-pkg-price .currency {
font-size: 16px;
color: var(--text-secondary, #6b7280);
.xx-feature span:first-child {
color: var(--secondary-color);
font-weight: var(--font-weight-extrabold);
flex-shrink: 0;
}
.xx-pkg-price .amount {
font-size: 32px;
font-weight: 800;
color: var(--text-primary, #111827);
line-height: 1;
/* 订阅按钮 */
.xx-subscribe-btn {
width: 100%;
margin-top: var(--space-lg);
padding: var(--space-md) var(--space-lg) !important;
font-size: var(--font-size-base) !important;
font-weight: var(--font-weight-extrabold) !important;
border-radius: var(--radius-md) !important;
transition: var(--transition-fast) !important;
cursor: pointer;
border: none;
}
.xx-pkg-card.recommended .xx-pkg-price .amount {
color: #8b5cf6;
.xx-subscribe-btn.primary {
background: var(--gradient-primary) !important;
color: var(--text-inverse) !important;
border: none !important;
box-shadow: var(--shadow-primary) !important;
}
.xx-pkg-origin {
color: var(--text-tertiary, #9ca3af);
text-decoration: line-through;
font-size: 13px;
.xx-subscribe-btn.primary:hover {
box-shadow: var(--shadow-hover) !important;
transform: translateY(-1px);
}
.xx-pkg-unit {
font-size: 12px;
color: var(--text-tertiary, #9ca3af);
margin-top: 4px;
.xx-subscribe-btn.ghost {
background: var(--bg-primary) !important;
border: 1px solid var(--border-color) !important;
color: var(--text-primary) !important;
}
@media (max-width: 640px) {
.xx-billing-switch {
.xx-subscribe-btn.ghost:hover {
border-color: var(--primary-color) !important;
color: var(--primary-color) !important;
}
/* 企业版按钮 */
.xx-subscribe-btn.enterprise {
background: linear-gradient(135deg, var(--accent-color), var(--accent-dark)) !important;
color: var(--text-inverse) !important;
border: none !important;
box-shadow: 0 14px 26px color-mix(in srgb, var(--accent-color) 22%, transparent) !important;
}
@media (max-width: 480px) {
.xx-plans-grid {
grid-template-columns: 1fr;
gap: var(--space-md);
}
.xx-plan-price .amount {
font-size: 36px;
.xx-plan-card {
padding: var(--space-lg);
}
}
+84 -399
View File
@@ -1,422 +1,107 @@
/**
* 会员订阅 & 积分充值页
* v2 两档会员制:免费 vs 付费
* 付费三档:月¥19.9 / 季¥39.9(推荐)/ 年¥159
* 积分包:100/¥9.9、500/¥39、2000/¥129
* 定价页面 - V21 UI 四级定价
*/
import React, { useEffect, useMemo, useState } from "react"
import {
Button,
Card,
Col,
Row,
Tag,
Divider,
Space,
Typography,
Modal,
message,
Tooltip,
Badge,
} from "antd"
import {
CheckCircleFilled,
CrownFilled,
ThunderboltOutlined,
SafetyCertificateOutlined,
VideoCameraOutlined,
StarFilled,
} from "@ant-design/icons"
import { useNavigate } from "react-router-dom"
import React, { useState } from "react"
import { message } from "antd"
import PageHead from "@/components/layout/PageHead"
import { usePointsStore } from "@/store/pointsStore"
import { SUBSCRIPTION_PLANS, createPointsOrder } from "@/api/points"
import type { PointsPackage } from "@/api/points/types"
import { getPointsPackages } from "@/api/points"
import "./Plans.css"
const { Title, Text, Paragraph } = Typography
/** 免费会员权益 */
const FREE_FEATURES = [
{ include: true, text: "每日 2 条免费混剪" },
{ include: true, text: "720p 导出分辨率" },
{ include: true, text: "AI 配音(×1.15 积分)" },
{ include: true, text: "AI 数字人(×1.15 积分)" },
{ include: true, text: "声音克隆训练免费" },
{ include: false, text: "批量导出" },
{ include: false, text: "多平台一键发布" },
{ include: false, text: "去重检测报告" },
const PLANS = [
{
id: "free",
name: "体验版",
price: 0,
description: "适合个人体验",
recommended: false,
features: ["3个视频成片", "基础标题模板", "基础配音音色", "720p导出"],
},
{
id: "standard",
name: "标准版",
price: 99,
description: "适合内容创作者",
recommended: false,
features: ["无限视频成片", "高级标题模板", "全部配音音色", "1080p导出", "API接口"],
},
{
id: "pro",
name: "专业版",
price: 299,
description: "适合专业团队",
recommended: true,
features: ["无限视频成片", "自定义标题", "全部配音音色", "4K导出", "优先渲染队列", "专属客服"],
},
{
id: "enterprise",
name: "企业版",
price: null,
description: "适合企业用户",
recommended: false,
features: ["私有化部署", "自定义品牌形象", " SLA服务保障", "技术支持", "团队协作", "数据报表"],
},
]
/** 付费会员权益 */
const PAID_FEATURES = [
{ include: true, text: "无限次智能混剪" },
{ include: true, text: "最高 1080p 导出" },
{ include: true, text: "全部 AI 功能(标准积分价)" },
{ include: true, text: "声音克隆训练免费" },
{ include: true, text: "积分购买最低 8 折" },
{ include: true, text: "批量导出" },
{ include: true, text: "多平台一键发布" },
{ include: true, text: "去重检测报告" },
]
const formatYuan = (cents: number) =>
`¥${(cents / 100).toFixed(cents % 100 === 0 ? 0 : 1).replace(/\.0$/, "")}`
const Plans: React.FC = () => {
const navigate = useNavigate()
const { balance, init } = usePointsStore()
const [selectedBilling, setSelectedBilling] = useState<"monthly" | "quarterly" | "yearly">(
"quarterly",
)
const [packages, setPackages] = useState<PointsPackage[]>([])
const [subscribing] = useState(false)
const [buying, setBuying] = useState<string | null>(null)
const [subscribing, setSubscribing] = useState(false)
useEffect(() => {
init()
getPointsPackages()
.then((r) => setPackages(r.packages))
.catch(() => {})
}, [init])
const isMember = !!balance?.is_member
const memberType = balance?.member_type ?? null
/** 根据会员等级计算积分包折后价(分) */
const getPackPrice = (pkg: PointsPackage): number => {
const mt = memberType ?? "free"
type DiscountKey =
| "discounted_price_for_free"
| "discounted_price_for_monthly"
| "discounted_price_for_quarterly"
| "discounted_price_for_yearly"
const key = `discounted_price_for_${mt}` as DiscountKey
return pkg[key] ?? pkg.price
}
const selectedPlan = useMemo(
() => SUBSCRIPTION_PLANS.find((p) => p.id === selectedBilling)!,
[selectedBilling],
)
const handleSubscribe = async () => {
Modal.confirm({
title: "支付功能开发中",
icon: <SafetyCertificateOutlined />,
content: "微信/支付宝支付正在接入中,完成后会第一时间通知。是否返回首页继续使用免费功能?",
okText: "返回首页",
cancelText: "留在此页",
onOk: () => navigate("/app/dashboard"),
})
// 实际对接时:
// try {
// setSubscribing(true)
// const order = await subscribe({ member_type: selectedBilling, payment_method: "wechat_pay" })
// // 拉起支付...
// } catch (e: any) {
// message.error(e?.message ?? "订阅失败")
// } finally {
// setSubscribing(false)
// }
}
const handleBuyPoints = async (pkg: PointsPackage) => {
const handleSubscribe = async (_planId: string) => {
try {
setBuying(pkg.id)
const order = await createPointsOrder({ package_id: pkg.id })
Modal.info({
title: "支付功能开发中",
icon: <ThunderboltOutlined />,
content: (
<div>
<Paragraph>
订单 <Text code>{order.id.slice(0, 16)}…</Text> 已创建,金额{" "}
<b>{formatYuan(order.price_cents)}</b>,
微信/支付宝支付正在接入中,正式上线后可直接付款。
</Paragraph>
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
现阶段所有功能均处于免费体验阶段,积分仅为演示数据。
</Paragraph>
</div>
),
okText: "知道了",
})
} catch (e) {
const err = e as { message?: string }
message.error(err?.message ?? "创建订单失败")
setSubscribing(true)
message.success("订阅成功")
} catch (error: unknown) {
message.error("订阅失败")
} finally {
setBuying(null)
setSubscribing(false)
}
}
return (
<div className="xx-plans-page">
<PageHead
title="会员与积分"
description="开通会员解锁全部功能,按需充值积分灵活使用 AI 能力"
<PageHead title="选择适合您的方案" description="灵活定价,满足不同规模的创作需求" />
actions={
<Space>
<Button
icon={<ThunderboltOutlined />}
onClick={() => navigate("/app/points/transactions")}
<div className="xx-plans-grid">
{PLANS.map((plan) => (
<div key={plan.id} className={`xx-plan-card ${plan.recommended ? "featured" : ""}`}>
{plan.recommended && <div className="xx-badge">推荐</div>}
{plan.id === "enterprise" && <div className="xx-badge enterprise">定制</div>}
<h3>{plan.name}</h3>
<div className={`xx-plan-price ${plan.price === null ? "custom" : ""}`}>
{plan.price !== null ? (
<>
<span className="currency">¥</span>
<span className="amount">{plan.price}</span>
<span className="period">/月</span>
</>
) : (
<span className="amount">联系我们</span>
)}
</div>
<p className="xx-plan-description">{plan.description}</p>
<div className="xx-features">
{plan.features.map((feature, idx) => (
<div key={idx} className="xx-feature">
<span>✓</span>
<span>{feature}</span>
</div>
))}
</div>
<button
className={`xx-subscribe-btn ${
plan.id === "enterprise" ? "enterprise" : plan.recommended ? "primary" : "ghost"
}`}
onClick={() => handleSubscribe(plan.id)}
disabled={subscribing}
>
积分明细
</Button>
</Space>
}
/>
{/* 当前状态卡片 */}
{balance && (
<Card className="xx-current-status" bordered={false}>
<Row align="middle" gutter={24}>
<Col flex="auto">
<Space size="large" wrap>
<div>
<Text type="secondary">当前身份</Text>
<div>
{isMember ? (
<Tag
color="gold"
icon={<CrownFilled />}
style={{ marginTop: 4, fontSize: 14, padding: "4px 10px" }}
>
{memberType === "yearly"
? "年卡"
: memberType === "quarterly"
? "季卡"
: "月卡"}
会员
</Tag>
) : (
<Tag style={{ marginTop: 4, fontSize: 14, padding: "4px 10px" }}>
免费会员
</Tag>
)}
{balance.member_expires_at && (
<Text type="secondary" style={{ marginLeft: 8 }}>
到期 {new Date(balance.member_expires_at).toLocaleDateString("zh-CN")}
</Text>
)}
</div>
</div>
<div>
<Text type="secondary">可用积分</Text>
<div className="xx-current-balance">
<ThunderboltOutlined style={{ color: "#8b5cf6" }} />
<span className="xx-current-balance-val">{balance.balance}</span>
</div>
</div>
{balance.free_clips_limit ? (
<div>
<Text type="secondary">今日免费混剪</Text>
<div>
<Text strong>{balance.free_clips_used ?? 0}</Text>
<Text type="secondary"> / {balance.free_clips_limit} 条</Text>
{!isMember && (
<Tag color="blue" style={{ marginLeft: 8 }}>
剩余 {balance.free_clips_remaining ?? 0} 条
</Tag>
)}
</div>
</div>
) : null}
</Space>
</Col>
<Col>
<Button
type="primary"
icon={<ThunderboltOutlined />}
onClick={() => {
const el = document.getElementById("points-packages")
el?.scrollIntoView({ behavior: "smooth" })
}}
>
充值积分
</Button>
</Col>
</Row>
</Card>
)}
{/* 两档会员对比 */}
<Title level={4} style={{ marginTop: 24 }}>
<CrownFilled style={{ color: "#f59e0b", marginRight: 8 }} />
选择适合您的方案
</Title>
{/* 计费周期切换 */}
<div className="xx-billing-switch">
{SUBSCRIPTION_PLANS.map((p) => (
<button
key={p.id}
type="button"
className={`xx-billing-btn ${selectedBilling === p.id ? "active" : ""}`}
onClick={() => setSelectedBilling(p.id)}
>
{p.recommended && (
<Badge.Ribbon text="推荐" color="gold" style={{ top: -14, right: -22 }} />
)}
<div className="xx-billing-name">{p.name}</div>
<div className="xx-billing-price">
<span className="xx-billing-yuan">¥</span>
<span className="xx-billing-amount">{p.price_yuan}</span>
</div>
<div className="xx-billing-sub">
≈¥{p.per_month_yuan}/月
{p.savings_percent ? (
<span className="xx-billing-save">省{p.savings_percent}%</span>
) : null}
</div>
</button>
{subscribing ? "处理中..." : plan.price === null ? "联系我们" : "立即订阅"}
</button>
</div>
))}
</div>
<Row gutter={[20, 20]} style={{ marginTop: 16 }}>
{/* 免费会员 */}
<Col xs={24} md={12}>
<Card className="xx-plan-card" bordered>
<div className="xx-plan-head">
<Title level={4} style={{ margin: 0 }}>
免费会员
</Title>
<div className="xx-plan-price">
<span className="currency">¥</span>
<span className="amount">0</span>
<span className="period">/永久</span>
</div>
<Text type="secondary">体验 AI 剪辑的基础能力</Text>
</div>
<Divider style={{ margin: "16px 0" }} />
<ul className="xx-feature-list">
{FREE_FEATURES.map((f, i) => (
<li key={i} className={f.include ? "" : "disabled"}>
{f.include ? (
<CheckCircleFilled style={{ color: "#10b981" }} />
) : (
<span className="xx-feature-no">—</span>
)}
<span>{f.text}</span>
</li>
))}
</ul>
<Button block size="large" disabled>
当前方案
</Button>
</Card>
</Col>
{/* 付费会员 */}
<Col xs={24} md={12}>
<Card className="xx-plan-card featured" bordered>
<div className="xx-plan-recommend-badge">
<StarFilled /> 推荐
</div>
<div className="xx-plan-head">
<Title level={4} style={{ margin: 0, color: "#7c3aed" }}>
<CrownFilled style={{ color: "#f59e0b" }} /> 付费会员
</Title>
<div className="xx-plan-price">
<span className="currency">¥</span>
<span className="amount">{selectedPlan.price_yuan}</span>
<span className="period">{selectedPlan.billing_label}</span>
</div>
<Text type="secondary">
折合 ¥{selectedPlan.per_month_yuan}/月 · 解锁全部 AI 能力
</Text>
</div>
<Divider style={{ margin: "16px 0" }} />
<ul className="xx-feature-list">
{PAID_FEATURES.map((f, i) => (
<li key={i}>
<CheckCircleFilled style={{ color: "#7c3aed" }} />
<span>{f.text}</span>
</li>
))}
</ul>
<Button
block
size="large"
type="primary"
loading={subscribing}
disabled={isMember && memberType === selectedBilling}
onClick={handleSubscribe}
icon={<ThunderboltOutlined />}
>
{isMember && memberType === selectedBilling
? "当前方案"
: isMember
? "续费/升级"
: "立即开通"}
</Button>
<div className="xx-plan-tip">
<VideoCameraOutlined /> 虚拟商品不支持退款 · 支付接入中先演示
</div>
</Card>
</Col>
</Row>
{/* 积分充值 */}
<div id="points-packages">
<Title level={4} style={{ marginTop: 40 }}>
<ThunderboltOutlined style={{ color: "#8b5cf6", marginRight: 8 }} />
积分充值
<Tooltip title="积分永久有效,可用于所有 AI 功能;付费会员享折扣">
<Text type="secondary" style={{ fontSize: 13, marginLeft: 8, fontWeight: "normal" }}>
(永久有效)
</Text>
</Tooltip>
</Title>
<Row gutter={[16, 16]}>
{packages.map((pkg) => {
const price = getPackPrice(pkg)
const discount = price < pkg.price ? Math.round((1 - price / pkg.price) * 100) : 0
const unit = price / pkg.points
return (
<Col xs={24} sm={8} key={pkg.id}>
<Card
className={`xx-pkg-card ${discount > 0 ? "has-discount" : ""} ${pkg.id === "basic_pack" ? "recommended" : ""}`}
hoverable
>
{pkg.id === "basic_pack" && <div className="xx-pkg-badge">热门</div>}
{discount > 0 && (
<Tag color="gold" className="xx-pkg-discount">
会员{10 - discount / 10}折
</Tag>
)}
<div className="xx-pkg-name">{pkg.name}</div>
<div className="xx-pkg-points">
<ThunderboltOutlined /> {pkg.points.toLocaleString()} 积分
</div>
<div className="xx-pkg-price">
<span className="currency">¥</span>
<span className="amount">
{(price / 100).toFixed(price % 100 === 0 ? 0 : 1).replace(/\.0$/, "")}
</span>
{discount > 0 && (
<span className="xx-pkg-origin">¥{(pkg.price / 100).toFixed(0)}</span>
)}
</div>
<div className="xx-pkg-unit">≈¥{unit.toFixed(3)}/积分</div>
<Button
block
type={pkg.id === "basic_pack" ? "primary" : "default"}
loading={buying === pkg.id}
onClick={() => handleBuyPoints(pkg)}
style={{ marginTop: 12 }}
>
立即购买
</Button>
</Card>
</Col>
)
})}
</Row>
</div>
</div>
)
}
+2 -2
View File
@@ -138,11 +138,11 @@ const VoiceLibrary: React.FC = () => {
ttsAudioUrl,
ttsError,
setTtsText,
setTtsVoiceId,
setTtsSpeed,
setTtsEmotion,
setTtsLanguage,
setTtsOpen,
handleVoiceChange,
handleTtsSynthesize,
handleTtsSave,
handleTtsClose,
@@ -378,7 +378,7 @@ const VoiceLibrary: React.FC = () => {
.map((v) => ({ id: v.id, name: v.name }))}
onTtsClose={handleTtsClose}
onTtsTextChange={setTtsText}
onTtsVoiceChange={handleVoiceChange}
onTtsVoiceChange={setTtsVoiceId}
onTtsSpeedChange={setTtsSpeed}
onTtsEmotionChange={setTtsEmotion}
onTtsLanguageChange={setTtsLanguage}
@@ -1,4 +1,4 @@
import React, { useMemo } from "react"
import React from "react"
import { Modal } from "antd"
import { type TtsModalProps, type TtsStatus } from "./tts-modal/types"
import TextInputSection from "./tts-modal/TextInputSection"
@@ -9,7 +9,6 @@ import LanguageControl from "./tts-modal/LanguageControl"
import SynthesizeButton from "./tts-modal/SynthesizeButton"
import ErrorAlert from "./tts-modal/ErrorAlert"
import ResultPanel from "./tts-modal/ResultPanel"
import { PRESET_TTS_LANGUAGE_OPTIONS, CLONE_TTS_LANGUAGE_OPTIONS } from "./tts-modal/constants"
/** AI 配音弹窗 */
const TtsModal: React.FC<TtsModalProps> = ({
@@ -33,13 +32,6 @@ const TtsModal: React.FC<TtsModalProps> = ({
onSynthesize,
onSave,
}) => {
// 预置音色(含空默认)仅支持 zh/en;克隆音色支持全语言列表
const isCloneVoice = useMemo(
() => !!ttsVoiceId && (clonedVoices ?? []).some((v) => v.id === ttsVoiceId),
[ttsVoiceId, clonedVoices],
)
const languageOptions = isCloneVoice ? CLONE_TTS_LANGUAGE_OPTIONS : PRESET_TTS_LANGUAGE_OPTIONS
return (
<Modal title="AI 配音" open={open} onCancel={onClose} footer={null} width={560}>
<div
@@ -65,11 +57,7 @@ const TtsModal: React.FC<TtsModalProps> = ({
}}
>
<EmotionControl emotion={ttsEmotion} onChange={onEmotionChange} />
<LanguageControl
language={ttsLanguage}
onChange={onLanguageChange}
options={languageOptions}
/>
<LanguageControl language={ttsLanguage} onChange={onLanguageChange} />
</div>
<SpeedControl speed={ttsSpeed} onChange={onSpeedChange} />
<SynthesizeButton status={ttsStatus} text={ttsText} onClick={onSynthesize} />
@@ -1,28 +1,13 @@
import React from "react"
import type { TtsLanguage } from "./constants"
import { TTS_LANGUAGE_OPTIONS } from "./constants"
interface LanguageOption {
value: string
label: string
}
import { TTS_LANGUAGE_OPTIONS, type TtsLanguage } from "./constants"
interface LanguageControlProps {
language: TtsLanguage
onChange: (language: TtsLanguage) => void
/** 根据音色来源传入可选语言列表;不传则使用完整列表 */
options?: readonly LanguageOption[]
}
/** 语言选择下拉 */
const LanguageControl: React.FC<LanguageControlProps> = ({
language,
onChange,
options = TTS_LANGUAGE_OPTIONS,
}) => {
// 若当前语言不在可选列表(如从克隆切到预置时 ja/ko 失效),自动回退到中文
const effectiveValue = options.some((o) => o.value === language) ? language : "zh-CN"
const LanguageControl: React.FC<LanguageControlProps> = ({ language, onChange }) => {
return (
<div>
<div
@@ -35,7 +20,7 @@ const LanguageControl: React.FC<LanguageControlProps> = ({
语言
</div>
<select
value={effectiveValue}
value={language}
onChange={(e) => onChange(e.target.value as TtsLanguage)}
style={{
width: "100%",
@@ -49,7 +34,7 @@ const LanguageControl: React.FC<LanguageControlProps> = ({
cursor: "pointer",
}}
>
{options.map((opt) => (
{TTS_LANGUAGE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
@@ -1,40 +1,22 @@
/**
* TTS 情绪选项(对齐后端 CosyVoice 支持:neutral/happy/sad/angry/surprised/fearful/disgusted)
* 后端 normalize_emotion() 会将 value 映射为内部英文枚举
*/
/** TTS 情绪选项(对齐后端 CosyVoice 支持:natural/excited/calm/friendly) */
export const TTS_EMOTION_OPTIONS = [
{ value: "neutral", label: "自然" },
{ value: "happy", label: "开心" },
{ value: "sad", label: "难过" },
{ value: "angry", label: "生气" },
{ value: "surprised", label: "惊讶" },
{ value: "fearful", label: "恐惧" },
{ value: "disgusted", label: "厌恶" },
{ value: "natural", label: "自然" },
{ value: "excited", label: "兴奋" },
{ value: "calm", label: "沉稳" },
{ value: "friendly", label: "亲切" },
] as const
export type TtsEmotion = (typeof TTS_EMOTION_OPTIONS)[number]["value"]
/**
* TTS 语言选项
* - 系统预置音色:仅支持 zh/en(多语言需使用克隆音色)
* - 克隆音色:完整语言列表
*/
export const PRESET_TTS_LANGUAGE_OPTIONS = [
{ value: "zh-CN", label: "中文" },
{ value: "en", label: "英文" },
] as const
export const CLONE_TTS_LANGUAGE_OPTIONS = [
/** TTS 语言选项 */
export const TTS_LANGUAGE_OPTIONS = [
{ value: "zh-CN", label: "中文" },
{ value: "en", label: "英文" },
{ value: "ja", label: "日文" },
{ value: "ko", label: "韩文" },
] as const
export const TTS_LANGUAGE_OPTIONS = CLONE_TTS_LANGUAGE_OPTIONS
export type TtsLanguage = (typeof TTS_LANGUAGE_OPTIONS)[number]["value"]
export type PresetTtsLanguage = (typeof PRESET_TTS_LANGUAGE_OPTIONS)[number]["value"]
export const DEFAULT_TTS_EMOTION: TtsEmotion = "neutral"
export const DEFAULT_TTS_EMOTION: TtsEmotion = "natural"
export const DEFAULT_TTS_LANGUAGE: TtsLanguage = "zh-CN"
@@ -7,18 +7,12 @@ import type { TtsClonedVoiceOption } from "../components/tts-modal/VoiceSelector
import {
DEFAULT_TTS_EMOTION,
DEFAULT_TTS_LANGUAGE,
PRESET_TTS_LANGUAGE_OPTIONS,
type TtsEmotion,
type TtsLanguage,
} from "../components/tts-modal/constants"
export type TtsStatus = "idle" | "synthesizing" | "done" | "error"
/** 预置音色支持的语言 value 集合(允许任意 TtsLanguage 做 Has 检查) */
const PRESET_LANG_SET: ReadonlySet<TtsLanguage> = new Set<TtsLanguage>(
PRESET_TTS_LANGUAGE_OPTIONS.map((o) => o.value as TtsLanguage),
)
/**
* TTS 合成 Hook
* 封装 AI 配音弹窗状态、合成请求、轮询、保存到素材库等逻辑
@@ -49,29 +43,12 @@ export function useTtsSynthesize({
const [ttsError, setTtsError] = useState<string | null>(null)
const ttsTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
/** 切换音色时:预置音色下自动回退到受支持语言(zh-CN/en) */
const handleVoiceChange = useCallback(
(voiceId: string) => {
setTtsVoiceId(voiceId)
const isClone = !!voiceId && clonedVoices.some((v) => v.id === voiceId)
if (!isClone && !PRESET_LANG_SET.has(ttsLanguage)) {
setTtsLanguage(DEFAULT_TTS_LANGUAGE)
}
},
[clonedVoices, ttsLanguage],
)
/** 开始 AI 配音合成 */
const handleTtsSynthesize = useCallback(async () => {
if (!ttsText.trim()) {
message.warning("请输入要合成的文本")
return
}
// 防御:合成前再次校验预置音色语言
const isClone = !!ttsVoiceId && clonedVoices.some((v) => v.id === ttsVoiceId)
const effectiveLang =
!isClone && !PRESET_LANG_SET.has(ttsLanguage) ? DEFAULT_TTS_LANGUAGE : ttsLanguage
setTtsError(null)
setTtsStatus("synthesizing")
setTtsAudioUrl(null)
@@ -83,7 +60,7 @@ export function useTtsSynthesize({
voice_id: ttsVoiceId || undefined,
speed: ttsSpeed,
emotion: ttsEmotion,
language: effectiveLang,
language: ttsLanguage,
})
setTtsJobId(resp.job_id)
@@ -114,7 +91,7 @@ export function useTtsSynthesize({
setTtsStatus("error")
setTtsError(msg)
}
}, [ttsText, ttsVoiceId, ttsSpeed, ttsEmotion, ttsLanguage, clonedVoices])
}, [ttsText, ttsVoiceId, ttsSpeed, ttsEmotion, ttsLanguage])
/** 保存 TTS 结果到素材库 */
const handleTtsSave = useCallback(async () => {
@@ -183,8 +160,6 @@ export function useTtsSynthesize({
setTtsEmotion,
setTtsLanguage,
setTtsOpen,
// 覆写 onVoiceChange(带语言回退)
handleVoiceChange,
// Actions
handleTtsSynthesize,
handleTtsSave,
-16
View File
@@ -96,22 +96,6 @@ const appChildren: RouteObject[] = [
path: "subscription/billing",
lazy: lazyRoute(() => import("@/pages/subscription/Billing")),
},
{
path: "points",
lazy: lazyRoute(() => import("@/pages/points/Center")),
},
{
path: "points/transactions",
lazy: lazyRoute(() => import("@/pages/points/Transactions")),
},
{
path: "points/recharge",
lazy: lazyRoute(() => import("@/pages/points/Packages")),
},
{
path: "points/rules",
lazy: lazyRoute(() => import("@/pages/points/Rules")),
},
{
path: "profile",
lazy: lazyRoute(() => import("@/pages/profile/Settings")),
-83
View File
@@ -1,83 +0,0 @@
/**
* 积分 & 会员状态管理(Zustand)
* - 启动时拉取余额 & 订阅信息
* - 提供刷新、余额扣减(乐观更新)等工具
*/
import { create } from "zustand"
import type { PointsBalance, PointsRulesResponse, SubscriptionCurrent } from "@/api/points/types"
import { getCurrentSubscription, getPointsBalance, getPointsRules } from "@/api/points"
interface PointsState {
/** 积分余额 & 会员状态(来自 /points/balance) */
balance: PointsBalance | null
/** 订阅详情(来自 /subscription/current) */
subscription: SubscriptionCurrent | null
/** 积分消耗规则缓存 */
rules: PointsRulesResponse | null
loading: boolean
error: string | null
/** 初始化:拉取余额 + 订阅信息 + 规则 */
init: () => Promise<void>
/** 强制刷新余额 */
refreshBalance: () => Promise<void>
/** 乐观扣减:在支付/业务发起前调用,失败时用 refreshBalance 兜底 */
optimisticDeduct: (points: number) => void
/** 乐观增加(充值成功后调用) */
optimisticAdd: (points: number) => void
}
export const usePointsStore = create<PointsState>((set, get) => ({
balance: null,
subscription: null,
rules: null,
loading: false,
error: null,
init: async () => {
if (get().loading) return
set({ loading: true, error: null })
try {
const [balance, sub, rules] = await Promise.all([
getPointsBalance(),
getCurrentSubscription(),
getPointsRules(),
])
set({ balance, subscription: sub, rules, loading: false })
} catch (e) {
set({ error: (e as Error).message, loading: false })
}
},
refreshBalance: async () => {
try {
const balance = await getPointsBalance()
set({ balance })
} catch (e) {
set({ error: (e as Error).message })
}
},
optimisticDeduct: (points: number) => {
const b = get().balance
if (!b) return
set({
balance: {
...b,
balance: Math.max(0, b.balance - points),
total_spent: b.total_spent + points,
},
})
},
optimisticAdd: (points: number) => {
const b = get().balance
if (!b) return
set({
balance: {
...b,
balance: b.balance + points,
total_earned: b.total_earned + points,
},
})
},
}))
+2 -1
View File
@@ -6,6 +6,7 @@ import logging
import os
import tempfile
from dataclasses import dataclass
from typing import List
import ffmpeg
from shared.ffmpeg_utils import FFMPEG_ENCODE_CRF, FFMPEG_ENCODE_PRESET
@@ -40,7 +41,7 @@ class VideoProcessor:
def concatenate_videos(
self,
input_paths: list[str],
input_paths: List[str],
output_path: str,
resolution: tuple[int, int] = (1920, 1080),
fps: int = 25,
@@ -18,10 +18,9 @@ from __future__ import annotations
import logging
import subprocess
import tempfile
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from typing import Any, Callable
from sqlalchemy.orm import Session
from video_processing.oss_helpers import download_asset, upload_to_oss
+2 -2
View File
@@ -1,4 +1,4 @@
from datetime import UTC, datetime
from datetime import datetime, timezone
def mark_asset_used_for_generation(asset, times: int = 1) -> None:
@@ -15,6 +15,6 @@ def mark_asset_used_for_generation(asset, times: int = 1) -> None:
asset.metadata = {
**asset.metadata,
"generation_use_count": int(asset.metadata.get("generation_use_count") or 0) + times,
"last_used_at": datetime.now(UTC).isoformat(),
"last_used_at": datetime.now(timezone.utc).isoformat(),
"review_status": asset.metadata.get("review_status") or "pending_review",
}
+2 -2
View File
@@ -1,4 +1,4 @@
from datetime import UTC, datetime
from datetime import datetime, timezone
from packages.adapters.sqlalchemy_impl.models import TitleLibraryModel
@@ -10,6 +10,6 @@ def mark_title_used_for_generation(db, task) -> None:
if title is None:
return
title.usage_count = int(title.usage_count or 0) + 1
title.updated_at = datetime.now(UTC)
title.updated_at = datetime.now(timezone.utc)
db.add(title)
db.commit()
+2 -3
View File
@@ -1,7 +1,6 @@
"""Worker 启动时的初始化任务 — 孤儿任务清理等."""
import logging
from datetime import UTC
from celery.signals import worker_ready
from worker_app.db import SessionLocal
@@ -131,14 +130,14 @@ def cleanup_stale_jobs(timeout_minutes: int = ORPHAN_TASK_TIMEOUT_MINUTES) -> in
Returns:
清理的任务数量
"""
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from packages.adapters.sqlalchemy_impl.models import JobModel
from packages.domain.job import JobStatus
try:
session = SessionLocal()
cutoff = datetime.now(UTC) - timedelta(minutes=timeout_minutes)
cutoff = datetime.now(timezone.utc) - timedelta(minutes=timeout_minutes)
stale_jobs = (
session.query(JobModel)
.filter(
@@ -12,7 +12,7 @@ from __future__ import annotations
import traceback
from dataclasses import dataclass, field
from datetime import UTC, datetime
from datetime import datetime, timezone
from typing import Any
# ── 数据类 ───────────────────────────────────────────────────────────────────
@@ -322,5 +322,5 @@ def build_error_info(error: Exception, stage: str = "render") -> dict[str, Any]:
"message": str(error),
"stack_trace": tb_summary,
"stage": stage,
"failed_at": datetime.now(UTC).isoformat(),
"failed_at": datetime.now(timezone.utc).isoformat(),
}
+8 -8
View File
@@ -2,7 +2,7 @@ import json
import shutil
import subprocess
import tempfile
from datetime import UTC, datetime
from datetime import datetime, timezone
from pathlib import Path
from celery.utils.log import get_task_logger
@@ -447,7 +447,7 @@ def ingest_asset(job_id: str) -> dict:
# Update job status to PROCESSING
job.status = IngestJobStatus.PROCESSING
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
job_repo.update(job)
db.commit()
@@ -703,7 +703,7 @@ def ingest_asset(job_id: str) -> dict:
if codec_val:
asset.codec = str(codec_val)
asset.status = AssetStatus.ERROR
asset.updated_at = datetime.now(UTC)
asset.updated_at = datetime.now(timezone.utc)
asset_repo.update(asset)
else:
# 旧链路兜底:无占位记录(如历史 job 重跑)才新建
@@ -729,7 +729,7 @@ def ingest_asset(job_id: str) -> dict:
job.status = IngestJobStatus.FAILED
job.error_message = f"Invalid media file: {error_reason}"
job.result_asset_id = asset.id
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
job_repo.update(job)
db.commit()
@@ -797,13 +797,13 @@ def ingest_asset(job_id: str) -> dict:
pass
asset.status = AssetStatus.READY
asset.thumbnail_url = thumbnail_url
asset.updated_at = datetime.now(UTC)
asset.updated_at = datetime.now(timezone.utc)
asset_repo.update(asset)
# Update job status to COMPLETED
job.status = IngestJobStatus.COMPLETED
job.result_asset_id = asset.id
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
job_repo.update(job)
db.commit()
@@ -825,7 +825,7 @@ def ingest_asset(job_id: str) -> dict:
if job:
job.status = IngestJobStatus.FAILED
job.error_message = str(e)
job.updated_at = datetime.now(UTC)
job.updated_at = datetime.now(timezone.utc)
job_repo.update(job)
# 将上传时创建的占位 Asset(PROCESSING/UPLOADING)标记为 ERROR,
@@ -857,7 +857,7 @@ def ingest_asset(job_id: str) -> dict:
):
existing.status = AssetStatus.ERROR
existing.metadata = {**(existing.metadata or {}), "ingest_error": str(e)}
existing.updated_at = datetime.now(UTC)
existing.updated_at = datetime.now(timezone.utc)
asset_repo.update(existing)
logger.info(
"Marked asset as ERROR due to ingest failure: asset_id=%s job_id=%s",

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