mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 03:55:09 +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-rank">{{ idx + 1 }}</span>
|
||||||
<span class="provider-pref-name">{{ providerNameById(pref.providerId) }}</span>
|
<span class="provider-pref-name">{{ providerNameById(pref.providerId) }}</span>
|
||||||
<select class="provider-pref-model" v-model="pref.modelId">
|
<select class="provider-pref-model" v-model="pref.modelId">
|
||||||
<option :value="null">{{ t('agents.binding.providerDefaultModel') }}</option>
|
<option :value="null" :disabled="isProviderChoiceTaken(pref.providerId, null, idx)">
|
||||||
<option v-for="m in modelsForProvider(pref.providerId)" :key="m.id" :value="m.id">
|
{{ 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 }}
|
{{ m.modelName }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
@ -625,6 +632,7 @@
|
|||||||
v-for="p in availableProviders"
|
v-for="p in availableProviders"
|
||||||
:key="p.id"
|
:key="p.id"
|
||||||
class="provider-pref-add-btn"
|
class="provider-pref-add-btn"
|
||||||
|
:disabled="!hasFreeProviderChoice(p.id)"
|
||||||
@click="addProviderEntry(p.id)"
|
@click="addProviderEntry(p.id)"
|
||||||
>+ {{ p.name }}</button>
|
>+ {{ p.name }}</button>
|
||||||
</div>
|
</div>
|
||||||
@ -1251,10 +1259,38 @@ function modelsForProvider(providerId: string) {
|
|||||||
return availableModels.value.filter(m => m.provider === providerId)
|
return availableModels.value.filter(m => m.provider === providerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append a new chain entry (defaults to the provider's default model). The same
|
// Whether a specific (provider, model) — or the provider's default slot when
|
||||||
// provider may appear more than once, so we never dedup here.
|
// 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) {
|
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) {
|
function removeProviderEntry(idx: number) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user