feat(tui): show model choices per auth profile in switch model dialog

Expand Switch Model entries by configured auth profiles per provider and preserve authProfile in recent/favorites selection keys so profile-specific model picks remain distinct.
pull/21353/head
PabloGNU 2026-04-07 17:58:05 +02:00
parent b9424c12a5
commit c4caae266a
2 changed files with 79 additions and 39 deletions

View File

@ -1,4 +1,4 @@
import { createMemo, createSignal } from "solid-js"
import { createMemo, createResource, createSignal } from "solid-js"
import { useLocal } from "@tui/context/local"
import { useSync } from "@tui/context/sync"
import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda"
@ -9,6 +9,7 @@ import { DialogVariant } from "./dialog-variant"
import { useKeybind } from "../context/keybind"
import * as fuzzysort from "fuzzysort"
import { consoleManagedProviderLabel } from "@tui/util/provider-origin"
import { Auth } from "@/auth"
export function useConnected() {
const sync = useSync()
@ -26,6 +27,7 @@ export function DialogModel(props: { providerID?: string }) {
const connected = useConnected()
const providers = createDialogProviderOptions()
const [auth] = createResource(async () => Auth.all())
const showExtra = createMemo(() => connected() && !props.providerID)
@ -35,6 +37,22 @@ export function DialogModel(props: { providerID?: string }) {
const favorites = connected() ? local.model.favorite() : []
const recents = local.model.recent()
const keyOf = (item: { providerID: string; modelID: string; authProfile?: string }) =>
`${item.providerID}/${item.modelID}/${item.authProfile ?? "default"}`
const profileMap = createMemo(() => {
const data = auth() ?? {}
const out = new Map<string, Set<string | undefined>>()
for (const key of Object.keys(data)) {
const idx = key.lastIndexOf(":")
const providerID = idx === -1 ? key : key.slice(0, idx)
const profile = idx === -1 ? undefined : key.slice(idx + 1)
if (!out.has(providerID)) out.set(providerID, new Set())
out.get(providerID)!.add(profile)
}
return out
})
function toOptions(items: typeof favorites, category: string) {
if (!showSections) return []
return items.flatMap((item) => {
@ -45,7 +63,7 @@ export function DialogModel(props: { providerID?: string }) {
return [
{
key: item,
value: { providerID: provider.id, modelID: model.id },
value: { providerID: provider.id, modelID: model.id, authProfile: item.authProfile },
title: model.name ?? item.modelID,
description: consoleManagedProviderLabel(
sync.data.console_state.consoleManagedProviders,
@ -56,7 +74,7 @@ export function DialogModel(props: { providerID?: string }) {
disabled: provider.id === "opencode" && model.id.includes("-nano"),
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
onSelect: () => {
onSelect(provider.id, model.id)
onSelect(provider.id, model.id, item.authProfile)
},
},
]
@ -65,9 +83,7 @@ export function DialogModel(props: { providerID?: string }) {
const favoriteOptions = toOptions(favorites, "Favorites")
const recentOptions = toOptions(
recents.filter(
(item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID),
),
recents.filter((item) => !favorites.some((fav) => keyOf(fav) === keyOf(item))),
"Recent",
)
@ -83,27 +99,29 @@ export function DialogModel(props: { providerID?: string }) {
entries(),
filter(([_, info]) => info.status !== "deprecated"),
filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)),
map(([model, info]) => ({
value: { providerID: provider.id, modelID: model },
title: info.name ?? model,
description: favorites.some((item) => item.providerID === provider.id && item.modelID === model)
? "(Favorite)"
: undefined,
category: connected()
? consoleManagedProviderLabel(sync.data.console_state.consoleManagedProviders, provider.id, provider.name)
: undefined,
disabled: provider.id === "opencode" && model.includes("-nano"),
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
onSelect() {
onSelect(provider.id, model)
},
})),
flatMap(([model, info]) => {
const profiles = [...(profileMap().get(provider.id) ?? new Set([undefined]))]
return profiles.map((authProfile) => {
const value = { providerID: provider.id, modelID: model, authProfile }
return {
value,
title: info.name ?? model,
description: favorites.some((item) => keyOf(item) === keyOf(value)) ? "(Favorite)" : undefined,
category: connected()
? `${consoleManagedProviderLabel(sync.data.console_state.consoleManagedProviders, provider.id, provider.name)}${authProfile ? `:${authProfile}` : ""}`
: undefined,
disabled: provider.id === "opencode" && model.includes("-nano"),
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
onSelect() {
onSelect(provider.id, model, authProfile)
},
}
})
}),
filter((x) => {
if (!showSections) return true
if (favorites.some((item) => item.providerID === x.value.providerID && item.modelID === x.value.modelID))
return false
if (recents.some((item) => item.providerID === x.value.providerID && item.modelID === x.value.modelID))
return false
if (favorites.some((item) => keyOf(item) === keyOf(x.value))) return false
if (recents.some((item) => keyOf(item) === keyOf(x.value))) return false
return true
}),
sortBy(
@ -145,8 +163,8 @@ export function DialogModel(props: { providerID?: string }) {
return consoleManagedProviderLabel(sync.data.console_state.consoleManagedProviders, value.id, value.name)
})
function onSelect(providerID: string, modelID: string) {
local.model.set({ providerID, modelID }, { recent: true })
function onSelect(providerID: string, modelID: string, authProfile?: string) {
local.model.set({ providerID, modelID, authProfile }, { recent: true })
const list = local.model.variant.list()
const cur = local.model.variant.selected()
if (cur === "default" || (cur && list.includes(cur))) {
@ -176,7 +194,7 @@ export function DialogModel(props: { providerID?: string }) {
title: "Favorite",
disabled: !connected(),
onTrigger: (option) => {
local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
local.model.toggleFavorite(option.value as { providerID: string; modelID: string; authProfile?: string })
},
},
]}
@ -184,7 +202,7 @@ export function DialogModel(props: { providerID?: string }) {
flat={true}
skipFilter={true}
title={title()}
current={local.model.current()}
current={local.model.current() as any}
/>
)
}

View File

@ -243,7 +243,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const current = currentModel()
if (!current) return
const recent = modelStore.recent
const index = recent.findIndex((x) => x.providerID === current.providerID && x.modelID === current.modelID)
const index = recent.findIndex(
(x) =>
x.providerID === current.providerID &&
x.modelID === current.modelID &&
x.authProfile === current.authProfile,
)
if (index === -1) return
let next = index + direction
if (next < 0) next = recent.length - 1
@ -265,7 +270,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const current = currentModel()
let index = -1
if (current) {
index = favorites.findIndex((x) => x.providerID === current.providerID && x.modelID === current.modelID)
index = favorites.findIndex(
(x) =>
x.providerID === current.providerID &&
x.modelID === current.modelID &&
x.authProfile === current.authProfile,
)
}
if (index === -1) {
index = direction === 1 ? 0 : favorites.length - 1
@ -277,11 +287,14 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const next = favorites[index]
if (!next) return
setModelStore("model", agent.current().name, { ...next })
const uniq = uniqueBy([next, ...modelStore.recent], (x) => `${x.providerID}/${x.modelID}`)
const uniq = uniqueBy(
[next, ...modelStore.recent],
(x) => `${x.providerID}/${x.modelID}/${x.authProfile ?? "default"}`,
)
if (uniq.length > 10) uniq.pop()
setModelStore(
"recent",
uniq.map((x) => ({ providerID: x.providerID, modelID: x.modelID })),
uniq.map((x) => ({ providerID: x.providerID, modelID: x.modelID, authProfile: x.authProfile })),
)
save()
},
@ -297,7 +310,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
setModelStore("model", agent.current().name, model)
if (options?.recent) {
const uniq = uniqueBy([model, ...modelStore.recent], (x) => `${x.providerID}/${x.modelID}`)
const uniq = uniqueBy(
[model, ...modelStore.recent],
(x) => `${x.providerID}/${x.modelID}/${x.authProfile ?? "default"}`,
)
if (uniq.length > 10) uniq.pop()
setModelStore(
"recent",
@ -307,7 +323,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
})
},
toggleFavorite(model: { providerID: string; modelID: string }) {
toggleFavorite(model: { providerID: string; modelID: string; authProfile?: string }) {
batch(() => {
if (!isModelValid(model)) {
toast.show({
@ -318,14 +334,20 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
return
}
const exists = modelStore.favorite.some(
(x) => x.providerID === model.providerID && x.modelID === model.modelID,
(x) =>
x.providerID === model.providerID && x.modelID === model.modelID && x.authProfile === model.authProfile,
)
const next = exists
? modelStore.favorite.filter((x) => x.providerID !== model.providerID || x.modelID !== model.modelID)
? modelStore.favorite.filter(
(x) =>
x.providerID !== model.providerID ||
x.modelID !== model.modelID ||
x.authProfile !== model.authProfile,
)
: [model, ...modelStore.favorite]
setModelStore(
"favorite",
next.map((x) => ({ providerID: x.providerID, modelID: x.modelID })),
next.map((x) => ({ providerID: x.providerID, modelID: x.modelID, authProfile: x.authProfile })),
)
save()
})
@ -402,7 +424,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
model.set({
providerID: value.model.providerID,
modelID: value.model.modelID,
authProfile: value.model.authProfile,
authProfile: (value.model as { authProfile?: string }).authProfile,
})
else
toast.show({