Codex/generation task reliability #25

Merged
stringadmin merged 4 commits from codex/generation-task-reliability into master 2026-06-08 07:49:25 +00:00
5 changed files with 22 additions and 6 deletions
Showing only changes of commit fe5a839b37 - Show all commits
+5
View File
@@ -8,6 +8,7 @@ import ToastContainer from "./components/toast/ToastContainer";
import { toast } from "./components/toast/toastStore"; import { toast } from "./components/toast/toastStore";
import { aiGenerationClient } from "./api/aiGenerationClient"; import { aiGenerationClient } from "./api/aiGenerationClient";
import { keyServerClient } from "./api/keyServerClient"; import { keyServerClient } from "./api/keyServerClient";
import { setUserMaxConcurrency } from "./api/generationConcurrency";
import { notificationClient } from "./api/notificationClient"; import { notificationClient } from "./api/notificationClient";
import { import {
SERVER_SESSION_REPLACED_EVENT, SERVER_SESSION_REPLACED_EVENT,
@@ -466,6 +467,7 @@ function App() {
const clearAuthenticatedState = useCallback((options?: { resetView?: boolean }) => { const clearAuthenticatedState = useCallback((options?: { resetView?: boolean }) => {
clearAllUserStorage(); clearAllUserStorage();
clearSessionState(); clearSessionState();
setUserMaxConcurrency(null);
setProjects([]); setProjects([]);
setProjectsLoaded(true); setProjectsLoaded(true);
setUsage(emptyUsageSummary); setUsage(emptyUsageSummary);
@@ -574,6 +576,7 @@ function App() {
const nextSession = await keyServerClient.getCurrentSession(); const nextSession = await keyServerClient.getCurrentSession();
if (cancelled) return; if (cancelled) return;
setSession(nextSession); setSession(nextSession);
setUserMaxConcurrency(nextSession?.user?.maxConcurrency);
await hydrateAccountData(nextSession); await hydrateAccountData(nextSession);
}; };
@@ -606,6 +609,7 @@ function App() {
if (cancelled) return; if (cancelled) return;
if (nextSession) { if (nextSession) {
setSession(nextSession); setSession(nextSession);
setUserMaxConcurrency(nextSession?.user?.maxConcurrency);
} else { } else {
clearAuthenticatedState({ resetView: true }); clearAuthenticatedState({ resetView: true });
} }
@@ -943,6 +947,7 @@ function App() {
async (nextSession: WebUserSession) => { async (nextSession: WebUserSession) => {
hideSessionReplaced(); hideSessionReplaced();
setSession(nextSession); setSession(nextSession);
setUserMaxConcurrency(nextSession?.user?.maxConcurrency);
await hydrateAccountData(nextSession); await hydrateAccountData(nextSession);
if (nextSession.user.email && !nextSession.user.emailVerified) { if (nextSession.user.email && !nextSession.user.emailVerified) {
+14 -3
View File
@@ -7,10 +7,20 @@ interface GenerationSlot {
createdAt: number; createdAt: number;
} }
const MAX_ACTIVE_GENERATION_TASKS = 3; const DEFAULT_MAX_ACTIVE_GENERATION_TASKS = 3;
const STALE_SLOT_MS = 6 * 60 * 60 * 1000; const STALE_SLOT_MS = 6 * 60 * 60 * 1000;
const activeSlots = new Map<string, GenerationSlot>(); const activeSlots = new Map<string, GenerationSlot>();
let userMaxConcurrency: number | null = null;
export function setUserMaxConcurrency(limit: number | null | undefined): void {
userMaxConcurrency = typeof limit === "number" && limit > 0 ? limit : null;
}
function getEffectiveLimit(): number {
return userMaxConcurrency ?? DEFAULT_MAX_ACTIVE_GENERATION_TASKS;
}
export function getGenerationUserKey(userId?: string | number | null): string { export function getGenerationUserKey(userId?: string | number | null): string {
return userId === undefined || userId === null || userId === "" ? "anonymous" : String(userId); return userId === undefined || userId === null || userId === "" ? "anonymous" : String(userId);
} }
@@ -39,8 +49,9 @@ export function claimGenerationSlot(input: {
}): () => void { }): () => void {
pruneStaleSlots(); pruneStaleSlots();
const activeCount = getActiveGenerationTaskCount(input.userKey); const activeCount = getActiveGenerationTaskCount(input.userKey);
if (activeCount >= MAX_ACTIVE_GENERATION_TASKS) { const effectiveLimit = getEffectiveLimit();
throw new Error("当前账号同时最多生成 3 个图片/视频任务,请等待已有任务完成后再提交。"); if (activeCount >= effectiveLimit) {
throw new Error(`当前账号同时最多生成 ${effectiveLimit} 个图片/视频任务,请等待已有任务完成后再提交。`);
} }
const id = input.id || `generation-slot-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; const id = input.id || `generation-slot-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+1
View File
@@ -434,6 +434,7 @@ function normalizeUser(raw: unknown): WebUserSession["user"] | null {
candidate.enterpriseBalance ?? candidate.enterpriseBalance ??
candidate.enterprise_balance, candidate.enterprise_balance,
), ),
maxConcurrency: toNumber(candidate.maxConcurrency ?? candidate.max_concurrency),
activePackages: toActivePackages(candidate.activePackages ?? candidate.active_packages), activePackages: toActivePackages(candidate.activePackages ?? candidate.active_packages),
}; };
} }
+1 -3
View File
@@ -44,7 +44,6 @@ export function waitForTask(
let settled = false; let settled = false;
let cleanup: (() => void) | null = null; let cleanup: (() => void) | null = null;
let timeoutId: ReturnType<typeof setTimeout> | null = null; let timeoutId: ReturnType<typeof setTimeout> | null = null;
let sseConnected = false;
let fallbackTimerId: ReturnType<typeof setTimeout> | null = null; let fallbackTimerId: ReturnType<typeof setTimeout> | null = null;
let lastProgress = 0; let lastProgress = 0;
let lastProgressAt = startedAt; let lastProgressAt = startedAt;
@@ -83,10 +82,9 @@ export function waitForTask(
}; };
cleanup = aiGenerationClient.subscribeTaskStatus(taskId, handleUpdate); cleanup = aiGenerationClient.subscribeTaskStatus(taskId, handleUpdate);
sseConnected = true;
fallbackTimerId = setTimeout(() => { fallbackTimerId = setTimeout(() => {
if (settled || !sseConnected) return; if (settled) return;
if (cleanup) cleanup(); if (cleanup) cleanup();
startPolling(); startPolling();
}, 5000); }, 5000);
+1
View File
@@ -73,6 +73,7 @@ export interface WebUserSession extends WebApiResultMeta {
enterpriseAdminUserId?: number | string | null; enterpriseAdminUserId?: number | string | null;
balanceCents?: number; balanceCents?: number;
enterpriseBalanceCents?: number; enterpriseBalanceCents?: number;
maxConcurrency?: number;
activePackages?: Array<{ activePackages?: Array<{
name: string; name: string;
expiresAt: string; expiresAt: string;