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

View File

@@ -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<string>
apiKey: Ref<string>
baseUrl: Ref<string>
baseUrlPreset?: Ref<string>
model: Ref<string>
maxContextTokens?: Ref<number>
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 baseUrlPresetItems = computed<LlmProviderUrlPresetItem[]>(() =>
(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,

View File

@@ -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<WizardData>({
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 || ''

View File

@@ -46,6 +46,7 @@ const SystemSettings = ref<any>({
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) => {
<VCombobox
:model-value="SystemSettings.Basic.LLM_BASE_URL"
@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')"
:hint="t('setting.system.llmBaseUrlHint')"

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({
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 () => {
<VCombobox
:model-value="wizardData.agent.baseUrl"
@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')"
:hint="t('setting.system.llmBaseUrlHint')"