mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(agents): keep each (provider, model) unique in the preference chain
In the "Edit Agent → Preferred Providers" tab the same (provider, model) combination could be selected repeatedly — e.g. two rows of the same provider both pointing at the same model, or two "provider default" rows. This is unintended: a provider may repeat across the fallback chain, but each (provider, model) should stay unique. Root cause: addProviderEntry() pushed unconditionally (the code comment even said "we never dedup here") and the model <option>s had no disabled state, so already-chosen models remained selectable. Fix (Agents.vue): - isProviderChoiceTaken(): detect whether a (provider, model) slot — or the provider-default slot (modelId === null) — is already used in another row. - Model <option> + the default-model option are :disabled when already taken; the current row's own value stays selectable (exceptIdx). - addProviderEntry(): take the default slot if free, else the first unused model; do nothing if every option is taken. - The "+ Provider" pool button is disabled once the provider has no free (provider, model) slot left, so the click is never a silent no-op. The existing unique index uk_agent_provider_model(agent_id, provider_id, model_id) already guards non-null duplicates at the DB level, but it cannot catch model_id IS NULL rows (SQL treats NULLs as distinct); the UI is now the single source of truth for that. Fixes #530
This commit is contained in:
parent
97a040aa89
commit
fbfb8982cc
@ -606,8 +606,15 @@
|
||||
<span class="provider-pref-rank">{{ idx + 1 }}</span>
|
||||
<span class="provider-pref-name">{{ providerNameById(pref.providerId) }}</span>
|
||||
<select class="provider-pref-model" v-model="pref.modelId">
|
||||
<option :value="null">{{ t('agents.binding.providerDefaultModel') }}</option>
|
||||
<option v-for="m in modelsForProvider(pref.providerId)" :key="m.id" :value="m.id">
|
||||
<option :value="null" :disabled="isProviderChoiceTaken(pref.providerId, null, idx)">
|
||||
{{ t('agents.binding.providerDefaultModel') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="m in modelsForProvider(pref.providerId)"
|
||||
:key="m.id"
|
||||
:value="m.id"
|
||||
:disabled="isProviderChoiceTaken(pref.providerId, m.id, idx)"
|
||||
>
|
||||
{{ m.modelName }}
|
||||
</option>
|
||||
</select>
|
||||
@ -625,6 +632,7 @@
|
||||
v-for="p in availableProviders"
|
||||
:key="p.id"
|
||||
class="provider-pref-add-btn"
|
||||
:disabled="!hasFreeProviderChoice(p.id)"
|
||||
@click="addProviderEntry(p.id)"
|
||||
>+ {{ p.name }}</button>
|
||||
</div>
|
||||
@ -1251,10 +1259,38 @@ function modelsForProvider(providerId: string) {
|
||||
return availableModels.value.filter(m => m.provider === providerId)
|
||||
}
|
||||
|
||||
// Append a new chain entry (defaults to the provider's default model). The same
|
||||
// provider may appear more than once, so we never dedup here.
|
||||
// Whether a specific (provider, model) — or the provider's default slot when
|
||||
// modelId === null — is already chosen in another row. Keeps each
|
||||
// (provider, model) unique across the chain. `exceptIdx` excludes one row
|
||||
// (pass the current row's index so its own value stays selectable); pass -1
|
||||
// to check against every row (used when appending).
|
||||
function isProviderChoiceTaken(providerId: string, modelId: string | null, exceptIdx: number): boolean {
|
||||
return selectedProviderPrefs.value.some(
|
||||
(pref, i) => i !== exceptIdx && pref.providerId === providerId && pref.modelId === modelId,
|
||||
)
|
||||
}
|
||||
|
||||
// Whether the provider still has any free (provider, model) slot to add.
|
||||
function hasFreeProviderChoice(providerId: string): boolean {
|
||||
if (!isProviderChoiceTaken(providerId, null, -1)) return true
|
||||
return modelsForProvider(providerId).some(m => !isProviderChoiceTaken(providerId, m.id, -1))
|
||||
}
|
||||
|
||||
// Append a new chain entry. The same provider may repeat across the chain,
|
||||
// but each (provider, model) must stay unique: take the default slot if it is
|
||||
// still free, otherwise the first unused model. If every option is already
|
||||
// picked there is nothing to add.
|
||||
function addProviderEntry(providerId: string) {
|
||||
selectedProviderPrefs.value.push({ providerId, modelId: null })
|
||||
if (!isProviderChoiceTaken(providerId, null, -1)) {
|
||||
selectedProviderPrefs.value.push({ providerId, modelId: null })
|
||||
return
|
||||
}
|
||||
const firstFree = modelsForProvider(providerId).find(
|
||||
m => !isProviderChoiceTaken(providerId, m.id, -1),
|
||||
)
|
||||
if (firstFree) {
|
||||
selectedProviderPrefs.value.push({ providerId, modelId: firstFree.id })
|
||||
}
|
||||
}
|
||||
|
||||
function removeProviderEntry(idx: number) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user