From 2332b5ef98a48b92cc5547eb6da15d949f219373 Mon Sep 17 00:00:00 2001 From: Coze Agent Date: Thu, 1 Oct 2026 01:33:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(viral-video):=20#2041=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E3=80=8C=E7=94=9F=E6=88=90=E8=BF=9B=E5=BA=A6=E3=80=8D=E9=98=B6?= =?UTF-8?q?=E6=AE=B5=E5=88=97=E8=A1=A8=E9=9D=A2=E6=9D=BF=EF=BC=8C=E4=BF=9D?= =?UTF-8?q?=E7=95=99=E8=BD=AE=E8=AF=A2=E5=81=9A=E7=8A=B6=E6=80=81=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/api/viral-video/types.ts | 41 +++-------- .../src/pages/viral-video/ViralVideoPage.css | 51 -------------- .../src/pages/viral-video/ViralVideoPage.tsx | 68 ++----------------- .../viral-video/hooks/useViralVideoPolling.ts | 13 ++-- 4 files changed, 21 insertions(+), 152 deletions(-) diff --git a/apps/web/src/api/viral-video/types.ts b/apps/web/src/api/viral-video/types.ts index 1ea182f55..54b38c06b 100644 --- a/apps/web/src/api/viral-video/types.ts +++ b/apps/web/src/api/viral-video/types.ts @@ -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 = { - 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([ "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 diff --git a/apps/web/src/pages/viral-video/ViralVideoPage.css b/apps/web/src/pages/viral-video/ViralVideoPage.css index 72f60b29b..074204928 100644 --- a/apps/web/src/pages/viral-video/ViralVideoPage.css +++ b/apps/web/src/pages/viral-video/ViralVideoPage.css @@ -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; diff --git a/apps/web/src/pages/viral-video/ViralVideoPage.tsx b/apps/web/src/pages/viral-video/ViralVideoPage.tsx index 8b498e5eb..ffec6f64e 100644 --- a/apps/web/src/pages/viral-video/ViralVideoPage.tsx +++ b/apps/web/src/pages/viral-video/ViralVideoPage.tsx @@ -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 (
@@ -1128,44 +1106,6 @@ const ViralVideoPage: React.FC = () => {
-
toggleCollapse("stages")} - > - {task.jobId && (isAnalyzing || isGenerating || isDone || isFailed) && ( - <> -
-
-
-
- {task.job?.progress_message || "处理中…"} - {progressPct}% -
- - )} -
- {STAGE_ORDER.map((s) => { - const st = stageState(s) - return ( -
- - {st === "done" ? ( - - ) : st === "failed" ? ( - "×" - ) : ( - "" - )} - - {STAGE_DISPLAYS[s].label} -
- ) - })} -
-
-
@@ -1206,7 +1146,7 @@ const ViralVideoPage: React.FC = () => { )} {isGenerating && ( )}
diff --git a/apps/web/src/pages/viral-video/hooks/useViralVideoPolling.ts b/apps/web/src/pages/viral-video/hooks/useViralVideoPolling.ts index 9d1e1c6bf..6d1893102 100644 --- a/apps/web/src/pages/viral-video/hooks/useViralVideoPolling.ts +++ b/apps/web/src/pages/viral-video/hooks/useViralVideoPolling.ts @@ -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) } -- 2.54.0