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
parent
b9424c12a5
commit
c4caae266a
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Reference in New Issue