"use client"; import localforage from "localforage"; import type { StoredReferenceImage } from "@/store/image-conversations"; export type ImageComposerDraftState = { prompt: string; count: string; size: string; model: string; quality: string; resolution: string; thirdPartyImageGroupId: string; memoryEnabled: boolean; accountPoolStrategy: string; }; export type LoadedImageComposerDraft = ImageComposerDraftState & { referenceImages: StoredReferenceImage[]; referenceImageFiles: File[]; }; type PersistedDraftReference = { name: string; type: string; source?: "upload" | "result"; blob: Blob; }; const DRAFT_STATE_STORAGE_KEY = "chatgpt2api:image_composer_draft_v2"; const DRAFT_TAB_ID_STORAGE_KEY = "chatgpt2api:image_composer_draft_tab_id"; const draftReferenceStorage = localforage.createInstance({ name: "chatgpt2api", storeName: "image_composer_draft", }); const EMPTY_DRAFT_STATE: ImageComposerDraftState = { prompt: "", count: "1", size: "", model: "gpt-image-2", quality: "high", resolution: "", thirdPartyImageGroupId: "", memoryEnabled: false, accountPoolStrategy: "own_first", }; function getDraftTabId(): string { if (typeof window === "undefined") return "server"; const existing = window.sessionStorage.getItem(DRAFT_TAB_ID_STORAGE_KEY); if (existing) return existing; const next = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`; window.sessionStorage.setItem(DRAFT_TAB_ID_STORAGE_KEY, next); return next; } function draftReferencesKey(): string { return `references:v2:${getDraftTabId()}`; } function normalizeDraftState(value: unknown): ImageComposerDraftState { const source = value && typeof value === "object" ? value as Partial : {}; return { prompt: String(source.prompt || ""), count: String(source.count || "1"), size: String(source.size || ""), model: String(source.model || "gpt-image-2"), quality: String(source.quality || "high"), resolution: String(source.resolution || ""), thirdPartyImageGroupId: String(source.thirdPartyImageGroupId || ""), memoryEnabled: source.memoryEnabled === true, accountPoolStrategy: String(source.accountPoolStrategy || "own_first"), }; } export function saveImageComposerDraftState(state: ImageComposerDraftState): void { if (typeof window === "undefined") return; try { window.sessionStorage.setItem(DRAFT_STATE_STORAGE_KEY, JSON.stringify(normalizeDraftState(state))); } catch { // Draft persistence is best-effort when browser storage is unavailable. } } export async function saveImageComposerDraftReferences( images: StoredReferenceImage[], files: File[], ): Promise { const references = images .map((image, index): PersistedDraftReference | null => { const file = files[index]; if (!file) return null; return { name: image.name || file.name || `reference-${index + 1}.png`, type: image.type || file.type || "image/png", source: image.source === "result" ? "result" : "upload", blob: file, }; }) .filter((item): item is PersistedDraftReference => Boolean(item)); if (references.length === 0) { await draftReferenceStorage.removeItem(draftReferencesKey()); return; } await draftReferenceStorage.setItem(draftReferencesKey(), references); } export async function loadImageComposerDraft(): Promise { let state = EMPTY_DRAFT_STATE; let hasStoredState = false; if (typeof window !== "undefined") { try { const raw = window.sessionStorage.getItem(DRAFT_STATE_STORAGE_KEY); hasStoredState = Boolean(raw); state = normalizeDraftState(JSON.parse(raw || "{}")); } catch { state = EMPTY_DRAFT_STATE; } } let persistedReferences: PersistedDraftReference[] = []; try { persistedReferences = (await draftReferenceStorage.getItem(draftReferencesKey())) || []; } catch { persistedReferences = []; } if (!hasStoredState && persistedReferences.length === 0) { return null; } const referenceImageFiles = persistedReferences.map( (item) => new File([item.blob], item.name || "reference.png", { type: item.type || item.blob.type || "image/png" }), ); const referenceImages = persistedReferences.map((item, index): StoredReferenceImage => ({ name: item.name || referenceImageFiles[index]?.name || "reference.png", type: item.type || referenceImageFiles[index]?.type || "image/png", dataUrl: URL.createObjectURL(item.blob), source: item.source === "result" ? "result" : "upload", })); return { ...state, referenceImages, referenceImageFiles }; } export async function clearImageComposerDraft(): Promise { if (typeof window !== "undefined") { try { window.sessionStorage.removeItem(DRAFT_STATE_STORAGE_KEY); } catch { // Ignore unavailable browser storage. } } await draftReferenceStorage.removeItem(draftReferencesKey()); }