修复内存溢出问题

This commit is contained in:
2025-09-19 01:25:30 +08:00
parent 803cc100be
commit 9674740c0d
13 changed files with 1085 additions and 337 deletions

View File

@@ -6,6 +6,10 @@ const DB_VERSION = 1;
const GENERATIONS_STORE = 'generations';
const EDITS_STORE = 'edits';
// 重试配置
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;
// IndexedDB实例
let db: IDBDatabase | null = null;
@@ -59,12 +63,50 @@ const getDB = (): IDBDatabase => {
* 添加生成记录
*/
export const addGeneration = async (generation: Generation): Promise<void> => {
// 创建轻量级生成记录只存储必要的信息和上传后的URL
const lightweightGeneration = {
id: generation.id,
prompt: generation.prompt,
parameters: generation.parameters,
modelVersion: generation.modelVersion,
timestamp: generation.timestamp,
uploadResults: generation.uploadResults,
usageMetadata: generation.usageMetadata,
// 只存储上传后的URL不存储base64数据
sourceAssets: generation.sourceAssets.map(asset => {
const uploadedUrl = getUploadedAssetUrl(generation, asset.id, 'source');
return {
id: asset.id,
type: asset.type,
// 如果没有上传后的URL则不存储URL以避免base64数据
url: uploadedUrl || '',
mime: asset.mime,
width: asset.width,
height: asset.height,
checksum: asset.checksum
};
}),
outputAssets: generation.outputAssets.map(asset => {
const uploadedUrl = getUploadedAssetUrl(generation, asset.id, 'output');
return {
id: asset.id,
type: asset.type,
// 如果没有上传后的URL则不存储URL以避免base64数据
url: uploadedUrl || '',
mime: asset.mime,
width: asset.width,
height: asset.height,
checksum: asset.checksum
};
})
};
const db = getDB();
const transaction = db.transaction([GENERATIONS_STORE], 'readwrite');
const store = transaction.objectStore(GENERATIONS_STORE);
return new Promise((resolve, reject) => {
const request = store.add(generation);
const request = store.add(lightweightGeneration);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
@@ -74,17 +116,95 @@ export const addGeneration = async (generation: Generation): Promise<void> => {
* 添加编辑记录
*/
export const addEdit = async (edit: Edit): Promise<void> => {
// 创建轻量级编辑记录只存储必要的信息和上传后的URL
const lightweightEdit = {
id: edit.id,
parentGenerationId: edit.parentGenerationId,
maskAssetId: edit.maskAssetId,
instruction: edit.instruction,
timestamp: edit.timestamp,
uploadResults: edit.uploadResults,
parameters: edit.parameters,
usageMetadata: edit.usageMetadata,
// 只存储上传后的URL不存储base64数据
maskReferenceAsset: edit.maskReferenceAsset ? (() => {
const uploadedUrl = getUploadedAssetUrl(edit, edit.maskReferenceAsset.id, 'mask');
return {
id: edit.maskReferenceAsset.id,
type: edit.maskReferenceAsset.type,
// 如果没有上传后的URL则不存储URL以避免base64数据
url: uploadedUrl || '',
mime: edit.maskReferenceAsset.mime,
width: edit.maskReferenceAsset.width,
height: edit.maskReferenceAsset.height,
checksum: edit.maskReferenceAsset.checksum
};
})() : undefined,
outputAssets: edit.outputAssets.map(asset => {
const uploadedUrl = getUploadedAssetUrl(edit, asset.id, 'output');
return {
id: asset.id,
type: asset.type,
// 如果没有上传后的URL则不存储URL以避免base64数据
url: uploadedUrl || '',
mime: asset.mime,
width: asset.width,
height: asset.height,
checksum: asset.checksum
};
})
};
const db = getDB();
const transaction = db.transaction([EDITS_STORE], 'readwrite');
const store = transaction.objectStore(EDITS_STORE);
return new Promise((resolve, reject) => {
const request = store.add(edit);
const request = store.add(lightweightEdit);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
};
/**
* 从uploadResults中获取资产的上传后URL
* 注意:这个函数需要根据资产在数组中的位置来匹配上传结果
* - 输出资产的索引与uploadResults中的索引相对应
* - 源资产参考图像的索引从outputAssets.length开始
*/
const getUploadedAssetUrl = (record: Generation | Edit, assetId: string, assetType: 'output' | 'source' | 'mask'): string | null => {
if (!record.uploadResults || record.uploadResults.length === 0) {
return null;
}
let assetIndex = -1;
// 根据资产类型确定在uploadResults中的索引
if (assetType === 'output') {
// 输出资产的索引与在outputAssets数组中的索引相同
assetIndex = record.outputAssets.findIndex(a => a.id === assetId);
} else if (assetType === 'source') {
// 源资产参考图像的索引从outputAssets.length开始
assetIndex = record.sourceAssets?.findIndex(a => a.id === assetId);
if (assetIndex >= 0) {
assetIndex += record.outputAssets.length;
}
} else if (assetType === 'mask') {
// 遮罩参考资产通常是第一个输出资产之后的第一个源资产
assetIndex = record.outputAssets.length;
}
// 检查索引是否有效并且对应的上传结果是否存在且成功
if (assetIndex >= 0 && assetIndex < record.uploadResults.length) {
const uploadResult = record.uploadResults[assetIndex];
if (uploadResult.success && uploadResult.url) {
return uploadResult.url;
}
}
return null;
};
/**
* 获取所有生成记录(按时间倒序)
*/
@@ -148,18 +268,16 @@ export const getEditsByParentGenerationId = async (parentGenerationId: string):
/**
* 删除最旧的记录以保持限制
*/
export const cleanupOldRecords = async (limit: number = 1000): Promise<void> => {
export const cleanupOldRecords = async (limit: number = 100): Promise<void> => {
const db = getDB();
// 清理生成记录
const genTransaction = db.transaction([GENERATIONS_STORE], 'readwrite');
const genStore = genTransaction.objectStore(GENERATIONS_STORE);
const genIndex = genStore.index('timestamp');
// 清理编辑记录
const editTransaction = db.transaction([EDITS_STORE], 'readwrite');
const editStore = editTransaction.objectStore(EDITS_STORE);
const editIndex = editStore.index('timestamp');
// 获取所有记录并按时间排序
const allGenerations = await getAllGenerations();
@@ -181,6 +299,117 @@ export const cleanupOldRecords = async (limit: number = 1000): Promise<void> =>
}
};
/**
* 清理记录中的base64数据
*/
export const cleanupBase64Data = async (): Promise<void> => {
try {
// 获取所有生成记录
const generations = await getAllGenerations();
// 获取所有编辑记录
const edits = await getAllEdits();
const db = getDB();
// 更新生成记录
for (const generation of generations) {
// 检查是否有base64数据需要清理
let needsUpdate = false;
// 清理源资产中的base64数据
const cleanedSourceAssets = generation.sourceAssets.map((asset: any) => {
if (asset.url && asset.url.startsWith('data:')) {
needsUpdate = true;
return {
...asset,
url: '' // 移除base64数据
};
}
return asset;
});
// 清理输出资产中的base64数据
const cleanedOutputAssets = generation.outputAssets.map((asset: any) => {
if (asset.url && asset.url.startsWith('data:')) {
needsUpdate = true;
return {
...asset,
url: '' // 移除base64数据
};
}
return asset;
});
// 如果需要更新,则保存清理后的记录
if (needsUpdate) {
const cleanedGeneration = {
...generation,
sourceAssets: cleanedSourceAssets,
outputAssets: cleanedOutputAssets
};
const transaction = db.transaction([GENERATIONS_STORE], 'readwrite');
const store = transaction.objectStore(GENERATIONS_STORE);
await new Promise((resolve, reject) => {
const request = store.put(cleanedGeneration);
request.onsuccess = () => resolve(undefined);
request.onerror = () => reject(request.error);
});
}
}
// 更新编辑记录
for (const edit of edits) {
// 检查是否有base64数据需要清理
let needsUpdate = false;
// 清理遮罩参考资产中的base64数据
let cleanedMaskReferenceAsset = edit.maskReferenceAsset;
if (edit.maskReferenceAsset && edit.maskReferenceAsset.url && edit.maskReferenceAsset.url.startsWith('data:')) {
needsUpdate = true;
cleanedMaskReferenceAsset = {
...edit.maskReferenceAsset,
url: '' // 移除base64数据
};
}
// 清理输出资产中的base64数据
const cleanedOutputAssets = edit.outputAssets.map((asset: any) => {
if (asset.url && asset.url.startsWith('data:')) {
needsUpdate = true;
return {
...asset,
url: '' // 移除base64数据
};
}
return asset;
});
// 如果需要更新,则保存清理后的记录
if (needsUpdate) {
const cleanedEdit = {
...edit,
maskReferenceAsset: cleanedMaskReferenceAsset,
outputAssets: cleanedOutputAssets
};
const transaction = db.transaction([EDITS_STORE], 'readwrite');
const store = transaction.objectStore(EDITS_STORE);
await new Promise((resolve, reject) => {
const request = store.put(cleanedEdit);
request.onsuccess = () => resolve(undefined);
request.onerror = () => reject(request.error);
});
}
}
console.log('IndexedDB中的base64数据清理完成');
} catch (error) {
console.error('清理IndexedDB中的base64数据时出错:', error);
}
};
/**
* 清空所有记录
*/
@@ -205,4 +434,14 @@ export const clearAllRecords = async (): Promise<void> => {
request.onerror = () => reject(request.error);
})
]).then(() => undefined);
};
/**
* 关闭数据库连接
*/
export const closeDB = (): void => {
if (db) {
db.close();
db = null;
}
};