Compare commits

...

1 Commits

Author SHA1 Message Date
CI Bot d655920ec1 style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m9s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m11s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m19s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 45s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m2s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 58s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m18s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m36s
AI Code Review / AI Code Review (pull_request) Successful in 6m57s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 7m56s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m50s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 8m32s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 11m40s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 17m15s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
2026-10-10 22:55:58 +00:00
4 changed files with 56 additions and 13 deletions
+6 -2
View File
@@ -66,13 +66,17 @@ export function isAnalysisStage(stage: ViralVideoStage | undefined): boolean {
}
/** 从 job 对象取到有效阶段(兼容 progress_stage/current_stage 两种字段名) */
export function getJobStage(job: { current_stage?: ViralVideoStage; progress_stage?: ViralVideoStage } | null | undefined): ViralVideoStage | undefined {
export function getJobStage(
job: { current_stage?: ViralVideoStage; progress_stage?: ViralVideoStage } | null | undefined,
): ViralVideoStage | undefined {
if (!job) return undefined
return job.current_stage || job.progress_stage
}
/** 从 job 对象取到阶段提示文案(兼容 phase_message/progress_message) */
export function getJobPhaseMessage(job: { phase_message?: string; progress_message?: string } | null | undefined): string {
export function getJobPhaseMessage(
job: { phase_message?: string; progress_message?: string } | null | undefined,
): string {
if (!job) return ""
return job.phase_message || job.progress_message || ""
}
@@ -1141,7 +1141,12 @@ const ViralVideoPage: React.FC = () => {
message.success("已提交图片分析…")
} catch (err: unknown) {
message.error(err instanceof Error ? err.message : "分析失败")
setTask({ uiStep: "failed", analysisError: err instanceof Error ? err.message : "分析失败", copyError: undefined, videoError: undefined })
setTask({
uiStep: "failed",
analysisError: err instanceof Error ? err.message : "分析失败",
copyError: undefined,
videoError: undefined,
})
}
}
@@ -1181,7 +1186,12 @@ const ViralVideoPage: React.FC = () => {
// 轮询会在 status=copy_generated 时推进 uiStep
} catch (err: unknown) {
message.error(err instanceof Error ? err.message : "文案生成失败")
setTask({ uiStep: "failed", copyError: err instanceof Error ? err.message : "文案生成失败", videoError: undefined, analysisError: undefined })
setTask({
uiStep: "failed",
copyError: err instanceof Error ? err.message : "文案生成失败",
videoError: undefined,
analysisError: undefined,
})
}
}
@@ -1209,7 +1219,13 @@ const ViralVideoPage: React.FC = () => {
// P1-5 修复:旧 /generate 接口只跑图片分析→wait_user_confirm 就停,永远出不了视频。
// 没有 storyboard 时直接提示用户先生成文案,不再走死路径。
message.warning("请先生成文案后再生成视频")
setTask({ uiStep: task.storyboard ? "step2_copy_ready" : (task.imageAnalysis ? "step1_done" : "step1_upload") })
setTask({
uiStep: task.storyboard
? "step2_copy_ready"
: task.imageAnalysis
? "step1_done"
: "step1_upload",
})
return
}
setTask({ job, jobId: job.id })
@@ -1224,7 +1240,12 @@ const ViralVideoPage: React.FC = () => {
?.message ||
"积分不足,请充值"
message.error(detail)
setTask({ uiStep: "step3_ready", videoError: detail, copyError: undefined, analysisError: undefined })
setTask({
uiStep: "step3_ready",
videoError: detail,
copyError: undefined,
analysisError: undefined,
})
return
}
const msg = err instanceof Error ? err.message : "提交失败"
@@ -1244,7 +1265,13 @@ const ViralVideoPage: React.FC = () => {
else if (isCopyStage(retryStage)) retryStep = "step2_generating"
else if (task.storyboard) retryStep = "step2_copy_ready"
else if (task.imageAnalysis) retryStep = "step1_done"
setTask({ job: j, uiStep: retryStep, videoError: undefined, copyError: undefined, analysisError: undefined })
setTask({
job: j,
uiStep: retryStep,
videoError: undefined,
copyError: undefined,
analysisError: undefined,
})
message.success("已重试")
} catch (err: unknown) {
message.error(err instanceof Error ? err.message : "重试失败")
@@ -1287,7 +1314,10 @@ const ViralVideoPage: React.FC = () => {
const srv = task.job?.error_message || task.job?.error_msg
if (srv) {
// P1-4: 后端错误消息已含"XX失败"前缀时去重
const cleaned = srv.replace(/^(视频生成失败|文案生成失败|图片分析失败|Wan 3\.0 视频生成失败)[::]\s*/g, "")
const cleaned = srv.replace(
/^(视频生成失败|文案生成失败|图片分析失败|Wan 3\.0 视频生成失败)[::]\s*/g,
"",
)
const st = getJobStage(task.job)
if (isVideoStage(st)) return { stage: "video" as const, msg: cleaned }
if (isImageAnalysisStage(st)) return { stage: "image" as const, msg: cleaned }
@@ -1296,10 +1326,13 @@ const ViralVideoPage: React.FC = () => {
return null
})()
const errLabel =
activeError?.stage === "image" ? "图片分析失败"
: activeError?.stage === "copy" ? "文案生成失败"
: activeError?.stage === "video" ? "视频生成失败"
: ""
activeError?.stage === "image"
? "图片分析失败"
: activeError?.stage === "copy"
? "文案生成失败"
: activeError?.stage === "video"
? "视频生成失败"
: ""
const step2Enabled =
task.uiStep === "step1_done" ||
task.uiStep === "step2_generating" ||
@@ -1,6 +1,11 @@
import { useCallback, useEffect, useRef } from "react"
import { getViralVideoJob } from "@/api/viral-video"
import { isAnalysisStage, getJobStage, type ViralVideoJob, type ViralVideoStatus } from "@/api/viral-video/types"
import {
isAnalysisStage,
getJobStage,
type ViralVideoJob,
type ViralVideoStatus,
} from "@/api/viral-video/types"
const TERMINAL: ViralVideoStatus[] = ["completed", "failed", "cancelled"]
@@ -1324,6 +1324,7 @@ def _resolve_tts_voice_id(job: ViralVideoJob) -> str:
def _parse_speaker_segments(text: str) -> list[tuple[str, str]]:
"""解析 voiceover_script 中的 [speaker:xxx] 标记,返回 [(speaker, text), ...] 列表。"""
import re
if not text:
return []