diff --git a/packages/config/src/migrations/m004-encrypted-keys-to-auth-profiles.ts b/packages/config/src/migrations/m004-encrypted-keys-to-auth-profiles.ts index 32091407..e8919a18 100644 --- a/packages/config/src/migrations/m004-encrypted-keys-to-auth-profiles.ts +++ b/packages/config/src/migrations/m004-encrypted-keys-to-auth-profiles.ts @@ -13,6 +13,31 @@ import type { Migration, MigrationContext } from './types' import { isEncrypted, decryptApiKey } from './crypto-legacy' import { writeAuthProfile } from '../auth-profiles' +/** + * Derive a human-readable profile name from config fields. + * Official providers → provider id (e.g. "deepseek") + * openai-compatible → hostname from baseUrl (e.g. "api.example.com") or config name + */ +function deriveProfileName(provider: string, config: Record): string { + if (provider !== 'openai-compatible') { + return provider.toLowerCase().replace(/\s+/g, '-') + } + + const baseUrl = config.baseUrl as string + if (baseUrl) { + try { + return new URL(baseUrl).hostname.toLowerCase() + } catch { + // invalid URL, fall through + } + } + + const name = config.name as string + if (name) return name.toLowerCase().replace(/\s+/g, '-') + + return 'custom' +} + export const m004EncryptedKeysToAuthProfiles: Migration = { version: 4, name: 'encrypted-keys-to-auth-profiles', @@ -31,34 +56,40 @@ export const m004EncryptedKeysToAuthProfiles: Migration = { const configs = (data.configs as Array>) || [] let migrated = false + const usedProfileNames = new Set() for (const config of configs) { const apiKey = config.apiKey as string if (!apiKey) continue - if (isEncrypted(apiKey)) { - const plainKey = decryptApiKey(apiKey) - if (plainKey) { - const provider = (config.provider as string) || 'unknown' - const name = (config.name as string) || provider - const profileName = name.toLowerCase().replace(/\s+/g, '-') + const provider = (config.provider as string) || 'unknown' + let plainKey: string | null = null - writeAuthProfile(profileName, { type: 'api_key', provider, key: plainKey }) - config.apiKey = '' - migrated = true - ctx.logger.info('Migration', `Migrated API key for "${name}" to auth-profiles.json`) - } else { + if (isEncrypted(apiKey)) { + plainKey = decryptApiKey(apiKey) + if (!plainKey) { ctx.logger.warn('Migration', `Failed to decrypt API key for "${config.name}", skipping`) + continue } } else if (apiKey.length > 0) { - const provider = (config.provider as string) || 'unknown' - const name = (config.name as string) || provider - const profileName = name.toLowerCase().replace(/\s+/g, '-') + plainKey = apiKey + } - writeAuthProfile(profileName, { type: 'api_key', provider, key: apiKey }) + if (plainKey) { + const baseName = deriveProfileName(provider, config) + + let profileName = baseName + if (usedProfileNames.has(profileName)) { + let i = 2 + while (usedProfileNames.has(`${profileName}-${i}`)) i++ + profileName = `${profileName}-${i}` + } + usedProfileNames.add(profileName) + + writeAuthProfile(profileName, { type: 'api_key', provider, key: plainKey }) config.apiKey = '' migrated = true - ctx.logger.info('Migration', `Migrated plaintext API key for "${name}" to auth-profiles.json`) + ctx.logger.info('Migration', `Migrated API key → profile "${profileName}"`) } } diff --git a/src/components/common/Settings/AI/useAIConfigForm.ts b/src/components/common/Settings/AI/useAIConfigForm.ts index bf01eca2..2e1e309a 100644 --- a/src/components/common/Settings/AI/useAIConfigForm.ts +++ b/src/components/common/Settings/AI/useAIConfigForm.ts @@ -597,32 +597,22 @@ export function useAIConfigForm(props: { // ============ 保存 ============ function generateName(): string { - let providerName: string - if (isCompatMode.value && formData.value.baseUrl) { try { const url = new URL(formData.value.baseUrl) - providerName = url.hostname + return url.hostname } catch { - providerName = t('settings.aiConfig.modal.customService') + return t('settings.aiConfig.modal.customService') } - } else { - const def = currentProviderDef.value - providerName = def - ? getLocalizedProviderName(def.id) || def.name - : (() => { - const legacy = props.providers.value.find((p) => p.id === formData.value.provider) - if (legacy) return legacy.name - return formData.value.baseUrl || t('settings.aiConfig.modal.customService') - })() } - const modelId = formData.value.model.trim() - if (!modelId) return providerName + const def = currentProviderDef.value + if (def) return getLocalizedProviderName(def.id) || def.name - const modelDef = llmStore.getModelById(formData.value.provider, modelId) - const modelName = modelDef?.name || modelId - return `${providerName} - ${modelName}` + const legacy = props.providers.value.find((p) => p.id === formData.value.provider) + if (legacy) return legacy.name + + return formData.value.baseUrl || t('settings.aiConfig.modal.customService') } async function doSave() {