阶段性提交

This commit is contained in:
2025-09-21 14:43:59 +08:00
parent af2058f752
commit 690a530031
20 changed files with 1577 additions and 781 deletions

View File

@@ -1,8 +1,9 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { Project, Generation, Edit, SegmentationMask, BrushStroke, UploadResult } from '../types';
import { Generation, Edit, BrushStroke, UploadResult } from '../types';
import { generateId } from '../utils/imageUtils';
import * as indexedDBService from '../services/indexedDBService';
import * as referenceImageService from '../services/referenceImageService';
// 定义不包含图像数据的轻量级项目结构
interface LightweightProject {
@@ -135,7 +136,7 @@ interface AppState {
}
// 限制历史记录数量
const MAX_HISTORY_ITEMS = 50;
const MAX_HISTORY_ITEMS = 1000;
export const useAppStore = create<AppState>()(
devtools(
@@ -179,18 +180,64 @@ export const useAppStore = create<AppState>()(
addUploadedImage: (url) => set((state) => ({
uploadedImages: [...state.uploadedImages, url]
})),
removeUploadedImage: (index) => set((state) => ({
uploadedImages: state.uploadedImages.filter((_, i) => i !== index)
})),
clearUploadedImages: () => set({ uploadedImages: [] }),
removeUploadedImage: (index) => set((state) => {
// 如果删除的是IndexedDB中的参考图像同时从IndexedDB中删除
const imageUrl = state.uploadedImages[index];
if (imageUrl && imageUrl.startsWith('indexeddb://')) {
const imageId = imageUrl.replace('indexeddb://', '');
referenceImageService.deleteReferenceImage(imageId).catch(err => {
console.error('删除参考图像失败:', err);
});
}
return {
uploadedImages: state.uploadedImages.filter((_, i) => i !== index)
};
}),
clearUploadedImages: () => set((state) => {
// 删除所有存储在IndexedDB中的参考图像
state.uploadedImages.forEach(imageUrl => {
if (imageUrl.startsWith('indexeddb://')) {
const imageId = imageUrl.replace('indexeddb://', '');
referenceImageService.deleteReferenceImage(imageId).catch(err => {
console.error('删除参考图像失败:', err);
});
}
});
return { uploadedImages: [] };
}),
addEditReferenceImage: (url) => set((state) => ({
editReferenceImages: [...state.editReferenceImages, url]
})),
removeEditReferenceImage: (index) => set((state) => ({
editReferenceImages: state.editReferenceImages.filter((_, i) => i !== index)
})),
clearEditReferenceImages: () => set({ editReferenceImages: [] }),
removeEditReferenceImage: (index) => set((state) => {
// 如果删除的是IndexedDB中的参考图像同时从IndexedDB中删除
const imageUrl = state.editReferenceImages[index];
if (imageUrl && imageUrl.startsWith('indexeddb://')) {
const imageId = imageUrl.replace('indexeddb://', '');
referenceImageService.deleteReferenceImage(imageId).catch(err => {
console.error('删除参考图像失败:', err);
});
}
return {
editReferenceImages: state.editReferenceImages.filter((_, i) => i !== index)
};
}),
clearEditReferenceImages: () => set((state) => {
// 删除所有存储在IndexedDB中的参考图像
state.editReferenceImages.forEach(imageUrl => {
if (imageUrl.startsWith('indexeddb://')) {
const imageId = imageUrl.replace('indexeddb://', '');
referenceImageService.deleteReferenceImage(imageId).catch(err => {
console.error('删除参考图像失败:', err);
});
}
});
return { editReferenceImages: [] };
}),
addBrushStroke: (stroke) => set((state) => ({
brushStrokes: [...state.brushStrokes, stroke]
@@ -342,43 +389,18 @@ export const useAppStore = create<AppState>()(
};
// 清理旧记录以保持在限制内
if (updatedProject.generations.length > MAX_HISTORY_ITEMS) {
// 收集需要释放的Blob URLs
const urlsToRevoke: string[] = [];
const generationsToRemove = updatedProject.generations.slice(0, updatedProject.generations.length - MAX_HISTORY_ITEMS);
generationsToRemove.forEach(gen => {
gen.sourceAssets.forEach(asset => {
if (asset.blobUrl.startsWith('blob:')) {
urlsToRevoke.push(asset.blobUrl);
}
});
gen.outputAssetsBlobUrls.forEach(url => {
if (url.startsWith('blob:')) {
urlsToRevoke.push(url);
}
});
});
// 释放Blob URLs
if (urlsToRevoke.length > 0) {
set((innerState) => {
urlsToRevoke.forEach(url => {
URL.revokeObjectURL(url);
const newBlobStore = new Map(innerState.blobStore);
newBlobStore.delete(url);
innerState = { ...innerState, blobStore: newBlobStore };
});
return innerState;
});
}
// 清理数组
updatedProject.generations.splice(0, updatedProject.generations.length - MAX_HISTORY_ITEMS);
// 同时清理IndexedDB中的旧记录
indexedDBService.cleanupOldRecords(MAX_HISTORY_ITEMS).catch(err => {
console.error('清理IndexedDB旧记录失败:', err);
});
}
if (updatedProject.generations.length > MAX_HISTORY_ITEMS) {
// 不再清理Blob URLs以确保参考图像不会被意外删除
// 只记录信息
console.log('历史记录已达到限制但Blob清理已禁用参考图像将被永久保留');
// 清理数组
updatedProject.generations.splice(0, updatedProject.generations.length - MAX_HISTORY_ITEMS);
// 同时清理IndexedDB中的旧记录
indexedDBService.cleanupOldRecords(MAX_HISTORY_ITEMS).catch(err => {
console.error('清理IndexedDB旧记录失败:', err);
});
}
return {
currentProject: updatedProject
@@ -472,32 +494,9 @@ export const useAppStore = create<AppState>()(
// 清理旧记录以保持在限制内
if (updatedProject.edits.length > MAX_HISTORY_ITEMS) {
// 收集需要释放的Blob URLs
const urlsToRevoke: string[] = [];
const editsToRemove = updatedProject.edits.slice(0, updatedProject.edits.length - MAX_HISTORY_ITEMS);
editsToRemove.forEach(edit => {
if (edit.maskReferenceAssetBlobUrl && edit.maskReferenceAssetBlobUrl.startsWith('blob:')) {
urlsToRevoke.push(edit.maskReferenceAssetBlobUrl);
}
edit.outputAssetsBlobUrls.forEach(url => {
if (url.startsWith('blob:')) {
urlsToRevoke.push(url);
}
});
});
// 释放Blob URLs
if (urlsToRevoke.length > 0) {
set((innerState) => {
urlsToRevoke.forEach(url => {
URL.revokeObjectURL(url);
const newBlobStore = new Map(innerState.blobStore);
newBlobStore.delete(url);
innerState = { ...innerState, blobStore: newBlobStore };
});
return innerState;
});
}
// 不再清理Blob URLs,以确保参考图像不会被意外删除
// 只记录信息
console.log('编辑记录已达到限制但Blob清理已禁用参考图像将被永久保留');
// 清理数组
updatedProject.edits.splice(0, updatedProject.edits.length - MAX_HISTORY_ITEMS);
@@ -528,56 +527,20 @@ export const useAppStore = create<AppState>()(
const generations = [...state.currentProject.generations];
const edits = [...state.currentProject.edits];
// 收集需要释放的Blob URLs
const urlsToRevoke: string[] = [];
// 不再清理Blob URLs,以确保参考图像不会被意外删除
// 只记录信息
console.log('历史记录清理已禁用,参考图像将被永久保留');
// 如果生成记录超过限制,只保留最新的记录
if (generations.length > MAX_HISTORY_ITEMS) {
const generationsToRemove = generations.slice(0, generations.length - MAX_HISTORY_ITEMS);
generationsToRemove.forEach(gen => {
gen.sourceAssets.forEach(asset => {
if (asset.blobUrl.startsWith('blob:')) {
urlsToRevoke.push(asset.blobUrl);
}
});
gen.outputAssetsBlobUrls.forEach(url => {
if (url.startsWith('blob:')) {
urlsToRevoke.push(url);
}
});
});
generations.splice(0, generations.length - MAX_HISTORY_ITEMS);
}
// 如果编辑记录超过限制,只保留最新的记录
if (edits.length > MAX_HISTORY_ITEMS) {
const editsToRemove = edits.slice(0, edits.length - MAX_HISTORY_ITEMS);
editsToRemove.forEach(edit => {
if (edit.maskReferenceAssetBlobUrl && edit.maskReferenceAssetBlobUrl.startsWith('blob:')) {
urlsToRevoke.push(edit.maskReferenceAssetBlobUrl);
}
edit.outputAssetsBlobUrls.forEach(url => {
if (url.startsWith('blob:')) {
urlsToRevoke.push(url);
}
});
});
edits.splice(0, edits.length - MAX_HISTORY_ITEMS);
}
// 释放Blob URLs
if (urlsToRevoke.length > 0) {
set((innerState) => {
urlsToRevoke.forEach(url => {
URL.revokeObjectURL(url);
const newBlobStore = new Map(innerState.blobStore);
newBlobStore.delete(url);
innerState = { ...innerState, blobStore: newBlobStore };
});
return innerState;
});
}
// 同时清理IndexedDB中的旧记录
indexedDBService.cleanupOldRecords(MAX_HISTORY_ITEMS).catch(err => {
console.error('清理IndexedDB旧记录失败:', err);
@@ -594,110 +557,35 @@ export const useAppStore = create<AppState>()(
}),
// 释放指定的Blob URLs
revokeBlobUrls: (urls: string[]) => set((state) => {
urls.forEach(url => {
if (state.blobStore.has(url)) {
URL.revokeObjectURL(url);
const newBlobStore = new Map(state.blobStore);
newBlobStore.delete(url);
state = { ...state, blobStore: newBlobStore };
}
});
revokeBlobUrls: () => set((state) => {
// 不再自动清理Blob URL以确保参考图像不会被意外删除
// 只有在用户明确请求清除会话时才清理
console.log('Blob清理已禁用参考图像将被永久保留');
return state;
}),
// 释放所有Blob URLs
cleanupAllBlobUrls: () => set((state) => {
state.blobStore.forEach((_, url) => {
URL.revokeObjectURL(url);
});
return { ...state, blobStore: new Map() };
// 不再自动清理Blob URL以确保参考图像不会被意外删除
// 只有在用户明确请求清除会话时才清理
console.log('Blob清理已禁用参考图像将被永久保留');
return state;
}),
// 定期清理Blob URL
scheduleBlobCleanup: () => {
// 清理超过10分钟未使用的Blob
const state = get();
const now = Date.now();
state.blobStore.forEach((blob, url) => {
// 检查URL是否仍在使用中
const isUsedInProject = state.currentProject && (
state.currentProject.generations.some(gen =>
gen.sourceAssets.some(asset => asset.blobUrl === url) ||
gen.outputAssetsBlobUrls.some(outputUrl => outputUrl === url)
) ||
state.currentProject.edits.some(edit =>
(edit.maskReferenceAssetBlobUrl === url) ||
edit.outputAssetsBlobUrls.some(outputUrl => outputUrl === url)
)
);
const isUsedInCanvas = state.canvasImage === url;
const isUsedInUploads = state.uploadedImages.includes(url);
const isUsedInEdits = state.editReferenceImages.includes(url);
// 检查是否是历史记录中的参考图像
const isUsedAsReference = state.currentProject && (
state.currentProject.generations.some(gen =>
gen.sourceAssets.some(asset => asset.blobUrl === url)
) ||
state.currentProject.edits.some(edit =>
(edit.maskReferenceAssetBlobUrl === url)
)
);
// 检查是否是当前编辑操作中的参考图像
const isUsedInCurrentEdit = state.editReferenceImages.includes(url);
// 检查是否是当前生成操作中的参考图像
const isUsedInCurrentGeneration = state.uploadedImages.includes(url);
// 如果Blob没有被使用则清理它
// 但保留仍在作为参考图像使用的Blob
if (!isUsedInProject && !isUsedInCanvas && !isUsedInUploads && !isUsedInEdits && !isUsedAsReference && !isUsedInCurrentEdit && !isUsedInCurrentGeneration) {
URL.revokeObjectURL(url);
const newBlobStore = new Map(state.blobStore);
newBlobStore.delete(url);
set({ blobStore: newBlobStore });
}
});
// 不再自动清理Blob URL以确保参考图像不会被意外删除
// 只有在用户明确请求清除会话时才清理
console.log('Blob清理已禁用参考图像将被永久保留');
},
// 删除生成记录
removeGeneration: (id) => set((state) => {
if (!state.currentProject) return {};
// 收集需要释放的Blob URLs
const urlsToRevoke: string[] = [];
const generationToRemove = state.currentProject.generations.find(gen => gen.id === id);
if (generationToRemove) {
// 收集要删除的生成记录中的Blob URLs
generationToRemove.sourceAssets.forEach(asset => {
if (asset.blobUrl.startsWith('blob:')) {
urlsToRevoke.push(asset.blobUrl);
}
});
generationToRemove.outputAssetsBlobUrls.forEach(url => {
if (url.startsWith('blob:')) {
urlsToRevoke.push(url);
}
});
// 释放Blob URLs
if (urlsToRevoke.length > 0) {
set((innerState) => {
urlsToRevoke.forEach(url => {
URL.revokeObjectURL(url);
const newBlobStore = new Map(innerState.blobStore);
newBlobStore.delete(url);
innerState = { ...innerState, blobStore: newBlobStore };
});
return innerState;
});
}
}
// 不再清理Blob URLs,以确保参考图像不会被意外删除
// 只记录信息
console.log('生成记录删除操作已执行但Blob清理已禁用参考图像将被永久保留');
// 从项目中移除生成记录
const updatedProject = {
@@ -715,34 +603,9 @@ export const useAppStore = create<AppState>()(
removeEdit: (id) => set((state) => {
if (!state.currentProject) return {};
// 收集需要释放的Blob URLs
const urlsToRevoke: string[] = [];
const editToRemove = state.currentProject.edits.find(edit => edit.id === id);
if (editToRemove) {
// 收集要删除的编辑记录中的Blob URLs
if (editToRemove.maskReferenceAssetBlobUrl && editToRemove.maskReferenceAssetBlobUrl.startsWith('blob:')) {
urlsToRevoke.push(editToRemove.maskReferenceAssetBlobUrl);
}
editToRemove.outputAssetsBlobUrls.forEach(url => {
if (url.startsWith('blob:')) {
urlsToRevoke.push(url);
}
});
// 释放Blob URLs
if (urlsToRevoke.length > 0) {
set((innerState) => {
urlsToRevoke.forEach(url => {
URL.revokeObjectURL(url);
const newBlobStore = new Map(innerState.blobStore);
newBlobStore.delete(url);
innerState = { ...innerState, blobStore: newBlobStore };
});
return innerState;
});
}
}
// 不再清理Blob URLs,以确保参考图像不会被意外删除
// 只记录信息
console.log('编辑记录删除操作已执行但Blob清理已禁用参考图像将被永久保留');
// 从项目中移除编辑记录
const updatedProject = {