feat(settings): persist LLM base URL presets

This commit is contained in:
jxxghp
2026-05-08 10:52:30 +08:00
parent a9403c9c34
commit e14c81d178
4 changed files with 65 additions and 2 deletions
@@ -17,11 +17,13 @@ export interface LlmProviderAuthStatus {
} }
export interface LlmProviderUrlPreset { export interface LlmProviderUrlPreset {
id: string
label: string label: string
value: string value: string
} }
export interface LlmProviderUrlPresetItem { export interface LlmProviderUrlPresetItem {
id: string
title: string title: string
value: string value: string
subtitle?: string subtitle?: string
@@ -80,6 +82,7 @@ interface UseLlmProviderDirectoryOptions {
provider: Ref<string> provider: Ref<string>
apiKey: Ref<string> apiKey: Ref<string>
baseUrl: Ref<string> baseUrl: Ref<string>
baseUrlPreset?: Ref<string>
model: Ref<string> model: Ref<string>
maxContextTokens?: Ref<number> maxContextTokens?: Ref<number>
authConnected?: Ref<boolean> authConnected?: Ref<boolean>
@@ -110,6 +113,7 @@ export function useLlmProviderDirectory(options: UseLlmProviderDirectoryOptions)
const providerItems = computed(() => providers.value.map(item => ({ title: item.name, value: item.id }))) const providerItems = computed(() => providers.value.map(item => ({ title: item.name, value: item.id })))
const baseUrlPresetItems = computed<LlmProviderUrlPresetItem[]>(() => const baseUrlPresetItems = computed<LlmProviderUrlPresetItem[]>(() =>
(selectedProvider.value?.base_url_presets || []).map(item => ({ (selectedProvider.value?.base_url_presets || []).map(item => ({
id: item.id,
title: item.value, title: item.value,
value: item.value, value: item.value,
subtitle: item.label, subtitle: item.label,
@@ -150,14 +154,37 @@ export function useLlmProviderDirectory(options: UseLlmProviderDirectoryOptions)
const currentBaseUrl = normalizeValue(options.baseUrl.value) const currentBaseUrl = normalizeValue(options.baseUrl.value)
const defaultBaseUrl = provider.default_base_url || '' const defaultBaseUrl = provider.default_base_url || ''
const defaultPresetId = normalizeValue(provider.base_url_presets?.[0]?.id)
if (reset) { if (reset) {
options.baseUrl.value = defaultBaseUrl options.baseUrl.value = defaultBaseUrl
if (options.baseUrlPreset) {
options.baseUrlPreset.value = defaultPresetId
}
return return
} }
if (!currentBaseUrl && defaultBaseUrl) { if (!currentBaseUrl && defaultBaseUrl) {
options.baseUrl.value = 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) { function handleProviderSelection(resetBaseUrl = true) {
@@ -225,6 +252,7 @@ export function useLlmProviderDirectory(options: UseLlmProviderDirectoryOptions)
provider: normalizeValue(options.provider.value), provider: normalizeValue(options.provider.value),
api_key: normalizeValue(options.apiKey.value) || undefined, api_key: normalizeValue(options.apiKey.value) || undefined,
base_url: normalizeValue(options.baseUrl.value) || undefined, base_url: normalizeValue(options.baseUrl.value) || undefined,
base_url_preset: normalizeValue(options.baseUrlPreset?.value) || undefined,
force_refresh: forceRefresh, force_refresh: forceRefresh,
}, },
}) })
@@ -363,6 +391,7 @@ export function useLlmProviderDirectory(options: UseLlmProviderDirectoryOptions)
showApiKeyField, showApiKeyField,
hasUsableCredential, hasUsableCredential,
canRefreshModels, canRefreshModels,
setBaseUrlPreset,
authDialogVisible, authDialogVisible,
authPolling, authPolling,
authPopupBlocked, authPopupBlocked,
+4
View File
@@ -60,6 +60,7 @@ export interface WizardData {
supportAudioInputOutput: boolean supportAudioInputOutput: boolean
apiKey: string apiKey: string
baseUrl: string baseUrl: string
baseUrlPreset: string
maxContextTokens: number maxContextTokens: number
voiceApiKey: string voiceApiKey: string
voiceBaseUrl: string voiceBaseUrl: string
@@ -240,6 +241,7 @@ const wizardData = ref<WizardData>({
supportAudioInputOutput: false, supportAudioInputOutput: false,
apiKey: '', apiKey: '',
baseUrl: 'https://api.deepseek.com', baseUrl: 'https://api.deepseek.com',
baseUrlPreset: '',
maxContextTokens: 64, maxContextTokens: 64,
voiceApiKey: '', voiceApiKey: '',
voiceBaseUrl: '', voiceBaseUrl: '',
@@ -1396,6 +1398,7 @@ export function useSetupWizard() {
LLM_SUPPORT_AUDIO_INPUT_OUTPUT: wizardData.value.agent.supportAudioInputOutput, LLM_SUPPORT_AUDIO_INPUT_OUTPUT: wizardData.value.agent.supportAudioInputOutput,
LLM_API_KEY: wizardData.value.agent.apiKey, LLM_API_KEY: wizardData.value.agent.apiKey,
LLM_BASE_URL: wizardData.value.agent.baseUrl || null, 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, LLM_MAX_CONTEXT_TOKENS: wizardData.value.agent.maxContextTokens,
AI_VOICE_API_KEY: wizardData.value.agent.voiceApiKey || null, AI_VOICE_API_KEY: wizardData.value.agent.voiceApiKey || null,
AI_VOICE_BASE_URL: wizardData.value.agent.voiceBaseUrl || 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.supportAudioInputOutput = Boolean(result.data.LLM_SUPPORT_AUDIO_INPUT_OUTPUT)
wizardData.value.agent.apiKey = result.data.LLM_API_KEY || '' wizardData.value.agent.apiKey = result.data.LLM_API_KEY || ''
wizardData.value.agent.baseUrl = result.data.LLM_BASE_URL || '' 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.maxContextTokens = result.data.LLM_MAX_CONTEXT_TOKENS || 64
wizardData.value.agent.voiceApiKey = result.data.AI_VOICE_API_KEY || '' wizardData.value.agent.voiceApiKey = result.data.AI_VOICE_API_KEY || ''
wizardData.value.agent.voiceBaseUrl = result.data.AI_VOICE_BASE_URL || '' wizardData.value.agent.voiceBaseUrl = result.data.AI_VOICE_BASE_URL || ''
+18 -1
View File
@@ -46,6 +46,7 @@ const SystemSettings = ref<any>({
LLM_SUPPORT_AUDIO_INPUT_OUTPUT: false, LLM_SUPPORT_AUDIO_INPUT_OUTPUT: false,
LLM_API_KEY: null, LLM_API_KEY: null,
LLM_BASE_URL: 'https://api.deepseek.com', LLM_BASE_URL: 'https://api.deepseek.com',
LLM_BASE_URL_PRESET: null,
AI_VOICE_API_KEY: null, AI_VOICE_API_KEY: null,
AI_VOICE_BASE_URL: null, AI_VOICE_BASE_URL: null,
AI_VOICE_STT_MODEL: 'gpt-4o-mini-transcribe', AI_VOICE_STT_MODEL: 'gpt-4o-mini-transcribe',
@@ -179,6 +180,7 @@ type LlmSettingsSnapshot = {
LLM_THINKING_LEVEL: string LLM_THINKING_LEVEL: string
LLM_API_KEY: string LLM_API_KEY: string
LLM_BASE_URL: string LLM_BASE_URL: string
LLM_BASE_URL_PRESET: string
} }
let llmTestRequestId = 0 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({ const llmModelRef = computed({
get: () => String(SystemSettings.value.Basic.LLM_MODEL ?? ''), get: () => String(SystemSettings.value.Basic.LLM_MODEL ?? ''),
set: value => { set: value => {
@@ -231,6 +240,7 @@ const {
showBaseUrlField, showBaseUrlField,
showApiKeyField, showApiKeyField,
canRefreshModels, canRefreshModels,
setBaseUrlPreset,
authDialogVisible, authDialogVisible,
authPolling, authPolling,
authPopupBlocked, authPopupBlocked,
@@ -248,6 +258,7 @@ const {
provider: llmProviderRef, provider: llmProviderRef,
apiKey: llmApiKeyRef, apiKey: llmApiKeyRef,
baseUrl: llmBaseUrlRef, baseUrl: llmBaseUrlRef,
baseUrlPreset: llmBaseUrlPresetRef,
model: llmModelRef, model: llmModelRef,
maxContextTokens: llmMaxContextRef, maxContextTokens: llmMaxContextRef,
}) })
@@ -260,6 +271,7 @@ function buildLlmSnapshot(): LlmSettingsSnapshot {
LLM_THINKING_LEVEL: String(SystemSettings.value.Basic.LLM_THINKING_LEVEL ?? 'off'), LLM_THINKING_LEVEL: String(SystemSettings.value.Basic.LLM_THINKING_LEVEL ?? 'off'),
LLM_API_KEY: String(SystemSettings.value.Basic.LLM_API_KEY ?? ''), LLM_API_KEY: String(SystemSettings.value.Basic.LLM_API_KEY ?? ''),
LLM_BASE_URL: String(SystemSettings.value.Basic.LLM_BASE_URL ?? ''), 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(), thinking_level: snapshot.LLM_THINKING_LEVEL.trim(),
api_key: snapshot.LLM_API_KEY.trim(), api_key: snapshot.LLM_API_KEY.trim(),
base_url: snapshot.LLM_BASE_URL.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) => {
<VCombobox <VCombobox
:model-value="SystemSettings.Basic.LLM_BASE_URL" :model-value="SystemSettings.Basic.LLM_BASE_URL"
@update:model-value="(value: any) => { @update:model-value="(value: any) => {
SystemSettings.Basic.LLM_BASE_URL = typeof value === 'object' && value !== null ? value.value : (value || ''); if (typeof value === 'object' && value !== null) {
setBaseUrlPreset(value.id, value.value);
} else {
setBaseUrlPreset('', value || '');
}
}" }"
:label="t('setting.system.llmBaseUrl')" :label="t('setting.system.llmBaseUrl')"
:hint="t('setting.system.llmBaseUrlHint')" :hint="t('setting.system.llmBaseUrlHint')"
+14 -1
View File
@@ -30,6 +30,13 @@ const baseUrlRef = computed({
}, },
}) })
const baseUrlPresetRef = computed({
get: () => wizardData.value.agent.baseUrlPreset,
set: value => {
wizardData.value.agent.baseUrlPreset = value || ''
},
})
const modelRef = computed({ const modelRef = computed({
get: () => wizardData.value.agent.model, get: () => wizardData.value.agent.model,
set: value => { set: value => {
@@ -63,6 +70,7 @@ const {
showBaseUrlField, showBaseUrlField,
showApiKeyField, showApiKeyField,
canRefreshModels, canRefreshModels,
setBaseUrlPreset,
authDialogVisible, authDialogVisible,
authPolling, authPolling,
authPopupBlocked, authPopupBlocked,
@@ -80,6 +88,7 @@ const {
provider: providerRef, provider: providerRef,
apiKey: apiKeyRef, apiKey: apiKeyRef,
baseUrl: baseUrlRef, baseUrl: baseUrlRef,
baseUrlPreset: baseUrlPresetRef,
model: modelRef, model: modelRef,
maxContextTokens: maxContextTokensRef, maxContextTokens: maxContextTokensRef,
authConnected: authConnectedRef, authConnected: authConnectedRef,
@@ -232,7 +241,11 @@ onMounted(async () => {
<VCombobox <VCombobox
:model-value="wizardData.agent.baseUrl" :model-value="wizardData.agent.baseUrl"
@update:model-value="(value: any) => { @update:model-value="(value: any) => {
wizardData.agent.baseUrl = typeof value === 'object' && value !== null ? value.value : (value || ''); if (typeof value === 'object' && value !== null) {
setBaseUrlPreset(value.id, value.value);
} else {
setBaseUrlPreset('', value || '');
}
}" }"
:label="t('setting.system.llmBaseUrl')" :label="t('setting.system.llmBaseUrl')"
:hint="t('setting.system.llmBaseUrlHint')" :hint="t('setting.system.llmBaseUrlHint')"