From 22a1e4ac3f272cf1c618fbbd019e60269bd47c48 Mon Sep 17 00:00:00 2001 From: linshen <32978552+linshenkx@users.noreply.github.com> Date: Mon, 29 Sep 2025 23:40:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20=E4=BF=AE=E5=A4=8D=E5=9B=BE?= =?UTF-8?q?=E5=83=8F=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE=E7=9A=84=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=92=8C=E5=AE=B9=E9=94=99=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 - 开发期间修改配置ID后,旧配置存储在本地但数据结构不完整 - 删除损坏配置时报错"Configuration does not exist" - 即使删除成功,配置仍然存在于列表中 ## 根本原因 - 存储key和配置ID不一致:旧配置自带ID字段与存储key不匹配 - 删除操作使用修复后的ID查找,无法匹配存储中的实际key ## 解决方案 ### 1. deleteConfig - 容错处理 - 移除严格的存在性检查,不再抛出异常 - 允许删除不存在的配置(降级处理) - 添加详细的日志输出用于诊断 ### 2. getConfig - 安全修复 - 尝试修复损坏的配置结构 - 即使修复失败也返回配置,确保可以删除 - 补齐缺失的provider/model字段 ### 3. getAllConfigs - 关键修复 - **强制使用存储key作为配置ID**(最关键) - 保证读写一致性:删除时使用的ID = 存储中的key - 对无法修复的配置返回占位对象,标记为disabled ### 4. ensureSelfContained - 容错降级 - 捕获修复失败的异常 - 创建占位配置,包含完整的provider/model结构 - 自动标记为disabled,引导用户删除 ## 效果 - ✅ 损坏配置可以正常显示和删除 - ✅ 删除操作不再报错 - ✅ ID一致性得到保证 - ✅ 向后兼容,支持配置结构变更 - ✅ 用户无需手动清理本地存储 --- .../core/src/services/image-model/manager.ts | 110 ++++++++++++++---- 1 file changed, 86 insertions(+), 24 deletions(-) diff --git a/packages/core/src/services/image-model/manager.ts b/packages/core/src/services/image-model/manager.ts index 57919b54..bc57d7fa 100644 --- a/packages/core/src/services/image-model/manager.ts +++ b/packages/core/src/services/image-model/manager.ts @@ -132,10 +132,18 @@ export class ImageModelManager implements IImageModelManager { this.storageKey, (current) => { const data = current || {} + + // 强制删除:无论配置是否存在都尝试删除 + // 这确保损坏的配置也能被清理 if (!data[id]) { - throw new Error(`Configuration with id '${id}' does not exist`) + console.warn(`[ImageModelManager] Config ${id} not found in storage, but proceeding anyway`) + // 仍然返回原数据,因为确实没什么可删的 + return data } + + // 配置存在,正常删除 const { [id]: removed, ...rest } = data + console.log(`[ImageModelManager] Successfully deleted config: ${id}`) return rest } ) @@ -146,25 +154,48 @@ export class ImageModelManager implements IImageModelManager { const data: Record = raw ? JSON.parse(raw) : {} const cfg = data[id] if (!cfg) return null + // 轻量迁移兜底:返回前补齐缺失的 id,避免 UI 无法删除 - // 说明:旧数据来自开发期,仅补 id 用于删除,不补 providerId/modelId/provider/model if (!(cfg as any).id) { - return { ...(cfg as any), id } as ImageModelConfig + ;(cfg as any).id = id + } + + // 尝试修复损坏的配置,确保能够正常读取和删除 + try { + return this.ensureSelfContained(cfg) + } catch (error) { + // 即使修复失败,也返回配置(已在ensureSelfContained中标记为disabled) + console.warn(`[ImageModelManager] Failed to fully repair config ${id}, but returning for deletion:`, error) + return cfg } - return cfg } async getAllConfigs(): Promise { const raw = await this.storage.getItem(this.storageKey) const data: Record = raw ? JSON.parse(raw) : {} - // 轻量迁移兜底:为缺失 id 的旧记录补齐 id(仅返回层面,不强制持久化) - // 说明:旧数据来自开发期,仅补 id 以便在界面上删除,无需补齐其它字段 + + // 轻量迁移兜底:为缺失 id 的旧记录补齐 id,并尝试修复损坏的配置 return Object.entries(data).map(([key, cfg]) => { - if (cfg && typeof cfg === 'object' && !(cfg as any).id) { - return { ...(cfg as any), id: key } as ImageModelConfig + if (!cfg || typeof cfg !== 'object') { + return null } - return cfg - }) + + // 始终使用存储键作为公开的 id,保持删除等操作一致 + ;(cfg as any).id = key + + // 尝试修复配置,如果失败则返回占位配置(标记为disabled) + try { + return this.ensureSelfContained(cfg) + } catch (error) { + console.warn(`[ImageModelManager] Failed to repair config ${key}, returning placeholder:`, error) + // 返回最小占位配置,确保能在UI中显示和删除 + return { + ...cfg, + id: key, + enabled: false + } as ImageModelConfig + } + }).filter((cfg): cfg is ImageModelConfig => cfg !== null) } async getEnabledConfigs(): Promise { @@ -249,23 +280,54 @@ export class ImageModelManager implements IImageModelManager { return config } - // 获取provider和model信息 - const adapter = this.registry.getAdapter(config.providerId) - const provider = adapter.getProvider() + try { + // 获取provider和model信息 + const adapter = this.registry.getAdapter(config.providerId) + const provider = adapter.getProvider() - // 尝试从静态模型列表获取模型信息 - let model = this.registry.getStaticModels(config.providerId).find(m => m.id === config.modelId) + // 尝试从静态模型列表获取模型信息 + let model = this.registry.getStaticModels(config.providerId).find(m => m.id === config.modelId) - // 如果静态模型不存在,使用buildDefaultModel构建 - if (!model) { - model = adapter.buildDefaultModel(config.modelId) - } + // 如果静态模型不存在,使用buildDefaultModel构建 + if (!model) { + model = adapter.buildDefaultModel(config.modelId) + } - // 返回自包含配置 - return { - ...config, - provider, - model + // 返回自包含配置 + return { + ...config, + provider, + model + } + } catch (error) { + // 对于无法修复的旧配置,创建占位数据并禁用,允许用户查看和删除 + console.warn(`[ImageModelManager] Cannot repair legacy config ${config.id}, marking as disabled:`, error) + return { + ...config, + enabled: false, + provider: { + id: config.providerId || 'unknown', + name: `Unknown Provider (${config.providerId || 'unknown'})`, + description: '此配置损坏,无法修复', + requiresApiKey: false, + supportsDynamicModels: false, + defaultBaseURL: '', + connectionSchema: { required: [], optional: [], fieldTypes: {} } + }, + model: { + id: config.modelId || 'unknown', + name: `Unknown Model (${config.modelId || 'unknown'})`, + description: '此配置损坏,请删除后重新创建', + providerId: config.providerId || 'unknown', + capabilities: { + text2image: false, + image2image: false, + multiImage: false + }, + parameterDefinitions: [], + defaultParameterValues: {} + } + } as ImageModelConfig } }