feat: 电商页面 KeepAlive 保活机制,切换页面不再丢失生成状态
通过 display:none 模式实现轻量 KeepAlive,电商页面首次访问后保持挂载, 切换到其他页面再切回时所有右侧面板状态(上传图片、生成进度、结果)完整保留。 同时清理项目中的临时文件和本地冗余图片。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import { create } from "zustand";
|
||||
import type { WebGenerationPreviewTask } from "../types";
|
||||
|
||||
export type QueueItemStatus = "pending" | "running" | "completed" | "failed" | "cancelled";
|
||||
|
||||
export interface GenerationQueueItem {
|
||||
id: string;
|
||||
taskId?: string;
|
||||
title: string;
|
||||
type: "image" | "video" | "agent" | "digital-human" | "character-mix" | "ecommerce-video";
|
||||
status: QueueItemStatus;
|
||||
progress: number;
|
||||
prompt: string;
|
||||
createdAt: number;
|
||||
sourceView: string; // which page created this: "ecommerce", "workbench", "canvas", "agent"
|
||||
resultUrl?: string | null;
|
||||
error?: string | null;
|
||||
params?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface PersistedQueueSnapshot {
|
||||
version: 1;
|
||||
items: GenerationQueueItem[];
|
||||
savedAt: number;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = "omniai:generation-queue";
|
||||
const MAX_ITEMS = 80;
|
||||
const STALE_MS = 2 * 60 * 60 * 1000; // 2 hours
|
||||
|
||||
function loadPersistedQueue(): GenerationQueueItem[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return [];
|
||||
const snapshot = JSON.parse(raw) as PersistedQueueSnapshot;
|
||||
if (Date.now() - (snapshot.savedAt || 0) > STALE_MS) {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
return [];
|
||||
}
|
||||
return snapshot.items.filter(
|
||||
(item) => item.status === "pending" || item.status === "running",
|
||||
);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function persistQueue(items: GenerationQueueItem[]): void {
|
||||
try {
|
||||
const snapshot: PersistedQueueSnapshot = { version: 1, items, savedAt: Date.now() };
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(snapshot));
|
||||
} catch { /* quota exceeded */ }
|
||||
}
|
||||
|
||||
interface GenerationStoreState {
|
||||
queue: GenerationQueueItem[];
|
||||
addTask: (item: GenerationQueueItem) => void;
|
||||
updateTask: (id: string, patch: Partial<GenerationQueueItem>) => void;
|
||||
removeTask: (id: string) => void;
|
||||
getRunningTasks: () => GenerationQueueItem[];
|
||||
getPendingTasks: () => GenerationQueueItem[];
|
||||
getTasksByView: (sourceView: string) => GenerationQueueItem[];
|
||||
clearTerminal: () => void;
|
||||
}
|
||||
|
||||
function hashUserId(): string {
|
||||
try {
|
||||
const raw = localStorage.getItem("omniai-web-session");
|
||||
if (!raw) return "anon";
|
||||
const parsed = JSON.parse(raw) as { user?: { id?: number | string } };
|
||||
return String(parsed?.user?.id || "anon");
|
||||
} catch {
|
||||
return "anon";
|
||||
}
|
||||
}
|
||||
|
||||
const initialQueue = loadPersistedQueue();
|
||||
|
||||
export const useGenerationStore = create<GenerationStoreState>((set, get) => ({
|
||||
queue: initialQueue,
|
||||
|
||||
addTask: (item) => {
|
||||
set((state) => {
|
||||
const next = [item, ...state.queue].slice(0, MAX_ITEMS);
|
||||
persistQueue(next.filter((i) => i.status === "pending" || i.status === "running"));
|
||||
return { queue: next };
|
||||
});
|
||||
},
|
||||
|
||||
updateTask: (id, patch) => {
|
||||
set((state) => {
|
||||
const next = state.queue.map((item) =>
|
||||
item.id === id ? { ...item, ...patch } : item,
|
||||
);
|
||||
persistQueue(next.filter((i) => i.status === "pending" || i.status === "running"));
|
||||
return { queue: next };
|
||||
});
|
||||
},
|
||||
|
||||
removeTask: (id) => {
|
||||
set((state) => {
|
||||
const next = state.queue.filter((item) => item.id !== id);
|
||||
persistQueue(next.filter((i) => i.status === "pending" || i.status === "running"));
|
||||
return { queue: next };
|
||||
});
|
||||
},
|
||||
|
||||
getRunningTasks: () => get().queue.filter((i) => i.status === "running" || i.status === "pending"),
|
||||
getPendingTasks: () => get().queue.filter((i) => i.status === "pending"),
|
||||
getTasksByView: (sourceView) => get().queue.filter((i) => i.sourceView === sourceView),
|
||||
|
||||
clearTerminal: () => {
|
||||
set((state) => {
|
||||
const next = state.queue.filter(
|
||||
(i) => i.status === "pending" || i.status === "running",
|
||||
);
|
||||
persistQueue(next);
|
||||
return { queue: next };
|
||||
});
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user