Compare commits

...

1 Commits

Author SHA1 Message Date
Coze Agent 2332b5ef98 fix(viral-video): #2041 删除「生成进度」阶段列表面板,保留轮询做状态驱动
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) Successful in 3s
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker 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 / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 3m20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m20s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 3m5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m4s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m14s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 7m54s
AI Code Review / AI Code Review (pull_request) Successful in 8m7s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 10m37s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m50s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 18m51s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 30s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m18s
2026-10-01 01:33:39 +08:00
4 changed files with 21 additions and 152 deletions
+10 -31
View File
@@ -16,8 +16,8 @@ export type ViralVideoStatus =
"pending" | "running" | "wait_user_confirm" | "completed" | "failed" | "cancelled"
/**
* 前端展示的流水线阶段(已移除 muse talk 口型同步;后端枚举保留字段以兼容旧数据,不展示)。
* 百分比对齐后端 worker _emit_progress 的真实值。
* 后端流水线阶段字符串。前端不展示逐阶段进度列表,仅保留类型
* 用于轮询时判断当前在哪个大阶段(分析中 vs 视频生成中)以选择轮询间隔/文案。
*/
export type ViralVideoStage =
| "image_analysis"
@@ -29,40 +29,19 @@ export type ViralVideoStage =
| "tts"
| "bgm_select"
| "rendering"
| "musetalk"
| "uploading"
export interface StageDisplay {
label: string
/** 该阶段完成时到达的总体进度百分比 */
pct: number
}
export const STAGE_DISPLAYS: Record<ViralVideoStage, StageDisplay> = {
image_analysis: { label: "图片分析", pct: 15 },
video_analysis: { label: "参考视频风格分析", pct: 20 },
intent_parsing: { label: "意图理解", pct: 30 },
copy_fusion: { label: "文案融合创作", pct: 50 },
storyboard: { label: "分镜生成", pct: 60 },
review: { label: "AI审核", pct: 70 },
tts: { label: "配音生成", pct: 75 },
bgm_select: { label: "BGM匹配", pct: 78 },
rendering: { label: "视频渲染", pct: 88 },
uploading: { label: "上传成片", pct: 100 },
}
/** 阶段顺序(用于进度列表渲染) */
export const STAGE_ORDER: ViralVideoStage[] = [
/** 分析类阶段(image_analysis / video_analysis / intent_parsing):属于「开始分析」阶段 */
const ANALYSIS_STAGES = new Set<ViralVideoStage>([
"image_analysis",
"video_analysis",
"intent_parsing",
"copy_fusion",
"storyboard",
"review",
"tts",
"bgm_select",
"rendering",
"uploading",
]
])
export function isAnalysisStage(stage: ViralVideoStage | undefined): boolean {
return !!stage && ANALYSIS_STAGES.has(stage)
}
export interface StyleTemplate {
id: string
@@ -639,57 +639,6 @@
font-size: 12px;
}
/* ── 阶段进度列表 ──────────────────────────────────── */
.vv-stages {
display: flex;
flex-direction: column;
gap: 4px;
margin-top: 8px;
}
.vv-stage {
display: flex;
align-items: center;
gap: 10px;
font-size: 12px;
color: #9ca3af;
padding: 5px 0;
}
.vv-stage-dot {
width: 16px;
height: 16px;
border-radius: 50%;
border: 2px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: center;
font-size: 9px;
background: #fff;
flex-shrink: 0;
}
.vv-stage.active {
color: #7c3aed;
}
.vv-stage.active .vv-stage-dot {
border-color: #7c3aed;
background: #f3f0ff;
}
.vv-stage.done {
color: #10b981;
}
.vv-stage.done .vv-stage-dot {
border-color: #10b981;
background: #10b981;
color: #fff;
}
.vv-stage.failed {
color: #ef4444;
}
.vv-stage.failed .vv-stage-dot {
border-color: #ef4444;
background: #ef4444;
color: #fff;
}
.vv-progress-bar {
width: 100%;
height: 6px;
@@ -8,7 +8,6 @@ import {
CaretDownOutlined,
VideoCameraOutlined,
SoundOutlined,
CheckOutlined,
ReloadOutlined,
WarningFilled,
BulbFilled,
@@ -20,12 +19,10 @@ import { uploadAssetDirect, getAssetLibraries, getAssetsByKind } from "@/api/ass
import {
FUSION_LEVELS,
STYLE_STRENGTHS,
STAGE_DISPLAYS,
STAGE_ORDER,
isAnalysisStage,
type FusionLevel,
type StyleStrength,
type ViralVideoJob,
type ViralVideoStage,
} from "@/api/viral-video/types"
import { generateViralVideo, confirmViralVideoIntent, retryViralVideo } from "@/api/viral-video"
import { useViralVideoPolling } from "./hooks/useViralVideoPolling"
@@ -517,30 +514,11 @@ const ViralVideoPage: React.FC = () => {
task.job.status === "running" &&
!isAwaitingConfirm &&
!task.generating &&
(!task.job.progress_stage ||
["image_analysis", "video_analysis", "intent_parsing"].includes(task.job.progress_stage)))
(!task.job.progress_stage || isAnalysisStage(task.job.progress_stage)))
const isGenerating =
task.generating ||
(task.job?.status === "running" &&
task.job.progress_stage &&
!["image_analysis", "video_analysis", "intent_parsing"].includes(task.job.progress_stage))
task.generating || (task.job?.status === "running" && !isAnalysisStage(task.job.progress_stage))
const isDone = task.job?.status === "completed"
const isFailed = task.job?.status === "failed"
const progressPct = task.job?.progress_percent ?? 0
const currentStage: ViralVideoStage | undefined = task.job?.progress_stage as
ViralVideoStage | undefined
/* ── 阶段列表派生显示状态 ── */
const stageState = (s: ViralVideoStage): "pending" | "active" | "done" | "failed" => {
if (isFailed && s === currentStage) return "failed"
if (isDone) return "done"
if (!currentStage) return "pending"
const cur = STAGE_DISPLAYS[currentStage].pct
const mine = STAGE_DISPLAYS[s].pct
if (s === currentStage) return "active"
if (mine < cur) return "done"
return "pending"
}
return (
<div className="vv-page">
@@ -1128,44 +1106,6 @@ const ViralVideoPage: React.FC = () => {
</div>
</Section>
<Section
title="生成进度"
step={3}
collapsed={task.collapsed.stages}
onToggle={() => toggleCollapse("stages")}
>
{task.jobId && (isAnalyzing || isGenerating || isDone || isFailed) && (
<>
<div className="vv-progress-bar">
<div className="vv-progress-fill" style={{ width: `${progressPct}%` }} />
</div>
<div className="vv-progress-meta">
<span>{task.job?.progress_message || "处理中…"}</span>
<span className="vv-progress-pct">{progressPct}%</span>
</div>
</>
)}
<div className="vv-stages">
{STAGE_ORDER.map((s) => {
const st = stageState(s)
return (
<div key={s} className={`vv-stage ${st}`}>
<span className="vv-stage-dot">
{st === "done" ? (
<CheckOutlined style={{ fontSize: 8 }} />
) : st === "failed" ? (
"×"
) : (
""
)}
</span>
<span>{STAGE_DISPLAYS[s].label}</span>
</div>
)
})}
</div>
</Section>
<div className="vv-credits">
<span>
<SoundOutlined style={{ marginRight: 4 }} />
@@ -1206,7 +1146,7 @@ const ViralVideoPage: React.FC = () => {
)}
{isGenerating && (
<button className="vv-btn vv-btn-primary" disabled>
<span className="vv-spinner" /> 视频生成中 {progressPct}%…
<span className="vv-spinner" /> AI 正在生成视频中…
</button>
)}
</div>
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef } from "react"
import { getViralVideoJob } from "@/api/viral-video"
import type { ViralVideoJob, ViralVideoStatus } from "@/api/viral-video/types"
import { isAnalysisStage, type ViralVideoJob, type ViralVideoStatus } from "@/api/viral-video/types"
const TERMINAL: ViralVideoStatus[] = ["completed", "failed", "cancelled"]
@@ -10,9 +10,9 @@ export interface UseViralVideoPollingOptions {
}
/**
* 爆款视频任务 HTTP 轮询 hook(后端暂未暴露 WS 桥,轮询兜底)。
* 爆款视频任务 HTTP 轮询 hook。
* 负责持续拉取任务状态并回调给上层;上层负责根据状态/阶段切换 UI 文案。
* 任务进入终态(completed/failed/cancelled)后自动停止。
* 后端进度条通过 progress_percent 真实值推进,前端不再依赖阶段插值。
*/
export function useViralVideoPolling(
jobId: string | null | undefined,
@@ -43,14 +43,15 @@ export function useViralVideoPolling(
return
}
if (stoppedRef.current) return
// 渲染阶段(Seedance 多段视频生成较慢)拉长轮询间隔
// 视频渲染阶段(Seedance 多段视频生成较慢)拉长轮询间隔
const inRender = job.progress_stage === "rendering"
const nextDelay = inRender ? 3000 : intervalMs
// 分析阶段走默认间隔即可
const isAnalyzing = isAnalysisStage(job.progress_stage)
const nextDelay = inRender ? 3000 : isAnalyzing ? 2000 : intervalMs
timerRef.current = setTimeout(() => pollOnce(id), nextDelay)
} catch (_err) {
failCountRef.current += 1
if (stoppedRef.current) return
// 指数退避,最多退到 10s
const delay = Math.min(intervalMs * 2 ** Math.min(failCountRef.current, 3), 10000)
timerRef.current = setTimeout(() => pollOnce(id), delay)
}