fix: harden generation task recovery

This commit is contained in:
2026-06-08 20:57:40 +08:00
parent eb095bbe98
commit af5081d382
5 changed files with 212 additions and 20 deletions
+87 -9
View File
@@ -37,7 +37,7 @@ import {
import "../../styles/pages/workbench.css";
import type { WebGenerationPreviewTask, WebUserSession } from "../../types";
import { aiGenerationClient } from "../../api/aiGenerationClient";
import { claimGenerationSlot, getActiveGenerationTaskCount, getGenerationUserKey, releaseGenerationSlot } from "../../api/generationConcurrency";
import { claimGenerationSlot, getActiveGenerationTaskCount, getEffectiveGenerationLimit, getGenerationUserKey, releaseGenerationSlot } from "../../api/generationConcurrency";
import { preUploadReference, resolvePreUploadedUrl } from "../../api/referenceUploadService";
import { assetClient } from "../../api/assetClient";
import { communityClient } from "../../api/communityClient";
@@ -1056,6 +1056,54 @@ function WorkbenchPage({
persistKeepaliveTasks(rest);
};
const releaseKeepaliveTaskLocally = useCallback((taskId: string, options?: { cancelServer?: boolean }) => {
const task = keepaliveTasksRef.current[taskId];
taskAbortControllersRef.current.get(taskId)?.abort();
taskAbortControllersRef.current.delete(taskId);
removeKeepaliveTask(taskId);
if (task && options?.cancelServer) {
aiGenerationClient.cancelTask(task.taskId).catch(() => {});
}
syncActiveGenerationUi();
}, [syncActiveGenerationUi]);
const releaseKeepaliveTaskAfterNetworkLoss = useCallback((task: WorkbenchKeepaliveTask, progress: number) => {
const latestTask = {
...task,
progress,
statusLabel: "网络中断,已释放提交按钮",
};
void patchConversationMessage(task.conversationId, task.assistantMessageId, {
status: "failed",
taskProgress: Math.max(progress, 100),
taskStatusLabel: "网络中断",
body: "网络中断,当前任务已停止等待并释放提交按钮。请确认网络恢复后重新提交任务。",
});
upsertKeepaliveTask(latestTask);
releaseKeepaliveTaskLocally(task.taskId, { cancelServer: true });
if (activeConversationIdRef.current === task.conversationId) {
setIsGenerating(false);
setGenerationStatus("网络中断,已释放提交按钮");
setGenerationProgress(0);
}
}, [patchConversationMessage, releaseKeepaliveTaskLocally]);
const cancelActiveKeepaliveTasksOnPageExit = useCallback(() => {
const tasks = Object.values(keepaliveTasksRef.current);
if (!tasks.length) return;
tasks.forEach((task) => {
taskAbortControllersRef.current.get(task.taskId)?.abort();
taskAbortControllersRef.current.delete(task.taskId);
releaseGenerationSlot(task.concurrencySlotId);
aiGenerationClient.cancelTaskOnUnload(task.taskId);
});
keepaliveTasksRef.current = {};
persistKeepaliveTasks({});
setIsGenerating(false);
setGenerationStatus("已释放未完成任务");
setGenerationProgress(0);
}, []);
const runKeepalivePoll = useCallback(
(task: WorkbenchKeepaliveTask) => {
if (taskAbortControllersRef.current.has(task.taskId)) return;
@@ -1082,6 +1130,10 @@ function WorkbenchPage({
if (abortController.signal.aborted) return;
if (attempt > 0) await sleep(3000);
if (abortController.signal.aborted) return;
if (typeof navigator !== "undefined" && navigator.onLine === false) {
releaseKeepaliveTaskAfterNetworkLoss(task, lastKnownProgress);
return;
}
let status;
try {
@@ -1095,7 +1147,8 @@ function WorkbenchPage({
taskProgress: 100,
taskStatusLabel: "任务异常",
});
removeKeepaliveTask(task.taskId);
releaseKeepaliveTaskLocally(task.taskId, { cancelServer: true });
onRefreshUsage?.();
return;
}
continue;
@@ -1323,6 +1376,24 @@ function WorkbenchPage({
};
}, [runKeepalivePoll]);
useEffect(() => {
const handlePageHide = () => {
cancelActiveKeepaliveTasksOnPageExit();
};
const handleOnline = () => {
Object.values(keepaliveTasksRef.current).forEach((task) => runKeepalivePoll(task));
syncActiveGenerationUi();
};
window.addEventListener("pagehide", handlePageHide);
window.addEventListener("online", handleOnline);
aiGenerationClient.flushPendingTaskCancellations();
return () => {
window.removeEventListener("pagehide", handlePageHide);
window.removeEventListener("online", handleOnline);
};
}, [cancelActiveKeepaliveTasksOnPageExit, runKeepalivePoll, syncActiveGenerationUi]);
useEffect(() => {
persistPromptHistory(promptHistory);
}, [promptHistory]);
@@ -1987,7 +2058,7 @@ function WorkbenchPage({
const trimmedPrompt = (promptOverride ?? inputValue).trim();
if (!trimmedPrompt) return;
const userKey = getGenerationUserKey(session?.user.id);
if (activeMode !== "chat" && getActiveGenerationTaskCount(userKey) >= 3) return;
if (activeMode !== "chat" && getActiveGenerationTaskCount(userKey) >= getEffectiveGenerationLimit()) return;
setReferencePreviewOpen(false);
let conversationId = activeConversationIdRef.current ?? activeConversationId;
@@ -2466,8 +2537,11 @@ function WorkbenchPage({
setProjectError("仅支持对视频结果进行超分");
return;
}
if (getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id)) >= 3) {
setProjectError(`当前已有 ${getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id))} 个任务进行中(上限3个),请等待任一任务完成后再提交新任务`);
const userKey = getGenerationUserKey(session?.user.id);
const activeCount = getActiveGenerationTaskCount(userKey);
const limit = getEffectiveGenerationLimit();
if (activeCount >= limit) {
setProjectError(`当前已有 ${activeCount} 个任务进行中(上限${limit}个),请等待任一任务完成后再提交新任务`);
return;
}
if (!isAuthenticated) {
@@ -2588,8 +2662,11 @@ function WorkbenchPage({
setProjectError("仅支持对图片结果进行超分");
return;
}
if (getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id)) >= 3) {
setProjectError(`当前已有 ${getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id))} 个任务进行中(上限3个),请等待任一任务完成后再提交新任务`);
const userKey = getGenerationUserKey(session?.user.id);
const activeCount = getActiveGenerationTaskCount(userKey);
const limit = getEffectiveGenerationLimit();
if (activeCount >= limit) {
setProjectError(`当前已有 ${activeCount} 个任务进行中(上限${limit}个),请等待任一任务完成后再提交新任务`);
return;
}
if (!isAuthenticated) {
@@ -2762,13 +2839,14 @@ function WorkbenchPage({
};
const activeGenerationCount = getActiveGenerationTaskCount(getGenerationUserKey(session?.user.id));
const generationLimitReached = activeMode !== "chat" && activeGenerationCount >= 3;
const activeGenerationLimit = getEffectiveGenerationLimit();
const generationLimitReached = activeMode !== "chat" && activeGenerationCount >= activeGenerationLimit;
const promptIsEmpty = !inputValue.trim();
const sendDisabled = promptIsEmpty || generationLimitReached;
const sendButtonTitle = promptIsEmpty
? "输入内容后可发送"
: generationLimitReached
? `当前已有 ${activeGenerationCount} 个任务进行中,请等待任一任务完成`
? `当前已有 ${activeGenerationCount} 个任务进行中(上限 ${activeGenerationLimit} 个),请等待任一任务完成`
: billingEstimate.title;
const suggestedPrompts = [