fix(data): include image model configs in backups

Include image model configuration data in app-wide export/import flows and initialize the image model manager before wiring backup services.
This commit is contained in:
linshen committed 2026-05-03 13:15:18 +08:00
1 parent 2d8720247f
commit 0c9ddd5063
5 files changed
+91 -8

No files matched your search

+13 -4
View File
@@ -1,5 +1,6 @@
import { IHistoryManager } from '../history/types';
import { IModelManager } from '../model/types';
import type { IImageModelManager } from '../image/types';
import { ITemplateManager } from '../template/types';
import { IPreferenceService } from '../preference/types';
import { ContextRepo } from '../context/types';
@@ -41,6 +42,7 @@ export interface IDataManager {
export class DataManager implements IDataManager {
private modelManager: IModelManager;
private imageModelManager?: IImageModelManager;
private templateManager: ITemplateManager;
private historyManager: IHistoryManager;
private preferenceService: IPreferenceService;
@@ -51,9 +53,11 @@ export class DataManager implements IDataManager {
templateManager: ITemplateManager,
historyManager: IHistoryManager,
preferenceService: IPreferenceService,
contextRepo: ContextRepo
contextRepo: ContextRepo,
imageModelManager?: IImageModelManager
) {
this.modelManager = modelManager;
this.imageModelManager = imageModelManager;
this.templateManager = templateManager;
this.historyManager = historyManager;
this.preferenceService = preferenceService;
@@ -67,6 +71,9 @@ export class DataManager implements IDataManager {
// 使用各服务的exportData接口,使用固定的键名保持兼容性
data['history'] = await this.historyManager.exportData();
data['models'] = await this.modelManager.exportData();
if (this.imageModelManager) {
data['imageModels'] = await this.imageModelManager.exportData();
}
data['userTemplates'] = await this.templateManager.exportData();
data['userSettings'] = await this.preferenceService.exportData();
data['contexts'] = await this.contextRepo.exportData();
@@ -110,7 +117,7 @@ export class DataManager implements IDataManager {
dataToImport = exportData.data;
}
// Old format: direct data object { history: [...], models: [...], ... }
else if (exportData.history || exportData.models || exportData.userTemplates || exportData.userSettings || exportData.contexts) {
else if (exportData.history || exportData.models || exportData.imageModels || exportData.userTemplates || exportData.userSettings || exportData.contexts) {
dataToImport = exportData;
}
else {
@@ -123,6 +130,7 @@ export class DataManager implements IDataManager {
const serviceMap = [
{ service: this.historyManager, dataKey: 'history' },
{ service: this.modelManager, dataKey: 'models' },
...(this.imageModelManager ? [{ service: this.imageModelManager, dataKey: 'imageModels' }] : []),
{ service: this.templateManager, dataKey: 'userTemplates' },
{ service: this.preferenceService, dataKey: 'userSettings' },
{ service: this.contextRepo, dataKey: 'contexts' }
@@ -161,7 +169,8 @@ export function createDataManager(
templateManager: ITemplateManager,
historyManager: IHistoryManager,
preferenceService: IPreferenceService,
contextRepo: ContextRepo
contextRepo: ContextRepo,
imageModelManager?: IImageModelManager
): DataManager {
return new DataManager(modelManager, templateManager, historyManager, preferenceService, contextRepo);
return new DataManager(modelManager, templateManager, historyManager, preferenceService, contextRepo, imageModelManager);
}
@@ -8,6 +8,9 @@ import { TemplateLanguageService } from '../../../src/services/template/language
import { MemoryStorageProvider } from '../../../src/services/storage/memoryStorageProvider';
import { TextModelConfig } from '../../../src/services/model/types';
import { TextAdapterRegistry } from '../../../src/services/llm/adapters/registry';
import { ImageAdapterRegistry } from '../../../src/services/image/adapters/registry';
import { ImageModelManager } from '../../../src/services/image-model/manager';
import type { ImageModelConfig } from '../../../src/services/image/types';
import { Template } from '../../../src/services/template/types';
import { PromptRecord } from '../../../src/services/history/types';
import { ContextRepo } from '../../../src/services/context/types';
@@ -19,9 +22,11 @@ describe('DataManager Import/Export Integration', () => {
let templateManager: TemplateManager;
let historyManager: HistoryManager;
let preferenceService: PreferenceService;
let imageModelManager: ImageModelManager;
let mockContextRepo: ContextRepo;
let storageProvider: MemoryStorageProvider;
let registry: TextAdapterRegistry;
let imageRegistry: ImageAdapterRegistry;
beforeEach(async () => {
storageProvider = new MemoryStorageProvider();
@@ -29,9 +34,12 @@ describe('DataManager Import/Export Integration', () => {
// 创建真实的服务实例
registry = new TextAdapterRegistry();
imageRegistry = new ImageAdapterRegistry();
preferenceService = new PreferenceService(storageProvider);
modelManager = new ModelManager(storageProvider, registry);
await modelManager.ensureInitialized();
imageModelManager = new ImageModelManager(storageProvider, imageRegistry);
await imageModelManager.ensureInitialized();
const languageService = new TemplateLanguageService(storageProvider, preferenceService);
await languageService.initialize();
@@ -59,7 +67,14 @@ describe('DataManager Import/Export Integration', () => {
validateData: vi.fn().mockReturnValue(true),
} as ContextRepo;
dataManager = new DataManager(modelManager, templateManager, historyManager, preferenceService, mockContextRepo);
dataManager = new DataManager(
modelManager,
templateManager,
historyManager,
preferenceService,
mockContextRepo,
imageModelManager,
);
});
afterEach(() => {
@@ -85,6 +100,24 @@ describe('DataManager Import/Export Integration', () => {
paramOverrides: {}
};
await modelManager.addModel('test-model-key', testModel);
const imageAdapter = imageRegistry.getAdapter('openai');
const imageProvider = imageAdapter.getProvider();
const imageModel = imageAdapter.buildDefaultModel('gpt-image-1');
const testImageModel: ImageModelConfig = {
id: 'test-image-model-key',
name: 'Test Image Model',
providerId: imageProvider.id,
modelId: imageModel.id,
enabled: true,
connectionConfig: {
apiKey: 'test-image-key',
baseURL: 'https://images.test.com/v1',
},
paramOverrides: {},
provider: imageProvider,
model: imageModel,
};
await imageModelManager.addConfig(testImageModel);
// 添加模板
const testTemplate: Template = {
@@ -129,6 +162,7 @@ describe('DataManager Import/Export Integration', () => {
// 验证导出的数据结构
expect(exportedData.data).toHaveProperty('models');
expect(exportedData.data).toHaveProperty('imageModels');
expect(exportedData.data).toHaveProperty('userTemplates');
expect(exportedData.data).toHaveProperty('history');
expect(exportedData.data).toHaveProperty('userSettings');
@@ -137,6 +171,9 @@ describe('DataManager Import/Export Integration', () => {
const exportedModel = exportedData.data.models.find((m: any) => m.id === 'test-model-key');
expect(exportedModel).toBeDefined();
expect(exportedModel.name).toBe('Test Model');
const exportedImageModel = exportedData.data.imageModels.find((m: any) => m.id === 'test-image-model-key');
expect(exportedImageModel).toBeDefined();
expect(exportedImageModel.name).toBe('Test Image Model');
const exportedTemplate = exportedData.data.userTemplates.find((t: any) => t.id === 'test-template');
expect(exportedTemplate).toBeDefined();
@@ -151,6 +188,7 @@ describe('DataManager Import/Export Integration', () => {
// 3. 清空数据
await historyManager.clearHistory();
await preferenceService.clear();
await imageModelManager.deleteConfig('test-image-model-key');
// 注意:模型和模板的清空需要通过删除操作
// 4. 导入数据
@@ -163,6 +201,10 @@ describe('DataManager Import/Export Integration', () => {
expect(importedModel).toBeDefined();
expect(importedModel?.name).toBe('Test Model');
expect(importedModel?.enabled).toBe(true);
const importedImageModel = await imageModelManager.getConfig('test-image-model-key');
expect(importedImageModel).toBeDefined();
expect(importedImageModel?.name).toBe('Test Image Model');
expect(importedImageModel?.connectionConfig?.apiKey).toBe('test-image-key');
// 验证模板
const importedTemplates = await templateManager.listTemplates();
+26 -1
View File
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { DataManager } from '../../../src/services/data/manager';
import { IHistoryManager } from '../../../src/services/history/types';
import { IModelManager } from '../../../src/services/model/types';
import type { IImageModelManager } from '../../../src/services/image/types';
import { ITemplateManager, Template } from '../../../src/services/template/types';
import { IPreferenceService } from '../../../src/services/preference/types';
import { ContextRepo } from '../../../src/services/context/types';
@@ -11,6 +12,7 @@ import { DATA_ERROR_CODES } from '../../../src/constants/error-codes';
describe('DataManager', () => {
let dataManager: DataManager;
let mockModelManager: IModelManager;
let mockImageModelManager: IImageModelManager;
let mockTemplateManager: ITemplateManager;
let mockHistoryManager: IHistoryManager;
let mockPreferenceService: IPreferenceService;
@@ -73,6 +75,21 @@ describe('DataManager', () => {
validateData: vi.fn().mockReturnValue(true),
};
mockImageModelManager = {
ensureInitialized: vi.fn().mockResolvedValue(undefined),
isInitialized: vi.fn().mockResolvedValue(true),
addConfig: vi.fn().mockResolvedValue(undefined),
updateConfig: vi.fn().mockResolvedValue(undefined),
deleteConfig: vi.fn().mockResolvedValue(undefined),
getConfig: vi.fn().mockResolvedValue(null),
getAllConfigs: vi.fn().mockResolvedValue([]),
getEnabledConfigs: vi.fn().mockResolvedValue([]),
exportData: vi.fn().mockResolvedValue([]),
importData: vi.fn().mockResolvedValue(undefined),
getDataType: vi.fn().mockResolvedValue('image-model-configs'),
validateData: vi.fn().mockReturnValue(true),
};
mockHistoryManager = {
getRecords: vi.fn().mockResolvedValue([]),
addRecord: vi.fn().mockResolvedValue(undefined),
@@ -117,7 +134,8 @@ describe('DataManager', () => {
mockTemplateManager,
mockHistoryManager,
mockPreferenceService,
mockContextRepo
mockContextRepo,
mockImageModelManager
);
});
@@ -128,6 +146,7 @@ describe('DataManager', () => {
describe('exportAllData', () => {
it('should fetch data from all managers and return a JSON string', async () => {
const models = [{ id: 'model1', name: 'Test Model' }];
const imageModels = [{ id: 'image-model1', name: 'Test Image Model' }];
const templates: Template[] = [{ id: 'tpl1', name: 'Test Template', content: 'c', isBuiltin: false, metadata: { templateType: 'optimize', version: '1.0', lastModified: 0 } }];
const history = [{
id: 'hist1',
@@ -142,6 +161,7 @@ describe('DataManager', () => {
}];
(mockModelManager.exportData as vi.Mock).mockResolvedValue(models);
(mockImageModelManager.exportData as vi.Mock).mockResolvedValue(imageModels);
(mockTemplateManager.exportData as vi.Mock).mockResolvedValue(templates.filter(t => !t.isBuiltin));
(mockHistoryManager.exportData as vi.Mock).mockResolvedValue(history as any);
(mockPreferenceService.exportData as vi.Mock).mockResolvedValue({});
@@ -151,6 +171,7 @@ describe('DataManager', () => {
expect(data.version).toBe(1);
expect(data.data.models).toEqual(models);
expect(data.data.imageModels).toEqual(imageModels);
expect(data.data.userTemplates).toEqual(templates.filter(t => !t.isBuiltin));
expect(data.data.history).toEqual(history);
});
@@ -161,6 +182,7 @@ describe('DataManager', () => {
version: 1,
data: {
models: [{ key: 'imp-model1', id: 'imp-model1', name: 'Imported Model' }],
imageModels: [{ id: 'imp-image-model1', name: 'Imported Image Model' }],
userTemplates: [{ id: 'imp-tpl1', name: 'Imported Template', content: 'test content', isBuiltin: false, metadata: { templateType: 'optimize', version: '1.0', lastModified: 0 } }],
history: [{
id: 'imp-hist1',
@@ -181,6 +203,7 @@ describe('DataManager', () => {
await dataManager.importAllData(JSON.stringify(importData));
expect(mockModelManager.importData).toHaveBeenCalledWith(importData.data.models);
expect(mockImageModelManager.importData).toHaveBeenCalledWith(importData.data.imageModels);
expect(mockTemplateManager.importData).toHaveBeenCalledWith(importData.data.userTemplates);
expect(mockHistoryManager.importData).toHaveBeenCalledWith(importData.data.history);
expect(mockPreferenceService.importData).toHaveBeenCalledWith(importData.data.userSettings);
@@ -210,12 +233,14 @@ describe('DataManager', () => {
templateId: 'old-template'
}],
models: [{ key: 'old-model1', name: 'Old Format Model' }],
imageModels: [{ id: 'old-image-model1', name: 'Old Image Model' }],
userTemplates: [{ id: 'old-tpl1', name: 'Old Format Template', content: 'old content', isBuiltin: false, metadata: { templateType: 'optimize', version: '1.0', lastModified: 0 } }],
userSettings: { 'app:settings:ui:theme-id': 'light' },
};
await expect(dataManager.importAllData(JSON.stringify(oldFormatData))).resolves.not.toThrow();
expect(mockModelManager.importData).toHaveBeenCalled();
expect(mockImageModelManager.importData).toHaveBeenCalled();
expect(mockTemplateManager.importData).toHaveBeenCalled();
expect(mockHistoryManager.importData).toHaveBeenCalled();
expect(mockPreferenceService.importData).toHaveBeenCalled();
+1 -1
View File
@@ -636,7 +636,7 @@ async function initializeServices() {
contextRepo = createContextRepo(storageProvider);
console.log('[DESKTOP] Creating Data manager...');
dataManager = createDataManager(modelManager, templateManager, historyManager, preferenceService, contextRepo);
dataManager = createDataManager(modelManager, templateManager, historyManager, preferenceService, contextRepo, imageModelManager);
console.log('[DESKTOP] Creating Favorite manager...');
favoriteManager = new FavoriteManager(storageProvider);
@@ -419,7 +419,14 @@ export function useAppInitializer(): {
const contextRepo = createContextRepo(storageProvider);
// 创建 DataManager(需要contextRepo)
dataManager = createDataManager(modelManagerInstance, templateManagerInstance, historyManagerInstance, preferenceService, contextRepo);
dataManager = createDataManager(
modelManagerInstance,
templateManagerInstance,
historyManagerInstance,
preferenceService,
contextRepo,
imageModelManagerInstance,
);
// 创建收藏管理器
favoriteManager = new FavoriteManager(storageProvider);