Merge pull request #62 from ljquan/fix-store

Fix store
This commit is contained in:
ljquan authored and GitHub committed 2026-01-13 22:43:22 +08:00
commit 78ce0cfc33
6 files changed
+37 -138

No files matched your search

@@ -197,13 +197,16 @@ export const IDB_DATABASES = {
} as const;
/**
* 废弃的 IndexedDB 数据库(用于清理)
* 废弃的 IndexedDB 数据库(仅作为参考记录)
* 注意:不再自动删除这些数据库,因为删除是危险操作
* 数据迁移由各服务自行处理(如 useTaskStorage, asset-storage-service)
* @deprecated 仅供参考,不再使用
*/
export const IDB_LEGACY_DATABASES = [
export const IDB_LEGACY_DATABASES_REFERENCE = [
'Drawnix', // 旧版 localforage 默认数据库
'drawnix', // 旧版画板数据(注意:与 CHARACTERS 数据库同名,需谨慎处理)
'drawnix', // 旧版画板数据(注意:与 CHARACTERS 数据库同名)
'localforage', // localforage 默认数据库
'aitu-task-queue', // 旧版任务队列
'aitu-task-queue', // 旧版任务队列(迁移到 sw-task-queue)
'aitu-media-cache', // 旧版媒体缓存
'aitu-url-cache', // 旧版 URL 缓存
] as const;
@@ -239,6 +242,3 @@ export type LSKeyDeprecated = (typeof LS_KEYS_DEPRECATED)[number];
/** IndexedDB 数据库名类型 */
export type IDBDatabaseName =
(typeof IDB_DATABASES)[keyof typeof IDB_DATABASES]['NAME'];
/** 废弃的 IndexedDB 数据库名类型 */
export type IDBLegacyDatabase = (typeof IDB_LEGACY_DATABASES)[number];
+5 -4
View File
@@ -9,7 +9,7 @@
*/
import { useEffect } from 'react';
import { taskQueueService, shouldUseSWTaskQueue } from '../services/task-queue';
import { taskQueueService, shouldUseSWTaskQueue, legacyTaskQueueService } from '../services/task-queue';
import { storageService } from '../services/storage-service';
import { UPDATE_INTERVALS } from '../constants/TASK_CONSTANTS';
import { migrateLegacyHistory } from '../utils/history-migration';
@@ -70,7 +70,7 @@ export function useTaskStorage(): void {
// Migrate legacy tasks from old storage to SW (one-time migration)
const legacyTasks = await storageService.loadTasks();
if (legacyTasks.length > 0) {
// console.log(`[useTaskStorage] Migrating ${legacyTasks.length} legacy tasks to SW`);
console.log(`[useTaskStorage] Migrating ${legacyTasks.length} legacy tasks to SW`);
// Restore legacy tasks to SW service (which will sync to SW)
await swTaskQueueService.restoreTasks(legacyTasks);
@@ -79,7 +79,7 @@ export function useTaskStorage(): void {
for (const task of legacyTasks) {
await storageService.deleteTask(task.id);
}
// console.log('[useTaskStorage] Legacy tasks migrated and cleared');
console.log('[useTaskStorage] Legacy tasks migrated and cleared');
}
// Sync tasks from SW to local state
@@ -122,7 +122,8 @@ export function useTaskStorage(): void {
errorCode = 'INTERRUPTED_DURING_SUBMISSION';
}
taskQueueService.updateTaskStatus(task.id, TaskStatus.FAILED, {
// Use legacy service directly since this code only runs in legacy mode
legacyTaskQueueService.updateTaskStatus(task.id, TaskStatus.FAILED, {
startedAt: undefined,
executionPhase: undefined, // Clear execution phase
error: {
@@ -339,6 +339,11 @@ export function useWorkflowSubmission(
referenceImages: string[],
retryContext: WorkflowRetryContext
): Promise<string> => {
// Ensure SW task queue is initialized before submitting workflow
// This sends TASK_QUEUE_INIT to SW which initializes the workflowHandler
const { swTaskQueueService } = await import('../services/sw-task-queue-service');
await swTaskQueueService.initialize();
// Convert to SW workflow format
const swWorkflow: SWWorkflowDefinition = {
id: legacyWorkflow.id,
@@ -176,40 +176,31 @@ class AssetStorageService {
/**
* 迁移旧版数据
* 1. 删除 AI 生成的素材(现在从任务队列读取)
* 2. 将本地上传的旧版 Blob 数据迁移到统一缓存
* 将旧版 Blob 数据迁移到统一缓存(包括 AI 生成的素材和本地上传的素材)
* 注意:不再删除 AI 生成的素材,因为任务队列迁移可能失败
*/
private async migrateOldData(): Promise<void> {
if (this.migrationDone || !this.store) return;
const migrationKey = 'drawnix_asset_migration_v3'; // 更新版本号
const migrationKey = 'drawnix_asset_migration_v4'; // 更新版本号,重新迁移
const migrated = localStorage.getItem(migrationKey);
if (migrated === 'done') {
this.migrationDone = true;
return;
}
// console.log('[AssetStorageService] Starting data migration v3...');
console.log('[AssetStorageService] Starting data migration v4...');
try {
const keys = await this.store.keys();
let migratedCount = 0;
let deletedAiCount = 0;
for (const key of keys) {
try {
const item = await this.store.getItem(key) as any;
if (!item) continue;
// 删除 AI 生成的素材(现在从任务队列读取)
if (item.source === 'AI_GENERATED') {
await this.store.removeItem(key);
deletedAiCount++;
// console.log(`[AssetStorageService] Deleted AI asset ${item.id}`);
continue;
}
// 迁移本地上传的旧版数据
// 迁移旧版数据(包含 blobData 的格式)
if (this.isLegacyFormat(item)) {
// 生成新的 URL
const assetUrl = this.generateAssetUrl(item.id, item.mimeType);
@@ -218,6 +209,8 @@ class AssetStorageService {
const cacheType = item.type === 'IMAGE' ? 'image' : 'video';
await unifiedCacheService.cacheMediaFromBlob(assetUrl, item.blobData, cacheType, {
taskId: item.id,
prompt: item.prompt,
model: item.modelName,
});
// 更新为新格式(不含 blobData)
@@ -230,11 +223,13 @@ class AssetStorageService {
mimeType: item.mimeType,
createdAt: item.createdAt,
size: item.size,
prompt: item.prompt,
modelName: item.modelName,
};
await this.store.setItem(key, newStoredAsset);
migratedCount++;
// console.log(`[AssetStorageService] Migrated local asset ${item.id}`);
console.log(`[AssetStorageService] Migrated asset ${item.id} (${item.source})`);
}
} catch (error) {
console.error(`[AssetStorageService] Failed to process asset ${key}:`, error);
@@ -243,7 +238,7 @@ class AssetStorageService {
localStorage.setItem(migrationKey, 'done');
this.migrationDone = true;
// console.log(`[AssetStorageService] Migration v3 completed: migrated ${migratedCount} local assets, deleted ${deletedAiCount} AI assets`);
console.log(`[AssetStorageService] Migration v4 completed: migrated ${migratedCount} assets`);
} catch (error) {
console.error('[AssetStorageService] Migration failed:', error);
}
@@ -1,17 +1,11 @@
/**
* Database Cleanup Service
*
* Cleans up legacy/redundant IndexedDB databases and LocalStorage data
* that are no longer needed. This service runs once on app startup and
* removes old data after migration to new storage locations.
* Cleans up deprecated LocalStorage data that is no longer needed.
* This service runs once on app startup.
*
* Legacy databases to clean:
* - Drawnix: Old localforage default database
* - drawnix: Old board data (migrated to aitu-workspace)
* - localforage: localforage default database
* - aitu-task-queue: Old task queue (migrated to sw-task-queue)
* - aitu-media-cache: Old media cache (migrated to drawnix-unified-cache)
* - aitu-url-cache: Old URL cache (migrated to drawnix-unified-cache)
* Note: IndexedDB database cleanup has been removed as it's a dangerous operation.
* Data migration is handled by individual services (e.g., useTaskStorage, asset-storage-service).
*
* Legacy LocalStorage keys to clean:
* - aitu-recent-colors-shadow: Orphaned data
@@ -20,80 +14,11 @@
import {
LS_KEYS,
LS_KEYS_DEPRECATED,
IDB_LEGACY_DATABASES,
} from '../constants/storage-keys';
// Storage key to track if cleanup has been performed
const CLEANUP_DONE_KEY = LS_KEYS.DB_CLEANUP_DONE;
// List of legacy databases to delete (use from constants)
const LEGACY_DATABASES = [...IDB_LEGACY_DATABASES];
/**
* Check if a database exists
*/
async function checkDBExists(dbName: string): Promise<boolean> {
return new Promise((resolve) => {
try {
const request = indexedDB.open(dbName);
let existed = false;
request.onupgradeneeded = () => {
// Database didn't exist, was just created
existed = false;
};
request.onsuccess = () => {
const db = request.result;
// Check if it has any object stores (empty DB means it was just created)
existed = db.objectStoreNames.length > 0;
db.close();
// If we just created an empty DB, delete it
if (!existed) {
indexedDB.deleteDatabase(dbName);
}
resolve(existed);
};
request.onerror = () => {
resolve(false);
};
} catch {
resolve(false);
}
});
}
/**
* Delete a database
*/
async function deleteDatabase(dbName: string): Promise<boolean> {
return new Promise((resolve) => {
try {
const request = indexedDB.deleteDatabase(dbName);
request.onsuccess = () => {
// console.log(`[DBCleanup] Deleted database: ${dbName}`);
resolve(true);
};
request.onerror = () => {
console.warn(`[DBCleanup] Failed to delete database: ${dbName}`);
resolve(false);
};
request.onblocked = () => {
console.warn(`[DBCleanup] Database deletion blocked: ${dbName}`);
resolve(false);
};
} catch {
resolve(false);
}
});
}
/**
* Clean up deprecated LocalStorage keys
*/
@@ -124,40 +49,14 @@ export async function runDatabaseCleanup(): Promise<void> {
return;
}
// console.log('[DBCleanup] Starting legacy database cleanup...');
let deletedCount = 0;
let skippedCount = 0;
// Clean up legacy IndexedDB databases
for (const dbName of LEGACY_DATABASES) {
try {
const exists = await checkDBExists(dbName);
if (exists) {
const deleted = await deleteDatabase(dbName);
if (deleted) {
deletedCount++;
} else {
skippedCount++;
}
}
} catch (error) {
console.warn(`[DBCleanup] Error processing database ${dbName}:`, error);
skippedCount++;
}
}
// Clean up deprecated LocalStorage keys
const lsCleanedCount = cleanupDeprecatedLocalStorage();
// Mark cleanup as done
localStorage.setItem(CLEANUP_DONE_KEY, Date.now().toString());
if (deletedCount > 0 || skippedCount > 0 || lsCleanedCount > 0) {
// console.log(`[DBCleanup] Cleanup complete: IDB deleted ${deletedCount}, IDB skipped ${skippedCount}, LS cleaned ${lsCleanedCount}`);
} else {
// console.log('[DBCleanup] No legacy data found');
if (lsCleanedCount > 0) {
// console.log(`[DBCleanup] Cleanup complete: LS cleaned ${lsCleanedCount}`);
}
}
@@ -174,10 +73,7 @@ if (typeof window !== 'undefined') {
(window as unknown as Record<string, unknown>).__dbCleanup = {
run: runDatabaseCleanup,
reset: resetCleanupFlag,
checkDB: checkDBExists,
deleteDB: deleteDatabase,
cleanupLS: cleanupDeprecatedLocalStorage,
LEGACY_DATABASES,
LS_KEYS_DEPRECATED,
};
}
+3 -1
View File
@@ -54,7 +54,7 @@ export interface Asset {
/**
* Stored Asset Interface
* 存储的素材接口 - IndexedDB中的数据结构
* 注意:只存储本地上传的素材,AI 生成的素材从任务队列读取
* 支持本地上传和 AI 生成的素材
*/
export interface StoredAsset {
id: string;
@@ -66,6 +66,8 @@ export interface StoredAsset {
createdAt: number;
size?: number;
contentHash?: string; // 文件内容哈希,用于去重
prompt?: string; // AI 生成的提示词(仅 AI_GENERATED)
modelName?: string; // 生成模型名称(仅 AI_GENERATED)
}
/**