From 193647a105a97e519acb781538d9fce5cdc20a83 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Thu, 10 Sep 2026 16:21:20 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(ai-avatar):=20=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E8=BF=9B=E5=BA=A6=E5=BC=B9=E7=AA=97=20+=20=E5=AE=9E=E6=97=B6?= =?UTF-8?q?=E7=99=BE=E5=88=86=E6=AF=94=E8=BD=AE=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - submitRender 返回 job 后弹出进度弹窗 - 每 3s 轮询 getRenderJob(jobId) 更新 progress 百分比 - 进度条动画 + 百分比数字显示 - completed 1.5s 后自动关闭弹窗 + success 提示 - failed 显示错误信息 - 关闭窗口后任务仍在后台继续 - 页面卸载时自动清理定时器 --- apps/web/src/pages/ai-avatar/AiAvatarPage.tsx | 158 +++++++++++++++++- 1 file changed, 152 insertions(+), 6 deletions(-) diff --git a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx index 42db7a340..f81bc8e14 100644 --- a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx +++ b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx @@ -21,6 +21,8 @@ import { createLipsyncJob, getLipsyncJob, submitRender, + getRenderJob, + cancelRenderJob, generateSmartCover, } from "./api/aiAvatar" import { @@ -52,9 +54,18 @@ const AiAvatarPage: React.FC = () => { const [lipsyncErrorMessage, setLipsyncErrorMessage] = useState("") /* ── 智能封面加载态 ── */ const [smartCoverLoading, setSmartCoverLoading] = useState(false) + /* ── 渲染进度弹窗 ── */ + const [showRenderModal, setShowRenderModal] = useState(false) + const [renderStatus, setRenderStatus] = useState<"generating" | "completed" | "failed">( + "generating", + ) + const [renderProgress, setRenderProgress] = useState(0) + const [renderErrorMessage, setRenderErrorMessage] = useState("") /* ── 对口型轮询 ── */ const lipsyncTimerRef = useRef | null>(null) + /* ── 渲染进度轮询 ── */ + const renderTimerRef = useRef | null>(null) const togglePanel = useCallback((key: PanelKey) => { setCollapsed((prev) => ({ ...prev, [key]: !prev[key] })) @@ -182,19 +193,19 @@ const AiAvatarPage: React.FC = () => { useEffect(() => { return () => { if (lipsyncTimerRef.current) clearInterval(lipsyncTimerRef.current) + if (renderTimerRef.current) clearInterval(renderTimerRef.current) } }, []) - /* ── 生成视频 ── */ + /* ── 生成视频(含实时进度轮询) ── */ const handleGenerate = useCallback(async () => { - // ② 前置条件提示(#1809) if (!state.lipsyncJob || state.lipsyncJob.status !== "completed") { message.warning("请先生成对口型视频,待对口型完成后再提交渲染") return } state.setIsGenerating(true) try { - await submitRender({ + const job = await submitRender({ lipsync_job_id: state.lipsyncJob.id, script_id: state.script?.id, b_roll_segments: state.bRollSegments.map((seg) => ({ @@ -206,12 +217,39 @@ const AiAvatarPage: React.FC = () => { pip_position: seg.pip_position, pip_scale: seg.pip_scale, })) as never, - // 字段映射:build_title_drawtext_filter 真实口径 text/font_size/font_color/position/... title_config: buildTitleConfigPayload(state.titleConfig), - // cover_config:智能封面 cover_url + 截帧 timestamp cover_config: buildCoverConfigPayload(state.coverConfig, state.coverConfig.smart_cover_url), }) - message.success("渲染任务已提交,可在视频管理中查看进度") + + // 打开渲染进度弹窗,启动轮询 + setShowRenderModal(true) + setRenderStatus("generating") + setRenderProgress(job.progress ?? 0) + setRenderErrorMessage("") + + if (renderTimerRef.current) clearInterval(renderTimerRef.current) + renderTimerRef.current = setInterval(async () => { + try { + const updated = await getRenderJob(job.id) + setRenderProgress(updated.progress ?? 0) + if (updated.status === "completed") { + if (renderTimerRef.current) clearInterval(renderTimerRef.current) + renderTimerRef.current = null + setRenderStatus("completed") + setTimeout(() => { + setShowRenderModal(false) + message.success("视频渲染完成!可在视频管理中查看") + }, 1500) + } else if (updated.status === "failed") { + if (renderTimerRef.current) clearInterval(renderTimerRef.current) + renderTimerRef.current = null + setRenderStatus("failed") + setRenderErrorMessage(updated.error_message || "渲染失败,请重试") + } + } catch (pollErr) { + console.error("[渲染] 轮询失败:", pollErr) + } + }, 3000) } catch (err) { console.error("渲染任务提交失败:", err) message.error(err instanceof Error ? err.message : "渲染任务提交失败,请重试") @@ -221,6 +259,18 @@ const AiAvatarPage: React.FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [state.lipsyncJob, state.script, state.bRollSegments, state.titleConfig, state.coverConfig]) + /* ── 关闭渲染进度弹窗 ── */ + const handleCancelRender = useCallback(() => { + if (renderTimerRef.current) { + clearInterval(renderTimerRef.current) + renderTimerRef.current = null + } + setShowRenderModal(false) + setRenderStatus("generating") + setRenderProgress(0) + setRenderErrorMessage("") + }, []) + /* ── 智能封面:调后端 MediaKit 选帧接口(#1822) ── */ const handleSmartCover = useCallback(async () => { // 基于对口型成片抽帧,必须先完成对口型 @@ -510,6 +560,102 @@ const AiAvatarPage: React.FC = () => { )} + + {/* 渲染进度弹窗 */} + {showRenderModal && ( +
+
e.stopPropagation()}> +
+ 视频渲染 + +
+
+ {renderStatus === "generating" && ( + <> +
+
+ 正在生成视频,请稍后 +
+
+ {renderProgress}% +
+
+
+
+
+ 请勿关闭页面,完成后将自动提示 +
+ + )} + {renderStatus === "completed" && ( + <> +
+
+ 视频渲染完成! +
+ + )} + {renderStatus === "failed" && ( + <> +
+
视频渲染失败
+ {renderErrorMessage && ( +
+ {renderErrorMessage} +
+ )} + + )} +
+
+ {renderStatus === "generating" && ( + + )} + {renderStatus !== "generating" && ( + + )} +
+
+
+ )}
) } -- 2.54.0 From e40dede02c0c03a8ac30a6272c9ea3d7e66133a9 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Thu, 10 Sep 2026 16:24:05 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(ai-avatar):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=9C=AA=E4=BD=BF=E7=94=A8=E7=9A=84=20cancelRenderJob=20import?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=20ESLint=20=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/pages/ai-avatar/AiAvatarPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx index f81bc8e14..81e783f7a 100644 --- a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx +++ b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx @@ -22,7 +22,6 @@ import { getLipsyncJob, submitRender, getRenderJob, - cancelRenderJob, generateSmartCover, } from "./api/aiAvatar" import { -- 2.54.0 From 3c674db818b37e302276eb21a0a78fd96c03fb4f Mon Sep 17 00:00:00 2001 From: CI Bot Date: Thu, 10 Sep 2026 16:38:57 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat(ai-avatar):=20=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=90=8E=E6=8F=90=E7=A4=BA=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E5=88=B0=E6=88=90=E7=89=87=E5=BA=93=20+=20=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E6=88=90=E7=89=87=E8=B7=B3=E8=BD=AC=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 渲染完成后显示'视频已保存到成片库' - 添加'📁 查看成片'按钮,点击跳转到 /app/products - 移除自动关闭弹窗逻辑,由用户主动关闭或跳转 --- apps/web/src/pages/ai-avatar/AiAvatarPage.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx index 81e783f7a..8196c3d53 100644 --- a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx +++ b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx @@ -5,6 +5,7 @@ */ import React, { useState, useCallback, useEffect, useRef } from "react" import { message } from "antd" +import { useNavigate } from "react-router-dom" import "./AiAvatar.css" import { useAiAvatar } from "./hooks/useAiAvatar" import { PanelVideoSelector } from "./components/PanelVideoSelector" @@ -35,6 +36,7 @@ type PanelKey = "video" | "voice" | "script" | "lipsync" | "title" | "cover" const AiAvatarPage: React.FC = () => { const state = useAiAvatar() + const navigate = useNavigate() const [currentStep, setCurrentStep] = useState<1 | 2>(1) const [collapsed, setCollapsed] = useState>({ video: false, @@ -235,10 +237,7 @@ const AiAvatarPage: React.FC = () => { if (renderTimerRef.current) clearInterval(renderTimerRef.current) renderTimerRef.current = null setRenderStatus("completed") - setTimeout(() => { - setShowRenderModal(false) - message.success("视频渲染完成!可在视频管理中查看") - }, 1500) + message.success("视频已生成并保存到成片库") } else if (updated.status === "failed") { if (renderTimerRef.current) clearInterval(renderTimerRef.current) renderTimerRef.current = null @@ -624,8 +623,18 @@ const AiAvatarPage: React.FC = () => { <>
- 视频渲染完成! + 视频已保存到成片库
+ )} {renderStatus === "failed" && ( -- 2.54.0 From ba9a0bc4ec8dfeca750c168e7dc352d548e01a21 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Thu, 10 Sep 2026 18:27:32 +0800 Subject: [PATCH 4/4] chore: trigger CI rerender -- 2.54.0