From 0e93d52db105016e52c981acf3b16339be9e58d9 Mon Sep 17 00:00:00 2001 From: hh <2587203630@qq.com> Date: Mon, 13 Jul 2026 17:11:37 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=87=AA=E7=94=B1=E5=88=9B=E4=BD=9C?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=90=8E=E5=90=8C=E6=AD=A5=E4=BD=99=E9=A2=9D?= =?UTF-8?q?=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/frontend/src/App.tsx | 9 ++++++++- core/frontend/src/routes/free-create.tsx | 20 ++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/core/frontend/src/App.tsx b/core/frontend/src/App.tsx index 87b8b89..9213892 100644 --- a/core/frontend/src/App.tsx +++ b/core/frontend/src/App.tsx @@ -218,6 +218,13 @@ export function App() { if (data) setUnreadCount(data.unread_count); }, []); + // 自由创作自行管理长视频轮询;任务终态后只需同步顶栏依赖的两项全局数据, + // 不必像通用 action 一样重拉商品、项目和素材列表。 + const refreshFreeCreateShell = useCallback(() => { + void api.billingSummary().then((summary) => setBilling(summary)).catch(() => {}); + void reloadNotifications(); + }, [reloadNotifications]); + // YYX#row22:拉未读生成任务汇总(导航胶囊 + 商品角标) const reloadAiUnread = useCallback(async () => { const data = await api.aiTasksUnread().catch(() => null); @@ -925,7 +932,7 @@ export function App() { case "assetFactory": return ; case "freeCreate": - return setNotice({ type, text })} />; + return setNotice({ type, text })} onTaskSettled={refreshFreeCreateShell} />; case "imageOptimize": return navigate("assetFactory")} navigate={navigate} onGenerate={generateImages} onResume={resumeImages} />; case "modelPhoto": diff --git a/core/frontend/src/routes/free-create.tsx b/core/frontend/src/routes/free-create.tsx index e87dd80..4d928d4 100644 --- a/core/frontend/src/routes/free-create.tsx +++ b/core/frontend/src/routes/free-create.tsx @@ -47,15 +47,20 @@ function pollDelay(count: number): number { let refKeySeq = 0; const nextRefKey = () => `ref-${Date.now()}-${refKeySeq++}`; -export function FreeCreatePage({ modelConfigs, onNotify }: { +export function FreeCreatePage({ modelConfigs, onNotify, onTaskSettled }: { modelConfigs: ModelConfig[]; onNotify: (type: "success" | "error" | "info", text: string) => void; + onTaskSettled: () => void; }) { // App 每次渲染会重建 onNotify 箭头函数;用 ref 稳定身份,否则依赖它的 effect(首屏拉取/轮询) // 会随 App 任意 setState(如 30s 未读轮询)反复重跑 → 任务流无谓全量刷新(实测踩坑)。 const onNotifyRef = useRef(onNotify); onNotifyRef.current = onNotify; const notify = useCallback((type: "success" | "error" | "info", text: string) => onNotifyRef.current(type, text), []); + // 同 onNotify:App 外壳重渲染时回调身份会变化,不能让任务列表/轮询 effect 因此重启。 + const onTaskSettledRef = useRef(onTaskSettled); + onTaskSettledRef.current = onTaskSettled; + const refreshGlobalShell = useCallback(() => onTaskSettledRef.current(), []); const videoConfigs = useMemo( () => modelConfigs.filter((c) => c.capability === "video" && FC_MODELS.some((m) => m.name === c.name)), [modelConfigs] @@ -128,6 +133,8 @@ export function FreeCreatePage({ modelConfigs, onNotify }: { } else { stopPolling(id); setProgress((prev) => { const next = { ...prev }; delete next[id]; sessionStorage.setItem(PROGRESS_KEY, JSON.stringify(next)); return next; }); + // 任务结算会改变余额,并产生已有的生成/计费消息;同步 App 外壳即可,页面任务流仍由 patchTask 管理。 + refreshGlobalShell(); if (data.task.status === "succeeded") notify("success", "视频生成完成"); else if (data.task.status === "failed") notify("error", data.task.error_message || "视频生成失败"); } @@ -137,7 +144,7 @@ export function FreeCreatePage({ modelConfigs, onNotify }: { } }; pollTimersRef.current.set(id, window.setTimeout(() => void tick(), pollDelay(pollCountsRef.current.get(id) || 0))); - }, [patchTask, stopPolling, notify]); + }, [patchTask, stopPolling, notify, refreshGlobalShell]); // 平滑进度动画:每 2s 给在途任务 +0.6~1.6%,封顶 95,sessionStorage 持久(刷新可续) useEffect(() => { @@ -332,7 +339,12 @@ export function FreeCreatePage({ modelConfigs, onNotify }: { return next; }); if (isInFlight(data.task.status)) schedulePoll(data.task.id); - else if (data.task.status === "failed") notify("error", data.task.error_message || "创建任务失败"); + else { + // 极快失败/成功时不会进入 schedulePoll,也要同步一次全局余额与消息徽标。 + refreshGlobalShell(); + if (data.task.status === "succeeded") notify("success", "视频生成完成"); + else if (data.task.status === "failed") notify("error", data.task.error_message || "创建任务失败"); + } return true; } catch (error) { setTasks((prev) => prev.filter((t) => t.id !== localId)); @@ -341,7 +353,7 @@ export function FreeCreatePage({ modelConfigs, onNotify }: { } finally { setSubmitting(false); } - }, [schedulePoll, notify]); + }, [schedulePoll, notify, refreshGlobalShell]); const handleSend = useCallback(async () => { const prompt = promptRef.current?.getText() || "";