diff --git a/components/scene-fusion-view.tsx b/components/scene-fusion-view.tsx index 6a9ab32be..361e08c7e 100644 --- a/components/scene-fusion-view.tsx +++ b/components/scene-fusion-view.tsx @@ -54,6 +54,7 @@ const BATCH_REQUEST_STAGGER_MS = 700; const BATCH_PARALLEL_WORKER_COUNT = 4; const MULTI_PRODUCT_FAILOVER_MAX_ROUNDS = 5; const MULTI_PRODUCT_FAILOVER_RETRY_DELAY_MS = 1500; +const PERSISTED_IMAGE_MAX_LENGTH = 200_000; const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -104,6 +105,72 @@ type SceneFusionPersistedState = { selectedResultIds: string[]; }; +function isPersistableImageValue(value: unknown): value is string { + if (typeof value !== "string") return false; + const normalized = value.trim(); + if (!normalized) return false; + if (normalized.startsWith("blob:")) return false; + if (normalized.startsWith("data:")) return false; + return normalized.length <= PERSISTED_IMAGE_MAX_LENGTH; +} + +function toPersistableImageValue(value: unknown) { + return isPersistableImageValue(value) ? value.trim() : ""; +} + +function toPersistableItems(items: ProcessItem[]) { + const persistableItems: ProcessItem[] = []; + for (const item of items) { + const image = toPersistableImageValue(item.image); + const result = toPersistableImageValue(item.result); + const originalUrl = toPersistableImageValue(item.originalUrl); + if (!image && !result && !originalUrl) continue; + + const persistableItem: ProcessItem = { + ...item, + image: image || result || originalUrl, + }; + if (result) { + persistableItem.result = result; + } else { + delete persistableItem.result; + } + if (originalUrl) { + persistableItem.originalUrl = originalUrl; + } else { + delete persistableItem.originalUrl; + } + persistableItems.push(persistableItem); + } + return persistableItems; +} + +function createPersistableSnapshot(snapshot: SceneFusionPersistedState): SceneFusionPersistedState { + const sceneItems = toPersistableItems(snapshot.sceneItems); + const multiProductItems = toPersistableItems(snapshot.multiProductItems); + const randomItems = toPersistableItems(snapshot.randomItems); + const batchItems = toPersistableItems(snapshot.batchItems); + const persistedIds = new Set( + [...sceneItems, ...multiProductItems, ...randomItems, ...batchItems].map( + (item) => item.id + ) + ); + + return { + ...snapshot, + productImage: toPersistableImageValue(snapshot.productImage) || null, + sceneItems, + multiProductItems, + referenceSceneImage: + toPersistableImageValue(snapshot.referenceSceneImage) || null, + randomItems, + batchItems, + selectedResultIds: snapshot.selectedResultIds.filter((id) => + persistedIds.has(id) + ), + }; +} + const randomBackgroundStyles = [ "现代简约客厅", "北欧卧室", @@ -539,7 +606,19 @@ export function SceneFusionView({ selectedResultIds, }; - window.sessionStorage.setItem(persistenceKey, JSON.stringify(snapshot)); + try { + window.sessionStorage.setItem( + persistenceKey, + JSON.stringify(createPersistableSnapshot(snapshot)) + ); + } catch (error) { + console.warn("[SceneFusionView] Failed to persist state:", error); + try { + window.sessionStorage.removeItem(persistenceKey); + } catch { + // Ignore storage cleanup failures. + } + } }, [ aspectRatio, batchItems,