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:
@@ -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>
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
Reference in New Issue
Block a user