diff --git a/mateclaw-ui/src/views/Agents.vue b/mateclaw-ui/src/views/Agents.vue index f038dbbe..242ed5b6 100644 --- a/mateclaw-ui/src/views/Agents.vue +++ b/mateclaw-ui/src/views/Agents.vue @@ -606,8 +606,15 @@ {{ idx + 1 }} {{ providerNameById(pref.providerId) }} @@ -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 }} @@ -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) {