新增 历史记录搜索、筛选功能;

新增 历史记录悬浮大图功能;
优化 现在历史记录最多可以存储1000条;
优化 历史记录的存储形式改为了使用IndexedDB;
This commit is contained in:
2025-09-15 22:19:24 +08:00
parent bda049fcd1
commit 7a5e5d77b0
4 changed files with 733 additions and 238 deletions

View File

@@ -2,6 +2,7 @@ import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { Project, Generation, Edit, SegmentationMask, BrushStroke, UploadResult } from '../types';
import { generateId } from '../utils/imageUtils';
import * as indexedDBService from '../services/indexedDBService';
// 定义不包含图像数据的轻量级项目结构
interface LightweightProject {
@@ -204,29 +205,45 @@ export const useAppStore = create<AppState>()(
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);
addGeneration: (generation) => {
// 保存到IndexedDB
indexedDBService.addGeneration(generation).catch(err => {
console.error('保存生成记录到IndexedDB失败:', err);
});
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
};
}
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,
@@ -234,164 +251,170 @@ export const useAppStore = create<AppState>()(
width: asset.width,
height: asset.height,
checksum: asset.checksum,
blobUrl
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,
uploadResults: generation.uploadResults
};
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()
};
// 清理旧记录以保持在限制内现在限制为1000条
if (updatedProject.generations.length > 1000) {
updatedProject.generations.splice(0, updatedProject.generations.length - 1000);
// 同时清理IndexedDB中的旧记录
indexedDBService.cleanupOldRecords(1000).catch(err => {
console.error('清理IndexedDB旧记录失败:', err);
});
}
return {
id: asset.id,
type: asset.type,
mime: asset.mime,
width: asset.width,
height: asset.height,
checksum: asset.checksum,
blobUrl: asset.url
currentProject: updatedProject
};
});
// 将输出资产转换为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,
uploadResults: generation.uploadResults
};
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;
}
addEdit: (edit) => {
// 保存到IndexedDB
indexedDBService.addEdit(edit).catch(err => {
console.error('保存编辑记录到IndexedDB失败:', err);
});
// 将输出资产转换为Blob URL
const outputAssetsBlobUrls = edit.outputAssets.map(asset => {
if (asset.url.startsWith('data:')) {
// 从base64创建Blob
const base64 = asset.url.split(',')[1];
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 = asset.url.split(',')[0].split(':')[1].split(';')[0];
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 });
const blobUrl = URL.createObjectURL(blob);
maskReferenceAssetBlobUrl = URL.createObjectURL(blob);
// 存储Blob对象
set((innerState) => {
const newBlobStore = new Map(innerState.blobStore);
newBlobStore.set(blobUrl, blob);
newBlobStore.set(maskReferenceAssetBlobUrl!, blob);
return { blobStore: newBlobStore };
});
return blobUrl;
} else if (edit.maskReferenceAsset) {
maskReferenceAssetBlobUrl = edit.maskReferenceAsset.url;
}
return asset.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,
uploadResults: edit.uploadResults
};
if (!state.currentProject) return {};
const updatedProject = {
...state.currentProject,
edits: [...state.currentProject.edits, lightweightEdit],
updatedAt: Date.now()
};
// 清理旧记录以保持在限制内现在限制为1000条
if (updatedProject.edits.length > 1000) {
updatedProject.edits.splice(0, updatedProject.edits.length - 1000);
// 同时清理IndexedDB中的旧记录
indexedDBService.cleanupOldRecords(1000).catch(err => {
console.error('清理IndexedDB旧记录失败:', err);
});
}
return {
currentProject: updatedProject
};
});
// 创建轻量级编辑记录
const lightweightEdit = {
id: edit.id,
parentGenerationId: edit.parentGenerationId,
maskAssetId: edit.maskAssetId,
maskReferenceAssetBlobUrl,
instruction: edit.instruction,
outputAssetsBlobUrls,
timestamp: edit.timestamp,
uploadResults: edit.uploadResults
};
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 }),
@@ -401,23 +424,28 @@ export const useAppStore = create<AppState>()(
setSelectedTool: (tool) => set({ selectedTool: tool }),
// 清理旧的历史记录保留最多10条
// 清理旧的历史记录保留最多1000
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);
// 如果生成记录超过1000只保留最新的1000
if (generations.length > 1000) {
generations.splice(0, generations.length - 1000);
}
// 如果编辑记录超过10条只保留最新的10条
if (edits.length > 10) {
edits.splice(0, edits.length - 10);
// 如果编辑记录超过1000只保留最新的1000
if (edits.length > 1000) {
edits.splice(0, edits.length - 1000);
}
// 同时清理IndexedDB中的旧记录
indexedDBService.cleanupOldRecords(1000).catch(err => {
console.error('清理IndexedDB旧记录失败:', err);
});
return {
currentProject: {
...state.currentProject,