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 { export interface AvailableModelsResponse {
default: string default: string
default_provider: string
groups: AvailableModelGroup[] groups: AvailableModelGroup[]
} }
@@ -30,8 +30,8 @@ function isGroupCollapsed(provider: string) {
return !!collapsedGroups.value[provider] return !!collapsedGroups.value[provider]
} }
function handleSelect(model: string) { function handleSelect(model: string, provider: string) {
appStore.switchModel(model) appStore.switchModel(model, provider)
showModal.value = false showModal.value = false
searchQuery.value = '' searchQuery.value = ''
} }
@@ -85,11 +85,11 @@ function openModal() {
v-for="model in group.models" v-for="model in group.models"
:key="model" :key="model"
class="model-item" class="model-item"
:class="{ active: model === appStore.selectedModel }" :class="{ active: model === appStore.selectedModel && group.provider === appStore.selectedProvider }"
@click="handleSelect(model)" @click="handleSelect(model, group.provider)"
> >
<span class="model-item-name">{{ model }}</span> <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" /> <polyline points="20 6 9 17 4 12" />
</svg> </svg>
</div> </div>
+4
View File
@@ -18,6 +18,7 @@ export const useAppStore = defineStore('app', () => {
const updating = ref(false) const updating = ref(false)
const modelGroups = ref<AvailableModelGroup[]>([]) const modelGroups = ref<AvailableModelGroup[]>([])
const selectedModel = ref('') const selectedModel = ref('')
const selectedProvider = ref('')
const healthPollTimer = ref<ReturnType<typeof setInterval>>() const healthPollTimer = ref<ReturnType<typeof setInterval>>()
// Settings // Settings
@@ -56,6 +57,7 @@ export const useAppStore = defineStore('app', () => {
const res = await fetchAvailableModels() const res = await fetchAvailableModels()
modelGroups.value = res.groups modelGroups.value = res.groups
selectedModel.value = res.default selectedModel.value = res.default
selectedProvider.value = res.default_provider || ''
} catch { } catch {
// ignore // ignore
} }
@@ -68,6 +70,7 @@ export const useAppStore = defineStore('app', () => {
const provider = providerOverride || group?.provider || '' const provider = providerOverride || group?.provider || ''
await updateDefaultModel({ default: modelId, provider }) await updateDefaultModel({ default: modelId, provider })
selectedModel.value = modelId selectedModel.value = modelId
selectedProvider.value = provider || ''
} catch (err: any) { } catch (err: any) {
console.error('Failed to switch model:', err) console.error('Failed to switch model:', err)
} }
@@ -117,6 +120,7 @@ export const useAppStore = defineStore('app', () => {
doUpdate, doUpdate,
modelGroups, modelGroups,
selectedModel, selectedModel,
selectedProvider,
streamEnabled, streamEnabled,
sessionPersistence, sessionPersistence,
maxTokens, maxTokens,
@@ -466,8 +466,10 @@ fsRoutes.get('/api/hermes/available-models', async (ctx) => {
const config = await readConfigYaml() const config = await readConfigYaml()
const modelSection = config.model const modelSection = config.model
let currentDefault = '' let currentDefault = ''
let currentDefaultProvider = ''
if (typeof modelSection === 'object' && modelSection !== null) { if (typeof modelSection === 'object' && modelSection !== null) {
currentDefault = String(modelSection.default || '').trim() currentDefault = String(modelSection.default || '').trim()
currentDefaultProvider = String(modelSection.provider || '').trim()
} else if (typeof modelSection === 'string') { } else if (typeof modelSection === 'string') {
currentDefault = modelSection.trim() currentDefault = modelSection.trim()
} }
@@ -592,7 +594,7 @@ fsRoutes.get('/api/hermes/available-models', async (ctx) => {
return return
} }
ctx.body = { default: currentDefault, groups: dedupedGroups } ctx.body = { default: currentDefault, default_provider: currentDefaultProvider, groups: dedupedGroups }
} catch (err: any) { } catch (err: any) {
ctx.status = 500 ctx.status = 500
ctx.body = { error: err.message } ctx.body = { error: err.message }