From e14c81d178ac1020db5cb05c8cc6f949c0cb2362 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 8 May 2026 10:52:30 +0800 Subject: [PATCH] feat(settings): persist LLM base URL presets --- src/composables/useLlmProviderDirectory.ts | 29 ++++++++++++++++++++++ src/composables/useSetupWizard.ts | 4 +++ src/views/setting/AccountSettingSystem.vue | 19 +++++++++++++- src/views/setup/AgentSettingsStep.vue | 15 ++++++++++- 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/composables/useLlmProviderDirectory.ts b/src/composables/useLlmProviderDirectory.ts index 0953376d..935f48b4 100644 --- a/src/composables/useLlmProviderDirectory.ts +++ b/src/composables/useLlmProviderDirectory.ts @@ -17,11 +17,13 @@ export interface LlmProviderAuthStatus { } export interface LlmProviderUrlPreset { + id: string label: string value: string } export interface LlmProviderUrlPresetItem { + id: string title: string value: string subtitle?: string @@ -80,6 +82,7 @@ interface UseLlmProviderDirectoryOptions { provider: Ref apiKey: Ref baseUrl: Ref + baseUrlPreset?: Ref model: Ref maxContextTokens?: Ref authConnected?: Ref @@ -110,6 +113,7 @@ export function useLlmProviderDirectory(options: UseLlmProviderDirectoryOptions) const providerItems = computed(() => providers.value.map(item => ({ title: item.name, value: item.id }))) const baseUrlPresetItems = computed(() => (selectedProvider.value?.base_url_presets || []).map(item => ({ + id: item.id, title: item.value, value: item.value, subtitle: item.label, @@ -150,14 +154,37 @@ export function useLlmProviderDirectory(options: UseLlmProviderDirectoryOptions) const currentBaseUrl = normalizeValue(options.baseUrl.value) const defaultBaseUrl = provider.default_base_url || '' + const defaultPresetId = normalizeValue(provider.base_url_presets?.[0]?.id) if (reset) { options.baseUrl.value = defaultBaseUrl + if (options.baseUrlPreset) { + options.baseUrlPreset.value = defaultPresetId + } return } if (!currentBaseUrl && defaultBaseUrl) { options.baseUrl.value = defaultBaseUrl } + + if (!options.baseUrlPreset) return + + const currentPresetId = normalizeValue(options.baseUrlPreset.value) + if (currentPresetId) return + + const matchedPreset = (provider.base_url_presets || []).find( + item => normalizeValue(item.value) === normalizeValue(options.baseUrl.value), + ) + options.baseUrlPreset.value = matchedPreset?.id || defaultPresetId + } + + function setBaseUrlPreset(presetId?: string, presetValue?: string) { + if (!options.baseUrlPreset) return + + options.baseUrlPreset.value = normalizeValue(presetId) + if (presetValue !== undefined) { + options.baseUrl.value = presetValue || '' + } } function handleProviderSelection(resetBaseUrl = true) { @@ -225,6 +252,7 @@ export function useLlmProviderDirectory(options: UseLlmProviderDirectoryOptions) provider: normalizeValue(options.provider.value), api_key: normalizeValue(options.apiKey.value) || undefined, base_url: normalizeValue(options.baseUrl.value) || undefined, + base_url_preset: normalizeValue(options.baseUrlPreset?.value) || undefined, force_refresh: forceRefresh, }, }) @@ -363,6 +391,7 @@ export function useLlmProviderDirectory(options: UseLlmProviderDirectoryOptions) showApiKeyField, hasUsableCredential, canRefreshModels, + setBaseUrlPreset, authDialogVisible, authPolling, authPopupBlocked, diff --git a/src/composables/useSetupWizard.ts b/src/composables/useSetupWizard.ts index b676507b..8bdc7ded 100644 --- a/src/composables/useSetupWizard.ts +++ b/src/composables/useSetupWizard.ts @@ -60,6 +60,7 @@ export interface WizardData { supportAudioInputOutput: boolean apiKey: string baseUrl: string + baseUrlPreset: string maxContextTokens: number voiceApiKey: string voiceBaseUrl: string @@ -240,6 +241,7 @@ const wizardData = ref({ supportAudioInputOutput: false, apiKey: '', baseUrl: 'https://api.deepseek.com', + baseUrlPreset: '', maxContextTokens: 64, voiceApiKey: '', voiceBaseUrl: '', @@ -1396,6 +1398,7 @@ export function useSetupWizard() { LLM_SUPPORT_AUDIO_INPUT_OUTPUT: wizardData.value.agent.supportAudioInputOutput, LLM_API_KEY: wizardData.value.agent.apiKey, LLM_BASE_URL: wizardData.value.agent.baseUrl || null, + LLM_BASE_URL_PRESET: wizardData.value.agent.baseUrlPreset || null, LLM_MAX_CONTEXT_TOKENS: wizardData.value.agent.maxContextTokens, AI_VOICE_API_KEY: wizardData.value.agent.voiceApiKey || null, AI_VOICE_BASE_URL: wizardData.value.agent.voiceBaseUrl || null, @@ -1503,6 +1506,7 @@ export function useSetupWizard() { wizardData.value.agent.supportAudioInputOutput = Boolean(result.data.LLM_SUPPORT_AUDIO_INPUT_OUTPUT) wizardData.value.agent.apiKey = result.data.LLM_API_KEY || '' wizardData.value.agent.baseUrl = result.data.LLM_BASE_URL || '' + wizardData.value.agent.baseUrlPreset = result.data.LLM_BASE_URL_PRESET || '' wizardData.value.agent.maxContextTokens = result.data.LLM_MAX_CONTEXT_TOKENS || 64 wizardData.value.agent.voiceApiKey = result.data.AI_VOICE_API_KEY || '' wizardData.value.agent.voiceBaseUrl = result.data.AI_VOICE_BASE_URL || '' diff --git a/src/views/setting/AccountSettingSystem.vue b/src/views/setting/AccountSettingSystem.vue index e2a19072..9716f73a 100644 --- a/src/views/setting/AccountSettingSystem.vue +++ b/src/views/setting/AccountSettingSystem.vue @@ -46,6 +46,7 @@ const SystemSettings = ref({ LLM_SUPPORT_AUDIO_INPUT_OUTPUT: false, LLM_API_KEY: null, LLM_BASE_URL: 'https://api.deepseek.com', + LLM_BASE_URL_PRESET: null, AI_VOICE_API_KEY: null, AI_VOICE_BASE_URL: null, AI_VOICE_STT_MODEL: 'gpt-4o-mini-transcribe', @@ -179,6 +180,7 @@ type LlmSettingsSnapshot = { LLM_THINKING_LEVEL: string LLM_API_KEY: string LLM_BASE_URL: string + LLM_BASE_URL_PRESET: string } let llmTestRequestId = 0 @@ -205,6 +207,13 @@ const llmBaseUrlRef = computed({ }, }) +const llmBaseUrlPresetRef = computed({ + get: () => String(SystemSettings.value.Basic.LLM_BASE_URL_PRESET ?? ''), + set: value => { + SystemSettings.value.Basic.LLM_BASE_URL_PRESET = value || '' + }, +}) + const llmModelRef = computed({ get: () => String(SystemSettings.value.Basic.LLM_MODEL ?? ''), set: value => { @@ -231,6 +240,7 @@ const { showBaseUrlField, showApiKeyField, canRefreshModels, + setBaseUrlPreset, authDialogVisible, authPolling, authPopupBlocked, @@ -248,6 +258,7 @@ const { provider: llmProviderRef, apiKey: llmApiKeyRef, baseUrl: llmBaseUrlRef, + baseUrlPreset: llmBaseUrlPresetRef, model: llmModelRef, maxContextTokens: llmMaxContextRef, }) @@ -260,6 +271,7 @@ function buildLlmSnapshot(): LlmSettingsSnapshot { LLM_THINKING_LEVEL: String(SystemSettings.value.Basic.LLM_THINKING_LEVEL ?? 'off'), LLM_API_KEY: String(SystemSettings.value.Basic.LLM_API_KEY ?? ''), LLM_BASE_URL: String(SystemSettings.value.Basic.LLM_BASE_URL ?? ''), + LLM_BASE_URL_PRESET: String(SystemSettings.value.Basic.LLM_BASE_URL_PRESET ?? ''), } } @@ -275,6 +287,7 @@ function buildLlmTestPayload(snapshot: LlmSettingsSnapshot) { thinking_level: snapshot.LLM_THINKING_LEVEL.trim(), api_key: snapshot.LLM_API_KEY.trim(), base_url: snapshot.LLM_BASE_URL.trim(), + base_url_preset: snapshot.LLM_BASE_URL_PRESET.trim(), } } @@ -1016,7 +1029,11 @@ watch(currentLlmSnapshotKey, (snapshotKey, previousSnapshotKey) => { wizardData.value.agent.baseUrlPreset, + set: value => { + wizardData.value.agent.baseUrlPreset = value || '' + }, +}) + const modelRef = computed({ get: () => wizardData.value.agent.model, set: value => { @@ -63,6 +70,7 @@ const { showBaseUrlField, showApiKeyField, canRefreshModels, + setBaseUrlPreset, authDialogVisible, authPolling, authPopupBlocked, @@ -80,6 +88,7 @@ const { provider: providerRef, apiKey: apiKeyRef, baseUrl: baseUrlRef, + baseUrlPreset: baseUrlPresetRef, model: modelRef, maxContextTokens: maxContextTokensRef, authConnected: authConnectedRef, @@ -232,7 +241,11 @@ onMounted(async () => {