fix: provider-aware model selection to prevent cross-provider conflicts

When multiple providers share the same model name, the selector now
uses both model ID and provider as the unique identifier instead of
model name alone. Backend returns default_provider alongside default
model, and model switching sends provider to the config.

Fixes #52

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-19 15:05:05 +08:00
parent b4f809d8b5
commit 8a4ab2d69d
4 changed files with 13 additions and 6 deletions
+1
View File
@@ -33,6 +33,7 @@ export interface AvailableModelGroup {
export interface AvailableModelsResponse {
default: string
default_provider: string
groups: AvailableModelGroup[]
}
@@ -30,8 +30,8 @@ function isGroupCollapsed(provider: string) {
return !!collapsedGroups.value[provider]
}
function handleSelect(model: string) {
appStore.switchModel(model)
function handleSelect(model: string, provider: string) {
appStore.switchModel(model, provider)
showModal.value = false
searchQuery.value = ''
}
@@ -85,11 +85,11 @@ function openModal() {
v-for="model in group.models"
:key="model"
class="model-item"
:class="{ active: model === appStore.selectedModel }"
@click="handleSelect(model)"
:class="{ active: model === appStore.selectedModel && group.provider === appStore.selectedProvider }"
@click="handleSelect(model, group.provider)"
>
<span class="model-item-name">{{ model }}</span>
<svg v-if="model === appStore.selectedModel" class="model-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<svg v-if="model === appStore.selectedModel && group.provider === appStore.selectedProvider" class="model-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12" />
</svg>
</div>
+4
View File
@@ -18,6 +18,7 @@ export const useAppStore = defineStore('app', () => {
const updating = ref(false)
const modelGroups = ref<AvailableModelGroup[]>([])
const selectedModel = ref('')
const selectedProvider = ref('')
const healthPollTimer = ref<ReturnType<typeof setInterval>>()
// Settings
@@ -56,6 +57,7 @@ export const useAppStore = defineStore('app', () => {
const res = await fetchAvailableModels()
modelGroups.value = res.groups
selectedModel.value = res.default
selectedProvider.value = res.default_provider || ''
} catch {
// ignore
}
@@ -68,6 +70,7 @@ export const useAppStore = defineStore('app', () => {
const provider = providerOverride || group?.provider || ''
await updateDefaultModel({ default: modelId, provider })
selectedModel.value = modelId
selectedProvider.value = provider || ''
} catch (err: any) {
console.error('Failed to switch model:', err)
}
@@ -117,6 +120,7 @@ export const useAppStore = defineStore('app', () => {
doUpdate,
modelGroups,
selectedModel,
selectedProvider,
streamEnabled,
sessionPersistence,
maxTokens,
@@ -466,8 +466,10 @@ fsRoutes.get('/api/hermes/available-models', async (ctx) => {
const config = await readConfigYaml()
const modelSection = config.model
let currentDefault = ''
let currentDefaultProvider = ''
if (typeof modelSection === 'object' && modelSection !== null) {
currentDefault = String(modelSection.default || '').trim()
currentDefaultProvider = String(modelSection.provider || '').trim()
} else if (typeof modelSection === 'string') {
currentDefault = modelSection.trim()
}
@@ -592,7 +594,7 @@ fsRoutes.get('/api/hermes/available-models', async (ctx) => {
return
}
ctx.body = { default: currentDefault, groups: dedupedGroups }
ctx.body = { default: currentDefault, default_provider: currentDefaultProvider, groups: dedupedGroups }
} catch (err: any) {
ctx.status = 500
ctx.body = { error: err.message }