From f2f9e4a239b6248fdc5856ffe4cfaf8e37bbe0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E6=B6=9B?= Date: Sun, 14 Sep 2025 07:14:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=8C=81=E4=B9=85=E5=8C=96=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=9B=20=E6=96=B0=E5=A2=9E=20=E5=A2=9E=E5=8A=A0=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E6=9D=A1=E6=95=B0?= =?UTF-8?q?=E4=B8=BA10=E6=9D=A1=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/HistoryPanel.tsx | 291 +++++++++++--------- src/hooks/useImageGeneration.ts | 5 +- src/store/useAppStore.ts | 459 +++++++++++++++++++++++++------- 3 files changed, 525 insertions(+), 230 deletions(-) diff --git a/src/components/HistoryPanel.tsx b/src/components/HistoryPanel.tsx index a7c6165..2a0b9e4 100644 --- a/src/components/HistoryPanel.tsx +++ b/src/components/HistoryPanel.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { useAppStore } from '../store/useAppStore'; import { Button } from './ui/Button'; -import { History, Download, Image as ImageIcon, Layers } from 'lucide-react'; +import { History, Download, Image as ImageIcon } from 'lucide-react'; import { cn } from '../utils/cn'; import { ImagePreviewModal } from './ImagePreviewModal'; @@ -19,6 +19,8 @@ export const HistoryPanel: React.FC = () => { selectedTool } = useAppStore(); + const { getBlob } = useAppStore.getState(); + const [previewModal, setPreviewModal] = React.useState<{ open: boolean; imageUrl: string; @@ -31,6 +33,9 @@ export const HistoryPanel: React.FC = () => { description: '' }); + // 存储从Blob URL解码的图像数据 + const [decodedImages, setDecodedImages] = useState>({}); + const generations = currentProject?.generations || []; const edits = currentProject?.edits || []; @@ -49,6 +54,57 @@ export const HistoryPanel: React.FC = () => { } }, [canvasImage]); + // 当项目变化时,解码Blob图像 + useEffect(() => { + const decodeBlobImages = async () => { + const newDecodedImages: Record = {}; + + // 解码生成记录的输出图像 + for (const gen of generations) { + if (Array.isArray(gen.outputAssetsBlobUrls)) { + for (const blobUrl of gen.outputAssetsBlobUrls) { + if (!decodedImages[blobUrl] && blobUrl.startsWith('blob:')) { + const blob = getBlob(blobUrl); + if (blob) { + const dataUrl = await new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.readAsDataURL(blob); + }); + newDecodedImages[blobUrl] = dataUrl; + } + } + } + } + } + + // 解码编辑记录的输出图像 + for (const edit of edits) { + if (Array.isArray(edit.outputAssetsBlobUrls)) { + for (const blobUrl of edit.outputAssetsBlobUrls) { + if (!decodedImages[blobUrl] && blobUrl.startsWith('blob:')) { + const blob = getBlob(blobUrl); + if (blob) { + const dataUrl = await new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.readAsDataURL(blob); + }); + newDecodedImages[blobUrl] = dataUrl; + } + } + } + } + } + + if (Object.keys(newDecodedImages).length > 0) { + setDecodedImages(prev => ({ ...prev, ...newDecodedImages })); + } + }; + + decodeBlobImages(); + }, [generations, edits, getBlob, decodedImages]); + if (!showHistory) { return (
@@ -88,85 +144,124 @@ export const HistoryPanel: React.FC = () => { {/* 变体网格 */}
-

当前变体

+
+

当前变体

+ + {generations.length + edits.length}/10 + +
{generations.length === 0 && edits.length === 0 ? (
🖼️

暂无生成记录

) : ( -
+
{/* 显示生成记录 */} - {generations.slice(-2).map((generation, index) => ( + {[...generations].sort((a, b) => b.timestamp - a.timestamp).slice(0, 10).map((generation, index) => (
{ selectGeneration(generation.id); - if (generation.outputAssets[0]) { - setCanvasImage(generation.outputAssets[0].url); + // 设置画布图像为第一个输出资产 + if (generation.outputAssetsBlobUrls && generation.outputAssetsBlobUrls.length > 0) { + const blobUrl = generation.outputAssetsBlobUrls[0]; + const decodedUrl = decodedImages[blobUrl]; + if (decodedUrl) { + setCanvasImage(decodedUrl); + } else if (!blobUrl.startsWith('blob:')) { + // 如果不是Blob URL,直接使用 + setCanvasImage(blobUrl); + } } }} > - {generation.outputAssets[0] ? ( - <> - 生成的变体 - + {generation.outputAssetsBlobUrls && generation.outputAssetsBlobUrls.length > 0 ? ( + (() => { + const blobUrl = generation.outputAssetsBlobUrls[0]; + const decodedUrl = decodedImages[blobUrl]; + if (decodedUrl) { + return 生成的变体; + } else if (!blobUrl.startsWith('blob:')) { + return 生成的变体; + } else { + return ( +
+
+
+ ); + } + })() ) : ( -
-
+
+
)} {/* 变体编号 */} -
- #{index + 1} +
+ G{index + 1}
))} {/* 显示编辑记录 */} - {edits.slice(-2).map((edit, index) => ( + {[...edits].sort((a, b) => b.timestamp - a.timestamp).slice(0, 10).map((edit, index) => (
{ - if (edit.outputAssets[0]) { - setCanvasImage(edit.outputAssets[0].url); - selectEdit(edit.id); - selectGeneration(null); + selectEdit(edit.id); + selectGeneration(null); + // 设置画布图像为第一个输出资产 + if (edit.outputAssetsBlobUrls && edit.outputAssetsBlobUrls.length > 0) { + const blobUrl = edit.outputAssetsBlobUrls[0]; + const decodedUrl = decodedImages[blobUrl]; + if (decodedUrl) { + setCanvasImage(decodedUrl); + } else if (!blobUrl.startsWith('blob:')) { + // 如果不是Blob URL,直接使用 + setCanvasImage(blobUrl); + } } }} > - {edit.outputAssets[0] ? ( - 编辑的变体 + {edit.outputAssetsBlobUrls && edit.outputAssetsBlobUrls.length > 0 ? ( + (() => { + const blobUrl = edit.outputAssetsBlobUrls[0]; + const decodedUrl = decodedImages[blobUrl]; + if (decodedUrl) { + return 编辑的变体; + } else if (!blobUrl.startsWith('blob:')) { + return 编辑的变体; + } else { + return ( +
+
+
+ ); + } + })() ) : ( -
-
+
+
)} {/* 编辑标签 */} -
- 编辑 #{index + 1} +
+ E{index + 1}
))} @@ -176,26 +271,26 @@ export const HistoryPanel: React.FC = () => { {/* 当前图像信息 */} {(canvasImage || imageDimensions) && ( -
-

当前图像

-
+
+

当前图像

+
{imageDimensions && (
尺寸: - {imageDimensions.width} × {imageDimensions.height} + {imageDimensions.width} × {imageDimensions.height}
)}
模式: - {selectedTool} + {selectedTool}
)} {/* 生成详情 */} -
-

生成详情

+
+

生成详情

{(() => { const gen = generations.find(g => g.id === selectedGenerationId); const selectedEdit = edits.find(e => e.id === selectedEditId); @@ -203,10 +298,10 @@ export const HistoryPanel: React.FC = () => { if (gen) { return (
-
+
- 提示: -

{gen.prompt}

+ 提示: +

{gen.prompt}

模型: @@ -218,37 +313,18 @@ export const HistoryPanel: React.FC = () => { {gen.parameters.seed}
)} +
+ 时间: + {new Date(gen.timestamp).toLocaleString()} +
- {/* 参考图像 */} + {/* 参考图像信息 */} {gen.sourceAssets.length > 0 && (
-
参考图像
-
- {gen.sourceAssets.map((asset, index) => ( - - ))} +
参考图像
+
+ {gen.sourceAssets.length} 个参考图像
)} @@ -258,10 +334,10 @@ export const HistoryPanel: React.FC = () => { const parentGen = generations.find(g => g.id === selectedEdit.parentGenerationId); return (
-
+
- 编辑指令: -

{selectedEdit.instruction}

+ 编辑指令: +

{selectedEdit.instruction}

类型: @@ -269,12 +345,12 @@ export const HistoryPanel: React.FC = () => {
创建时间: - {new Date(selectedEdit.timestamp).toLocaleTimeString()} + {new Date(selectedEdit.timestamp).toLocaleString()}
{selectedEdit.maskAssetId && (
遮罩: - 已应用 + 已应用
)}
@@ -282,53 +358,10 @@ export const HistoryPanel: React.FC = () => { {/* 原始生成参考 */} {parentGen && (
-
原始图像
- -
- )} - - {/* 遮罩可视化 */} - {selectedEdit.maskReferenceAsset && ( -
-
遮罩参考
- +
原始生成
+
+ 基于: G{generations.length - generations.indexOf(parentGen)} +
)}
@@ -336,7 +369,7 @@ export const HistoryPanel: React.FC = () => { } else { return (
-

选择一个生成记录或编辑记录以查看详细信息

+

选择一个生成记录或编辑记录以查看详细信息

); } @@ -354,8 +387,8 @@ export const HistoryPanel: React.FC = () => { let imageUrl: string | null = null; if (selectedGenerationId) { - const gen = generations.find(g => g.id === selectedGenerationId); - imageUrl = gen?.outputAssets[0]?.url || null; + const { canvasImage } = useAppStore.getState(); + imageUrl = canvasImage; } else { // 如果没有选择生成记录,尝试获取当前画布图像 const { canvasImage } = useAppStore.getState(); diff --git a/src/hooks/useImageGeneration.ts b/src/hooks/useImageGeneration.ts index d03724e..1ca7450 100644 --- a/src/hooks/useImageGeneration.ts +++ b/src/hooks/useImageGeneration.ts @@ -6,7 +6,7 @@ import { Generation, Edit, Asset } from '../types'; import { useToast } from '../components/ToastContext'; export const useImageGeneration = () => { - const { addGeneration, setIsGenerating, setCanvasImage, setCurrentProject, currentProject } = useAppStore(); + const { addGeneration, setIsGenerating, setCanvasImage } = useAppStore(); const { addToast } = useToast(); const generateMutation = useMutation({ @@ -81,7 +81,6 @@ export const useImageEditing = () => { editReferenceImages, brushStrokes, selectedGenerationId, - currentProject, seed, temperature, uploadedImages @@ -229,7 +228,7 @@ export const useImageEditing = () => { const edit: Edit = { id: generateId(), - parentGenerationId: selectedGenerationId || (currentProject?.generations[currentProject.generations.length - 1]?.id || ''), + parentGenerationId: selectedGenerationId || '', maskAssetId: brushStrokes.length > 0 ? generateId() : undefined, maskReferenceAsset, instruction, diff --git a/src/store/useAppStore.ts b/src/store/useAppStore.ts index b564797..0a94074 100644 --- a/src/store/useAppStore.ts +++ b/src/store/useAppStore.ts @@ -1,11 +1,49 @@ import { create } from 'zustand'; -import { devtools } from 'zustand/middleware'; +import { devtools, persist } from 'zustand/middleware'; import { Project, Generation, Edit, SegmentationMask, BrushStroke } from '../types'; import { generateId } from '../utils/imageUtils'; +// 定义不包含图像数据的轻量级项目结构 +interface LightweightProject { + id: string; + title: string; + generations: Array<{ + id: string; + prompt: string; + parameters: Generation['parameters']; + sourceAssets: Array<{ + id: string; + type: 'original'; + mime: string; + width: number; + height: number; + checksum: string; + // 存储Blob URL而不是base64数据 + blobUrl: string; + }>; + // 存储输出资产的Blob URL + outputAssetsBlobUrls: string[]; + modelVersion: string; + timestamp: number; + }>; + edits: Array<{ + id: string; + parentGenerationId: string; + maskAssetId?: string; + // 存储遮罩参考资产的Blob URL + maskReferenceAssetBlobUrl?: string; + instruction: string; + // 存储输出资产的Blob URL + outputAssetsBlobUrls: string[]; + timestamp: number; + }>; + createdAt: number; + updatedAt: number; +} + interface AppState { - // 当前项目 - currentProject: Project | null; + // 当前项目(轻量级版本,不包含实际图像数据) + currentProject: LightweightProject | null; // 画布状态 canvasImage: string | null; @@ -38,8 +76,11 @@ interface AppState { // UI状态 selectedTool: 'generate' | 'edit' | 'mask'; + // 存储Blob对象的Map + blobStore: Map; + // 操作 - setCurrentProject: (project: Project | null) => void; + setCurrentProject: (project: LightweightProject | null) => void; setCanvasImage: (url: string | null) => void; setCanvasZoom: (zoom: number) => void; setCanvasPan: (pan: { x: number; y: number }) => void; @@ -71,103 +112,325 @@ interface AppState { setShowPromptPanel: (show: boolean) => void; setSelectedTool: (tool: 'generate' | 'edit' | 'mask') => void; + + // Blob存储操作 + addBlob: (blob: Blob) => string; + getBlob: (url: string) => Blob | undefined; + cleanupOldHistory: () => void; } export const useAppStore = create()( devtools( - (set, get) => ({ - // 初始状态 - currentProject: null, - canvasImage: null, - canvasZoom: 1, - canvasPan: { x: 0, y: 0 }, - - uploadedImages: [], - editReferenceImages: [], - - brushStrokes: [], - brushSize: 20, - showMasks: true, - - isGenerating: false, - currentPrompt: '', - temperature: 0.7, - seed: null, - - selectedGenerationId: null, - selectedEditId: null, - showHistory: true, - - showPromptPanel: true, - - selectedTool: 'generate', - - // 操作 - setCurrentProject: (project) => set({ currentProject: project }), - setCanvasImage: (url) => set({ canvasImage: url }), - setCanvasZoom: (zoom) => set({ canvasZoom: zoom }), - setCanvasPan: (pan) => set({ canvasPan: pan }), - - addUploadedImage: (url) => set((state) => ({ - uploadedImages: [...state.uploadedImages, url] - })), - removeUploadedImage: (index) => set((state) => ({ - uploadedImages: state.uploadedImages.filter((_, i) => i !== index) - })), - clearUploadedImages: () => set({ uploadedImages: [] }), - - addEditReferenceImage: (url) => set((state) => ({ - editReferenceImages: [...state.editReferenceImages, url] - })), - removeEditReferenceImage: (index) => set((state) => ({ - editReferenceImages: state.editReferenceImages.filter((_, i) => i !== index) - })), - clearEditReferenceImages: () => set({ editReferenceImages: [] }), - - addBrushStroke: (stroke) => set((state) => ({ - brushStrokes: [...state.brushStrokes, stroke] - })), - clearBrushStrokes: () => set({ brushStrokes: [] }), - setBrushSize: (size) => set({ brushSize: size }), - setShowMasks: (show) => set({ showMasks: show }), - - setIsGenerating: (generating) => set({ isGenerating: generating }), - setCurrentPrompt: (prompt) => set({ currentPrompt: prompt }), - setTemperature: (temp) => set({ temperature: temp }), - setSeed: (seed) => set({ seed: seed }), - - addGeneration: (generation) => set((state) => ({ - currentProject: state.currentProject ? { - ...state.currentProject, - generations: [...state.currentProject.generations, generation], - updatedAt: Date.now() - } : { - // 如果没有项目,创建一个新项目包含此生成记录 - id: generateId(), - title: '未命名项目', - generations: [generation], - edits: [], - createdAt: Date.now(), - updatedAt: Date.now() - } - })), - - addEdit: (edit) => set((state) => ({ - currentProject: state.currentProject ? { - ...state.currentProject, - edits: [...state.currentProject.edits, edit], - updatedAt: Date.now() - } : null - })), - - selectGeneration: (id) => set({ selectedGenerationId: id }), - selectEdit: (id) => set({ selectedEditId: id }), - setShowHistory: (show) => set({ showHistory: show }), - - setShowPromptPanel: (show) => set({ showPromptPanel: show }), - - setSelectedTool: (tool) => set({ selectedTool: tool }), - }), - { name: 'nano-banana-store' } + persist( + (set, get) => ({ + // 初始状态 + currentProject: null, + canvasImage: null, + canvasZoom: 1, + canvasPan: { x: 0, y: 0 }, + + uploadedImages: [], + editReferenceImages: [], + + brushStrokes: [], + brushSize: 20, + showMasks: true, + + isGenerating: false, + currentPrompt: '', + temperature: 0.7, + seed: null, + + selectedGenerationId: null, + selectedEditId: null, + showHistory: true, + + showPromptPanel: true, + + selectedTool: 'generate', + + // Blob存储(不在持久化中保存) + blobStore: new Map(), + + // 操作 + setCurrentProject: (project) => set({ currentProject: project }), + setCanvasImage: (url) => set({ canvasImage: url }), + setCanvasZoom: (zoom) => set({ canvasZoom: zoom }), + setCanvasPan: (pan) => set({ canvasPan: pan }), + + addUploadedImage: (url) => set((state) => ({ + uploadedImages: [...state.uploadedImages, url] + })), + removeUploadedImage: (index) => set((state) => ({ + uploadedImages: state.uploadedImages.filter((_, i) => i !== index) + })), + clearUploadedImages: () => set({ uploadedImages: [] }), + + addEditReferenceImage: (url) => set((state) => ({ + editReferenceImages: [...state.editReferenceImages, url] + })), + removeEditReferenceImage: (index) => set((state) => ({ + editReferenceImages: state.editReferenceImages.filter((_, i) => i !== index) + })), + clearEditReferenceImages: () => set({ editReferenceImages: [] }), + + addBrushStroke: (stroke) => set((state) => ({ + brushStrokes: [...state.brushStrokes, stroke] + })), + clearBrushStrokes: () => set({ brushStrokes: [] }), + setBrushSize: (size) => set({ brushSize: size }), + setShowMasks: (show) => set({ showMasks: show }), + + setIsGenerating: (generating) => set({ isGenerating: generating }), + setCurrentPrompt: (prompt) => set({ currentPrompt: prompt }), + setTemperature: (temp) => set({ temperature: temp }), + setSeed: (seed) => set({ seed: seed }), + + // 添加Blob到存储并返回URL + addBlob: (blob: Blob) => { + const url = URL.createObjectURL(blob); + set((state) => { + const newBlobStore = new Map(state.blobStore); + newBlobStore.set(url, blob); + return { blobStore: newBlobStore }; + }); + return url; + }, + + // 从存储中获取Blob + getBlob: (url: string) => { + return get().blobStore.get(url); + }, + + addGeneration: (generation) => set((state) => { + // 将base64图像数据转换为Blob并存储 + const sourceAssets = generation.sourceAssets.map(asset => { + if (asset.url.startsWith('data:')) { + // 从base64创建Blob + const base64 = asset.url.split(',')[1]; + const byteString = atob(base64); + const mimeString = asset.url.split(',')[0].split(':')[1].split(';')[0]; + const ab = new ArrayBuffer(byteString.length); + const ia = new Uint8Array(ab); + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + const blob = new Blob([ab], { type: mimeString }); + const blobUrl = URL.createObjectURL(blob); + + // 存储Blob对象 + set((innerState) => { + const newBlobStore = new Map(innerState.blobStore); + newBlobStore.set(blobUrl, blob); + return { blobStore: newBlobStore }; + }); + + return { + id: asset.id, + type: asset.type, + mime: asset.mime, + width: asset.width, + height: asset.height, + checksum: asset.checksum, + blobUrl + }; + } + return { + id: asset.id, + type: asset.type, + mime: asset.mime, + width: asset.width, + height: asset.height, + checksum: asset.checksum, + blobUrl: asset.url + }; + }); + + // 将输出资产转换为Blob URL + const outputAssetsBlobUrls = generation.outputAssets.map(asset => { + if (asset.url.startsWith('data:')) { + // 从base64创建Blob + const base64 = asset.url.split(',')[1]; + const byteString = atob(base64); + const mimeString = asset.url.split(',')[0].split(':')[1].split(';')[0]; + const ab = new ArrayBuffer(byteString.length); + const ia = new Uint8Array(ab); + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + const blob = new Blob([ab], { type: mimeString }); + const blobUrl = URL.createObjectURL(blob); + + // 存储Blob对象 + set((innerState) => { + const newBlobStore = new Map(innerState.blobStore); + newBlobStore.set(blobUrl, blob); + return { blobStore: newBlobStore }; + }); + + return blobUrl; + } + return asset.url; + }); + + // 创建轻量级生成记录 + const lightweightGeneration = { + id: generation.id, + prompt: generation.prompt, + parameters: generation.parameters, + sourceAssets, + outputAssetsBlobUrls, + modelVersion: generation.modelVersion, + timestamp: generation.timestamp + }; + + const updatedProject = state.currentProject ? { + ...state.currentProject, + generations: [...state.currentProject.generations, lightweightGeneration], + updatedAt: Date.now() + } : { + // 如果没有项目,创建一个新项目包含此生成记录 + id: generateId(), + title: '未命名项目', + generations: [lightweightGeneration], + edits: [], + createdAt: Date.now(), + updatedAt: Date.now() + }; + + // 清理旧记录以保持在限制内 + if (updatedProject.generations.length > 10) { + updatedProject.generations.splice(0, updatedProject.generations.length - 10); + } + + return { + currentProject: updatedProject + }; + }), + + addEdit: (edit) => set((state) => { + // 将遮罩参考资产转换为Blob URL(如果存在) + let maskReferenceAssetBlobUrl: string | undefined; + if (edit.maskReferenceAsset && edit.maskReferenceAsset.url.startsWith('data:')) { + const base64 = edit.maskReferenceAsset.url.split(',')[1]; + const byteString = atob(base64); + const mimeString = edit.maskReferenceAsset.url.split(',')[0].split(':')[1].split(';')[0]; + const ab = new ArrayBuffer(byteString.length); + const ia = new Uint8Array(ab); + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + const blob = new Blob([ab], { type: mimeString }); + maskReferenceAssetBlobUrl = URL.createObjectURL(blob); + + // 存储Blob对象 + set((innerState) => { + const newBlobStore = new Map(innerState.blobStore); + newBlobStore.set(maskReferenceAssetBlobUrl!, blob); + return { blobStore: newBlobStore }; + }); + } else if (edit.maskReferenceAsset) { + maskReferenceAssetBlobUrl = edit.maskReferenceAsset.url; + } + + // 将输出资产转换为Blob URL + const outputAssetsBlobUrls = edit.outputAssets.map(asset => { + if (asset.url.startsWith('data:')) { + // 从base64创建Blob + const base64 = asset.url.split(',')[1]; + const byteString = atob(base64); + const mimeString = asset.url.split(',')[0].split(':')[1].split(';')[0]; + const ab = new ArrayBuffer(byteString.length); + const ia = new Uint8Array(ab); + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + const blob = new Blob([ab], { type: mimeString }); + const blobUrl = URL.createObjectURL(blob); + + // 存储Blob对象 + set((innerState) => { + const newBlobStore = new Map(innerState.blobStore); + newBlobStore.set(blobUrl, blob); + return { blobStore: newBlobStore }; + }); + + return blobUrl; + } + return asset.url; + }); + + // 创建轻量级编辑记录 + const lightweightEdit = { + id: edit.id, + parentGenerationId: edit.parentGenerationId, + maskAssetId: edit.maskAssetId, + maskReferenceAssetBlobUrl, + instruction: edit.instruction, + outputAssetsBlobUrls, + timestamp: edit.timestamp + }; + + if (!state.currentProject) return {}; + + const updatedProject = { + ...state.currentProject, + edits: [...state.currentProject.edits, lightweightEdit], + updatedAt: Date.now() + }; + + // 清理旧记录以保持在限制内 + if (updatedProject.edits.length > 10) { + updatedProject.edits.splice(0, updatedProject.edits.length - 10); + } + + return { + currentProject: updatedProject + }; + }), + + selectGeneration: (id) => set({ selectedGenerationId: id }), + selectEdit: (id) => set({ selectedEditId: id }), + setShowHistory: (show) => set({ showHistory: show }), + + setShowPromptPanel: (show) => set({ showPromptPanel: show }), + + setSelectedTool: (tool) => set({ selectedTool: tool }), + + // 清理旧的历史记录,保留最多10条 + cleanupOldHistory: () => set((state) => { + if (!state.currentProject) return {}; + + const generations = [...state.currentProject.generations]; + const edits = [...state.currentProject.edits]; + + // 如果生成记录超过10条,只保留最新的10条 + if (generations.length > 10) { + generations.splice(0, generations.length - 10); + } + + // 如果编辑记录超过10条,只保留最新的10条 + if (edits.length > 10) { + edits.splice(0, edits.length - 10); + } + + return { + currentProject: { + ...state.currentProject, + generations, + edits, + updatedAt: Date.now() + } + }; + }) + }), + { + name: 'nano-banana-store', + partialize: (state) => ({ + currentProject: state.currentProject, + // 我们只持久化轻量级项目数据,不包含Blob对象 + }) + } + ) ) ); \ No newline at end of file