Compare commits

..

1 Commits

Author SHA1 Message Date
saas-agent a4c6f492a7 feat(generate): #1970 智能剪辑流程重构 - 选择模式→素材→标题→确认→封面
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 3s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m35s
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m31s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m48s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 28s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m4s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 5m13s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 5m40s
AI Code Review / AI Code Review (pull_request) Successful in 7m0s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m42s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 17m36s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 18s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 50s
## 主流程改造
- STEPS 改为 5 步:选择模式→选择素材→选择标题→确认生成→选择封面
- 原"选择配音"步骤从主流程移除,改为 Step1 下一步分支弹窗
- Step1 新组件 Step1EditMode:模式卡片(🎲随机混剪/📖叙事剪辑,紫底紫框) + 生成设置(数量-/+1-10、比例9:16/16:9、智能降重开关默认开)

## 新增 3 个弹窗
- VoiceSelectModal(随机混剪):复用配音库音频卡片+▶试听,无 TTS/克隆入口
- ScriptSelectModal(叙事剪辑):搜索框+300ms防抖+命中黄高亮+标签筛选(全部/带货/工厂/测评/教程/口播/种草)+max-height 420px 卡片列表
- TtsVoiceModal(叙事剪辑合成配音):Tabs(系统音色/🎙️我的克隆音色) + 2列音色卡片 + 合成按钮 + loading(spinner+"正在合成配音..."+"通常10-30秒")

## Step2 素材页
- 叙事剪辑模式显示"🤖 AI智能匹配"紫色提示卡

## Step4 确认页
- 摘要卡片按 editMode 展示剪辑模式+配音来源/文案标题+TTS音色+比例+降重

## payload 新增字段
- assembly_mode('random'|'narrative')、script_id、tts_voice_id、tts_voice_source('preset'|'clone')、dedup_enabled(默认true)、video_ratio('9:16')

## 不改动
- Step3/Step5 内容、文案库/配音库页面、AI数字人
2026-09-18 00:02:18 +08:00
3 changed files with 13 additions and 38 deletions
+2 -12
View File
@@ -443,20 +443,10 @@ def extract_from_douyin(
result.source, len(text), time.time() - t0,
)
last_err_stage = "asr"
except HTTPException as exc:
# 下载超时(504)是明确的网络错误,直接抛出
if exc.status_code == status.HTTP_504_GATEWAY_TIMEOUT:
raise
# 本地 ASR 不可用/失败(502/503)时记录后继续走 desc 兜底,
# 不直接抛 502,避免 API 镜像缺 worker 模块时整条链路挂掉
logger.warning("本地 ASR 链路失败(status=%d): %s", exc.status_code, exc.detail)
text = ""
# 如果是下载失败(非ASR错误),保持stage为download
if "语音识别" in str(exc.detail) or "ASR" in str(exc.detail):
last_err_stage = "asr"
except HTTPException:
raise
except Exception as exc: # noqa: BLE001
logger.warning("本地 ASR 链路异常: %s", exc)
text = ""
# ── Phase C:结果判定 & 兜底 ──
+1 -6
View File
@@ -38,12 +38,7 @@ def transcribe_to_text(media_path: str | Path) -> str:
ASRTranscriptionError: ASR 调用失败
"""
# 延迟导入,避免循环依赖和启动时副作用
try:
from apps.worker.services.asr_service_factory import get_asr_service
except ImportError as exc:
# API 镜像未打包 worker 代码(本地 ASR 依赖 worker 的 asr_service_factory
logger.warning("本地 ASR 不可用(apps.worker 未安装): %s", exc)
raise ASRNotConfiguredError("本地 ASR 服务不可用(worker 模块未安装)") from exc
from apps.worker.services.asr_service_factory import get_asr_service
asr = get_asr_service()
if asr is None:
+10 -20
View File
@@ -187,39 +187,29 @@ def test_asr_not_configured_returns_503(fake_user):
# ── ASR 转写失败 → 502 ────────────────────────────────────────
def test_asr_transcription_failure_returns_503_with_desc_fallback(fake_user):
"""ASR 转写异常MediaKit+本地都失败)→ desc 兜底;desc 也空则 503"""
from app.api.routes import scripts_ai
from app.services.mediakit_client import MediaKitError
from fastapi import HTTPException
def test_asr_transcription_failure_returns_502(fake_user):
"""ASR 转写异常 → 502(被 _direct_url_download_and_local_asr 包装)"""
from app.services.script_asr_service import ASRTranscriptionError
scripts_ai = _import_target()
body = scripts_ai.ExtractFromDouyinRequest(url="https://v.douyin.com/xxxxx/")
# MediaKit 失败
fake_mk = mock.MagicMock()
fake_mk.is_available = True
from app.services.mediakit_client import MediaKitError
fake_mk.asr_submit.side_effect = MediaKitError("ASR failed", code="TaskFailed")
# desc 为空 → 最终 503stage=asr
with _fake_resolver_success(desc=""):
with mock.patch("app.api.routes.scripts_ai.get_mediakit_client", return_value=fake_mk):
with mock.patch(
"app.api.routes.scripts_ai._direct_url_download_and_local_asr",
side_effect=HTTPException(status_code=502, detail="语音识别失败: No module named 'apps.worker'"),
):
with pytest.raises(HTTPException) as exc:
scripts_ai.extract_from_douyin(request=body, current_user=fake_user, db=mock.MagicMock())
assert exc.value.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
# desc 非空 → desc 兜底成功,返回 200
with _fake_resolver_success(desc="这是视频文案描述"):
with mock.patch("app.api.routes.scripts_ai.get_mediakit_client", return_value=fake_mk):
with mock.patch(
"app.api.routes.scripts_ai._direct_url_download_and_local_asr",
side_effect=HTTPException(status_code=502, detail="语音识别失败"),
):
resp = scripts_ai.extract_from_douyin(request=body, current_user=fake_user, db=mock.MagicMock())
assert resp.text == "这是视频文案描述"
assert resp.duration_seconds == 0.0
with pytest.raises(HTTPException) as exc:
scripts_ai.extract_from_douyin(request=body, current_user=fake_user, db=mock.MagicMock())
assert exc.value.status_code == status.HTTP_502_BAD_GATEWAY
# ── 下载超时 → 504 ────────────────────────────────────────────