From 5bf13763690932403569556860f14ff499e0893e Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 24 Sep 2026 12:22:51 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(ai-avatar):=20=E5=AF=B9=E5=8F=A3?= =?UTF-8?q?=E5=9E=8B=E7=94=9F=E6=88=90=E5=BC=B9=E7=AA=97=E5=8A=A0=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E8=AE=A1=E6=97=B6=E5=99=A8=EF=BC=88mm:ss=EF=BC=89+=20?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=97=B6=E6=80=BB=E8=80=97=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/pages/ai-avatar/AiAvatarPage.tsx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx index 2e2b3e293..e0eb0594f 100644 --- a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx +++ b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx @@ -61,6 +61,10 @@ const AiAvatarPage: React.FC = () => { "generating", ) const [lipsyncErrorMessage, setLipsyncErrorMessage] = useState("") + /* ── 对口型耗时计时(秒) ── */ + const [lipsyncElapsed, setLipsyncElapsed] = useState(0) + const lipsyncStartAtRef = useRef(0) + const lipsyncTickRef = useRef | null>(null) /* ── 渲染进度弹窗 ── */ const [showRenderModal, setShowRenderModal] = useState(false) const [renderStatus, setRenderStatus] = useState<"generating" | "completed" | "failed">( @@ -222,6 +226,13 @@ const AiAvatarPage: React.FC = () => { setShowLipsyncModal(true) setLipsyncStatus("generating") setLipsyncErrorMessage("") + // 启动计时器 + lipsyncStartAtRef.current = Date.now() + setLipsyncElapsed(0) + if (lipsyncTickRef.current) clearInterval(lipsyncTickRef.current) + lipsyncTickRef.current = setInterval(() => { + setLipsyncElapsed(Math.floor((Date.now() - lipsyncStartAtRef.current) / 1000)) + }, 1000) const asset = await getAssetById(video.id) const videoUrl = asset?.file_url @@ -265,6 +276,8 @@ const AiAvatarPage: React.FC = () => { state.setLipsyncJob(updated) if (updated.status === "completed") { if (lipsyncTimerRef.current) clearInterval(lipsyncTimerRef.current) + if (lipsyncTickRef.current) { clearInterval(lipsyncTickRef.current); lipsyncTickRef.current = null } + setLipsyncElapsed(Math.floor((Date.now() - lipsyncStartAtRef.current) / 1000)) setLipsyncStatus("completed") setTimeout(() => { setShowLipsyncModal(false) @@ -272,6 +285,7 @@ const AiAvatarPage: React.FC = () => { }, 1000) } else if (updated.status === "failed") { if (lipsyncTimerRef.current) clearInterval(lipsyncTimerRef.current) + if (lipsyncTickRef.current) { clearInterval(lipsyncTickRef.current); lipsyncTickRef.current = null } setLipsyncStatus("failed") setLipsyncErrorMessage(updated.error_message || "对口型生成失败") } @@ -285,6 +299,7 @@ const AiAvatarPage: React.FC = () => { data: (err as { response?: { data?: unknown } })?.response?.data, message: err instanceof Error ? err.message : String(err), }) + if (lipsyncTickRef.current) { clearInterval(lipsyncTickRef.current); lipsyncTickRef.current = null } setShowLipsyncModal(false) message.error(err instanceof Error ? err.message : "对口型任务提交失败,请重试") } @@ -305,15 +320,21 @@ const AiAvatarPage: React.FC = () => { clearInterval(lipsyncTimerRef.current) lipsyncTimerRef.current = null } + if (lipsyncTickRef.current) { + clearInterval(lipsyncTickRef.current) + lipsyncTickRef.current = null + } setShowLipsyncModal(false) setLipsyncStatus("generating") setLipsyncErrorMessage("") + setLipsyncElapsed(0) }, []) // 清理轮询 useEffect(() => { return () => { if (lipsyncTimerRef.current) clearInterval(lipsyncTimerRef.current) + if (lipsyncTickRef.current) clearInterval(lipsyncTickRef.current) if (renderTimerRef.current) clearInterval(renderTimerRef.current) } }, []) @@ -963,6 +984,19 @@ const AiAvatarPage: React.FC = () => {
对口型视频生成中…
+
+ {`${Math.floor(lipsyncElapsed / 60) + .toString() + .padStart(2, "0")}:${(lipsyncElapsed % 60).toString().padStart(2, "0")}`} +
请勿关闭页面,完成后将自动提示
@@ -974,6 +1008,18 @@ const AiAvatarPage: React.FC = () => {
对口型视频生成完成
+
+ 总耗时 {Math.floor(lipsyncElapsed / 60) + .toString() + .padStart(2, "0")}:{(lipsyncElapsed % 60).toString().padStart(2, "0")} +
)} {lipsyncStatus === "failed" && ( -- 2.54.0 From 0db8e661a573c28a37420bb4e70fd2fa7dae9833 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Thu, 24 Sep 2026 12:30:17 +0800 Subject: [PATCH 2/2] =?UTF-8?q?style(ai-avatar):=20prettier=20=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/pages/ai-avatar/AiAvatarPage.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx index e0eb0594f..b3c0537f4 100644 --- a/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx +++ b/apps/web/src/pages/ai-avatar/AiAvatarPage.tsx @@ -276,7 +276,10 @@ const AiAvatarPage: React.FC = () => { state.setLipsyncJob(updated) if (updated.status === "completed") { if (lipsyncTimerRef.current) clearInterval(lipsyncTimerRef.current) - if (lipsyncTickRef.current) { clearInterval(lipsyncTickRef.current); lipsyncTickRef.current = null } + if (lipsyncTickRef.current) { + clearInterval(lipsyncTickRef.current) + lipsyncTickRef.current = null + } setLipsyncElapsed(Math.floor((Date.now() - lipsyncStartAtRef.current) / 1000)) setLipsyncStatus("completed") setTimeout(() => { @@ -285,7 +288,10 @@ const AiAvatarPage: React.FC = () => { }, 1000) } else if (updated.status === "failed") { if (lipsyncTimerRef.current) clearInterval(lipsyncTimerRef.current) - if (lipsyncTickRef.current) { clearInterval(lipsyncTickRef.current); lipsyncTickRef.current = null } + if (lipsyncTickRef.current) { + clearInterval(lipsyncTickRef.current) + lipsyncTickRef.current = null + } setLipsyncStatus("failed") setLipsyncErrorMessage(updated.error_message || "对口型生成失败") } @@ -299,7 +305,10 @@ const AiAvatarPage: React.FC = () => { data: (err as { response?: { data?: unknown } })?.response?.data, message: err instanceof Error ? err.message : String(err), }) - if (lipsyncTickRef.current) { clearInterval(lipsyncTickRef.current); lipsyncTickRef.current = null } + if (lipsyncTickRef.current) { + clearInterval(lipsyncTickRef.current) + lipsyncTickRef.current = null + } setShowLipsyncModal(false) message.error(err instanceof Error ? err.message : "对口型任务提交失败,请重试") } @@ -1016,9 +1025,11 @@ const AiAvatarPage: React.FC = () => { fontVariantNumeric: "tabular-nums", }} > - 总耗时 {Math.floor(lipsyncElapsed / 60) + 总耗时{" "} + {Math.floor(lipsyncElapsed / 60) .toString() - .padStart(2, "0")}:{(lipsyncElapsed % 60).toString().padStart(2, "0")} + .padStart(2, "0")} + :{(lipsyncElapsed % 60).toString().padStart(2, "0")} )} -- 2.54.0