Compare commits

...

2 Commits

Author SHA1 Message Date
CI Bot 5ba8e4b13f style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 50s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 54s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m20s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m39s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m10s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m6s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m5s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m6s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 2m17s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m28s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m18s
AI Code Review / AI Code Review (pull_request) Successful in 6m17s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 7m33s
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 / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 4s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m26s
2026-09-10 07:20:56 +00:00
CI Test 3d2e94b884 fix(#1830): 改进异常处理 - 缩小异常类型并添加日志记录
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 Web Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 22s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 59s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m37s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m59s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m0s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m4s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 2m6s
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
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-10 15:18:35 +08:00
11 changed files with 51 additions and 70 deletions
+1 -1
View File
@@ -336,7 +336,7 @@ def get_project_asset_diagnosis(
assets.extend(asset_repository.list_by_library(library.id))
except HTTPException:
raise
except Exception:
except Exception as e:
logger.exception("素材诊断查询失败: project_id=%s", project_id)
# 返回空诊断结果,避免 500
return _build_diagnosis(project_id, [])
+1 -1
View File
@@ -178,7 +178,7 @@ def _cleanup_expired_uploads() -> int:
cleaned += 1
logger.info(f"Cleaned up expired upload: {upload_id}")
except Exception as e:
logger.warning(f"Failed to cleanup upload metadata {meta_file}: {e}")
logger.exception("Failed to cleanup upload metadata: %s", meta_file)
return cleaned
@@ -107,8 +107,8 @@ def create_variant_plans(
)
if _latest:
source_plan_id = _latest.id
except Exception:
logger.warning("[variant-plans] 源 plan 解析失败", exc_info=True)
except Exception as e:
logger.exception("[variant-plans] 源 plan 解析失败")
if not source_plan_id:
raise HTTPException(
@@ -121,8 +121,8 @@ def create_variant_plans(
from app.api.routes.generation_tasks import _query_voice_durations
voice_durations = _query_voice_durations(db, voices)
except Exception:
logger.warning("[variant-plans] 配音时长查询失败(按占位段长选片)", exc_info=True)
except Exception as e:
logger.exception("[variant-plans] 配音时长查询失败(按占位段长选片)")
voice_durations = [0.0] * request.count
from app.services.edit_plan_service import EditPlanService
@@ -143,7 +143,7 @@ def create_variant_plans(
except HTTPException:
raise
except Exception as e:
logger.error("[variant-plans] 选片异常: %s", e, exc_info=True)
logger.exception("[variant-plans] 选片异常")
raise HTTPException(status_code=500, detail="选片失败,请稍后重试") from e
# 组装 clips 响应
@@ -66,7 +66,7 @@ def _build_asset_analyses(
video_urls.append(url)
valid_asset_ids.append(aid)
except Exception as e:
logger.warning("获取素材URL失败: asset_id=%s error=%s", aid, str(e))
logger.exception("获取素材URL失败: asset_id=%s", aid)
if not video_urls:
logger.info("无可用视频素材,跳过视频理解分析")
@@ -108,7 +108,7 @@ def _build_asset_analyses(
return analyses
except Exception as e:
logger.warning("MediaKit 视频理解异常,将降级到无分析模式: %s", str(e))
logger.exception("MediaKit 视频理解异常,将降级到无分析模式: %s", e)
return {}
@@ -176,8 +176,8 @@ def editor_ai_recommend(
)
try:
db.rollback()
except Exception:
pass
except Exception as e:
logger.exception("db rollback failed in ai_recommend")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="AI推荐结果保存失败,请稍后重试",
@@ -132,16 +132,16 @@ def _build_asset_url_map(
result: dict[str, str | None] = {}
try:
storage = get_storage_service()
except Exception:
logger.warning("获取存储服务失败,跳过asset_url生成")
except Exception as e:
logger.exception("获取存储服务失败,跳过asset_url生成: %s", e)
return {aid: None for aid in asset_ids}
# 批量查询所有 Asset(单次 SQL IN 查询,避免 N+1)
try:
assets = asset_repo.find_by_ids(unique_ids)
asset_map = {a.id: a for a in assets}
except Exception:
logger.warning("批量查询素材失败: asset_ids=%s", asset_ids, exc_info=True)
except Exception as e:
logger.exception("批量查询素材失败: asset_ids=%s", asset_ids)
return {aid: None for aid in asset_ids if aid}
for aid in unique_ids:
@@ -155,8 +155,8 @@ def _build_asset_url_map(
result[aid] = None
continue
result[aid] = storage.get_download_url(storage_key, expires_seconds=3600)
except Exception:
logger.warning("生成素材签名URL失败: asset_id=%s", aid, exc_info=True)
except Exception as e:
logger.exception("生成素材签名URL失败: asset_id=%s", aid)
result[aid] = None
return result
@@ -487,7 +487,7 @@ def _get_mediakit_recommendations(
video_urls.append(url)
valid_asset_ids.append(asset_id)
except Exception as e:
logger.warning("获取素材URL失败: asset_id=%s error=%s", asset_id, e)
logger.exception("获取素材URL失败: asset_id=%s", asset_id)
if not video_urls:
return {}
@@ -563,7 +563,7 @@ def _get_mediakit_recommendations(
return recommendations
except Exception as e:
logger.warning("MediaKit 智能选片异常,降级为随机选择: %s", e)
logger.exception("MediaKit 智能选片异常,降级为随机选择: %s", e)
return {}
@@ -860,7 +860,7 @@ def create_clips_from_assets_editor(
duplicate_warning = None
if dup_rate > 50:
duplicate_warning = f"查重率 {dup_rate:.1f}% 超过50%,建议更换素材或模板"
logger.warning(
logger.exception(
"from-assets 成片查重率超标: plan_id=%s dup_rate=%.1f%%",
plan_id,
dup_rate,
@@ -960,8 +960,8 @@ def _update_mediakit_recommendations_async( # pragma: no cover
# 尝试获取存储服务(用于生成视频 URL)
try:
storage = get_storage_service()
except Exception:
logger.warning("后台任务: 获取存储服务失败,跳过 SceneChange 更新")
except Exception as e:
logger.exception("后台任务: 获取存储服务失败,跳过 SceneChange 更新: %s", e)
return
# 获取 MediaKit 客户端
@@ -988,7 +988,7 @@ def _update_mediakit_recommendations_async( # pragma: no cover
try:
video_url = storage.get_download_url(storage_key)
except Exception as e:
logger.warning("后台任务: 获取素材URL失败: asset_id=%s error=%s", asset_id, e)
logger.exception("后台任务: 获取素材URL失败: asset_id=%s", asset_id)
# 构建该素材的占用区间列表(排除已更新片段)
def _get_other_segments(asset_id_inner, clip_id_inner):
@@ -1041,10 +1041,9 @@ def _update_mediakit_recommendations_async( # pragma: no cover
)
except Exception as cache_err:
# 缓存写入失败不影响本次片段更新
logger.warning(
"后台任务: 场景点缓存写入失败: asset_id=%s error=%s",
logger.exception(
"后台任务: 场景点缓存写入失败: asset_id=%s",
asset_id,
cache_err,
)
# SceneChange 未获得有效结果 → 尝试 analyze_videos 作为 fallback
@@ -1125,10 +1124,9 @@ def _update_mediakit_recommendations_async( # pragma: no cover
plan_id,
)
except Exception as me:
logger.warning(
"后台任务: 同步素材区间记录失败,回滚本次片段更新: clip_id=%s error=%s",
logger.exception(
"后台任务: 同步素材区间记录失败,回滚本次片段更新: clip_id=%s",
clip.id,
me,
)
db.rollback()
continue
@@ -1145,10 +1143,10 @@ def _update_mediakit_recommendations_async( # pragma: no cover
recommended_start,
)
except Exception as ue:
logger.warning("后台任务: 单个片段更新失败: clip_id=%s error=%s", clip.id, ue)
logger.exception("后台任务: 单个片段更新失败: clip_id=%s", clip.id)
try:
db.rollback()
except Exception:
except Exception as e:
pass
continue
@@ -1156,15 +1154,15 @@ def _update_mediakit_recommendations_async( # pragma: no cover
except Exception as e:
# 后台任务失败不影响已创建的片段,静默处理
logger.warning("后台任务异常: plan_id=%s error=%s", plan_id, e, exc_info=True)
logger.exception("后台任务异常: plan_id=%s", plan_id)
if db:
try:
db.rollback()
except Exception:
except Exception as e:
pass
finally:
if db:
try:
db.close()
except Exception:
except Exception as e:
pass
+4 -4
View File
@@ -163,12 +163,12 @@ def create_voice_clone(
celery_app.send_task("worker.process_voice_clone", args=[profile.id])
logger.info(f"Celery task dispatched for voice clone {profile.id}")
except Exception as e:
logger.error(f"Failed to dispatch Celery task: {e}")
logger.exception("Failed to dispatch Celery task")
# P2-3: Celery 调度失败时标记 profile 为 failed,避免永久卡在 processing
try:
workflow.process_clone_failure(profile.id, f"Celery 任务调度失败: {e}")
except Exception as inner_e:
logger.error(f"Failed to mark profile as failed after dispatch error: {inner_e}")
logger.exception("Failed to mark profile as failed after dispatch error")
return _to_response(profile)
@@ -277,12 +277,12 @@ def retry_voice_clone(
celery_app.send_task("worker.process_voice_clone", args=[profile.id])
logger.info(f"Celery task dispatched for voice clone retry {profile.id}")
except Exception as e:
logger.error(f"Failed to dispatch Celery task: {e}")
logger.exception("Failed to dispatch Celery task")
# P2-3: Celery 调度失败时标记 profile 为 failed,避免永久卡在 processing
try:
workflow.process_clone_failure(profile.id, f"Celery 任务调度失败: {e}")
except Exception as inner_e:
logger.error(f"Failed to mark profile as failed after dispatch error: {inner_e}")
logger.exception("Failed to mark profile as failed after dispatch error")
return _to_response(profile)
+5 -4
View File
@@ -106,7 +106,7 @@ def _resolve_preset_preview_url(
logger.info("Preset voice preview generated: %s", voice_id)
return audio_url
except Exception as e:
logger.warning("Failed to generate preview for %s, using fallback: %s", voice_id, e)
logger.exception("Failed to generate preset voice preview: voice_id=%s", voice_id)
return fallback_url
@@ -126,7 +126,8 @@ def _resolve_all_preset_preview_urls(
for p in presets:
try:
result_map[p.voice_id] = _resolve_preset_preview_url(p.voice_id, p.preview_url, cosyvoice)
except Exception:
except Exception as e:
logger.exception("Failed to resolve preset preview URL: voice_id=%s", p.voice_id)
result_map[p.voice_id] = p.preview_url
return result_map
@@ -731,8 +732,8 @@ def _find_or_create_voice_library_for_extract(*, user_id, project_repository, as
if session is not None:
try:
session.rollback()
except Exception:
pass
except Exception as e:
logger.exception("session rollback failed in _find_or_create_voice_library")
for lib in asset_library_repository.find_by_project(project.id):
kind = lib.kind.value if hasattr(lib.kind, "value") else lib.kind
if kind == AssetLibraryKind.VOICE.value:
@@ -484,7 +484,6 @@
margin-bottom: 12px;
}
.aa-lipsync-preview {
width: 100%;
max-width: 240px;
@@ -1249,4 +1248,3 @@
width: auto;
min-width: 300px;
}
@@ -211,14 +211,7 @@ const AiAvatarPage: React.FC = () => {
state.setIsGenerating(false)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
state.lipsyncJob,
state.script,
state.bRollSegments,
state.titleConfig,
state.coverConfig,
])
}, [state.lipsyncJob, state.script, state.bRollSegments, state.titleConfig, state.coverConfig])
/* ── 智能封面:调后端 MediaKit 选帧接口(#1822 ── */
const handleSmartCover = useCallback(async () => {
@@ -375,7 +368,10 @@ const AiAvatarPage: React.FC = () => {
<span className="aa-panel__toggle"></span>
</div>
<div className="aa-panel__body">
<PanelTitleConfig titleConfig={state.titleConfig} onUpdate={state.updateTitleConfig} />
<PanelTitleConfig
titleConfig={state.titleConfig}
onUpdate={state.updateTitleConfig}
/>
</div>
</div>
@@ -34,8 +34,7 @@ export function PanelLipsyncPreview({
onRemoveBRoll,
titleConfig,
}: PanelLipsyncPreviewProps) {
const isGenerating =
lipsyncJob?.status === "pending" || lipsyncJob?.status === "processing"
const isGenerating = lipsyncJob?.status === "pending" || lipsyncJob?.status === "processing"
const isDone = lipsyncJob?.status === "completed"
const isFailed = lipsyncJob?.status === "failed"
@@ -78,9 +77,7 @@ export function PanelLipsyncPreview({
<div className="aa-lipsync-section__title">
<span style={{ marginRight: 8 }}>🎞 </span>
{bRollSegments.length > 0 && (
<span className="aa-broll-badge">
🎬 {bRollSegments.length}
</span>
<span className="aa-broll-badge">🎬 {bRollSegments.length} </span>
)}
</div>
<div className="aa-lipsync-actions">
@@ -104,10 +101,7 @@ export function PanelLipsyncPreview({
alt={seg.asset.name}
/>
) : (
<span
className="aa-broll-item__thumb"
style={{ padding: "6px 4px" }}
>
<span className="aa-broll-item__thumb" style={{ padding: "6px 4px" }}>
🎬
</span>
)}
@@ -189,11 +183,7 @@ export function PanelLipsyncPreview({
<div className="aa-lipsync-actions">
{isDone ? (
<button
type="button"
className="aa-btn aa-btn--full"
onClick={onGenerateLipsync}
>
<button type="button" className="aa-btn aa-btn--full" onClick={onGenerateLipsync}>
🔄
</button>
) : isGenerating ? (
@@ -55,9 +55,7 @@ export function PanelScript({
value={scriptText}
readOnly={scriptTab === "library"}
placeholder={
scriptTab === "library"
? "点击上方按钮,从文案库选择文案…"
: "请输入数字人口播文案…"
scriptTab === "library" ? "点击上方按钮,从文案库选择文案…" : "请输入数字人口播文案…"
}
onChange={(e) => onScriptTextChange(e.target.value)}
/>