study / web /src /store /image-composer-draft.ts
xiaolongmr
添加编辑
638cad0
Raw History Blame Contribute Delete
5.14 kB
"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<ImageComposerDraftState> : {};
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<void> {
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<LoadedImageComposerDraft | null> {
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<PersistedDraftReference[]>(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<void> {
if (typeof window !== "undefined") {
try {
window.sessionStorage.removeItem(DRAFT_STATE_STORAGE_KEY);
} catch {
// Ignore unavailable browser storage.
}
}
await draftReferenceStorage.removeItem(draftReferencesKey());
}