mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
tui: streamline model selection and provider discovery
- Show unconnected providers in the list so users can find and connect new services without leaving the workflow. - Group recently used models at the top for faster access to common configurations. - Add a 'Free' badge to opencode models to clearly identify no-cost options. - Adjust dialog sizing to improve readability of the expanded model list.
This commit is contained in:
parent
54f48ec66b
commit
1bebe26454
4 changed files with 64 additions and 18 deletions
|
|
@ -2,7 +2,7 @@ import { createMemo, createSignal } from "solid-js"
|
|||
import { useLocal } from "@tui/context/local"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { map, pipe, flatMap, entries, filter, isDeepEqual, sortBy } from "remeda"
|
||||
import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
|
||||
import { DialogSelect, type DialogSelectOption, type DialogSelectRef } from "@tui/ui/dialog-select"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { useTheme } from "../context/theme"
|
||||
|
||||
|
|
@ -10,6 +10,15 @@ function Free() {
|
|||
const { theme } = useTheme()
|
||||
return <span style={{ fg: theme.secondary }}>Free</span>
|
||||
}
|
||||
const PROVIDER_PRIORITY: Record<string, number> = {
|
||||
opencode: 0,
|
||||
anthropic: 1,
|
||||
"github-copilot": 2,
|
||||
openai: 3,
|
||||
google: 4,
|
||||
openrouter: 5,
|
||||
vercel: 6,
|
||||
}
|
||||
|
||||
export function DialogModel() {
|
||||
const local = useLocal()
|
||||
|
|
@ -17,9 +26,15 @@ export function DialogModel() {
|
|||
const dialog = useDialog()
|
||||
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
|
||||
|
||||
const connected = createMemo(() =>
|
||||
sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
|
||||
)
|
||||
|
||||
const showRecent = createMemo(() => !ref()?.filter && local.model.recent().length > 0 && connected())
|
||||
|
||||
const options = createMemo(() => {
|
||||
return [
|
||||
...(!ref()?.filter
|
||||
...(showRecent()
|
||||
? local.model.recent().flatMap((item) => {
|
||||
const provider = sync.data.provider.find((x) => x.id === item.providerID)!
|
||||
if (!provider) return []
|
||||
|
|
@ -36,6 +51,16 @@ export function DialogModel() {
|
|||
description: provider.name,
|
||||
category: "Recent",
|
||||
footer: model.cost?.input === 0 && provider.id === "opencode" ? <Free /> : undefined,
|
||||
onSelect: () => {
|
||||
dialog.clear()
|
||||
local.model.set(
|
||||
{
|
||||
providerID: provider.id,
|
||||
modelID: model.id,
|
||||
},
|
||||
{ recent: true },
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
|
@ -57,27 +82,37 @@ export function DialogModel() {
|
|||
},
|
||||
title: info.name ?? model,
|
||||
description: provider.name,
|
||||
category: provider.name,
|
||||
category: connected() ? provider.name : undefined,
|
||||
footer: info.cost?.input === 0 && provider.id === "opencode" ? <Free /> : undefined,
|
||||
onSelect() {
|
||||
dialog.clear()
|
||||
local.model.set(
|
||||
{
|
||||
providerID: provider.id,
|
||||
modelID: model,
|
||||
},
|
||||
{ recent: true },
|
||||
)
|
||||
},
|
||||
})),
|
||||
filter((x) => Boolean(ref()?.filter) || !local.model.recent().find((y) => isDeepEqual(y, x.value))),
|
||||
filter((x) => !showRecent() || !local.model.recent().find((y) => isDeepEqual(y, x.value))),
|
||||
sortBy((x) => x.title),
|
||||
),
|
||||
),
|
||||
),
|
||||
...(!connected()
|
||||
? pipe(
|
||||
sync.data.provider_next.all,
|
||||
map((provider) => ({
|
||||
title: provider.name,
|
||||
category: "Connect a provider",
|
||||
value: provider.id,
|
||||
})),
|
||||
sortBy((x) => PROVIDER_PRIORITY[x.value] ?? 99),
|
||||
)
|
||||
: []),
|
||||
]
|
||||
})
|
||||
|
||||
return (
|
||||
<DialogSelect
|
||||
ref={setRef}
|
||||
title="Select model"
|
||||
current={local.model.current()}
|
||||
options={options()}
|
||||
onSelect={(option) => {
|
||||
dialog.clear()
|
||||
local.model.set(option.value, { recent: true })
|
||||
}}
|
||||
/>
|
||||
)
|
||||
return <DialogSelect ref={setRef} title="Select model" current={local.model.current()} options={options()} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
|||
|
||||
sdk.event.subscribe().then(async (events) => {
|
||||
for await (const event of events.stream) {
|
||||
console.log("event", event.type)
|
||||
emitter.emit(event.type, event)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import type {
|
|||
McpStatus,
|
||||
FormatterStatus,
|
||||
SessionStatus,
|
||||
ProviderListResponse,
|
||||
} from "@opencode-ai/sdk"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
|
|
@ -28,6 +29,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||
status: "loading" | "partial" | "complete"
|
||||
provider: Provider[]
|
||||
provider_default: Record<string, string>
|
||||
provider_next: ProviderListResponse
|
||||
agent: Agent[]
|
||||
command: Command[]
|
||||
permission: {
|
||||
|
|
@ -56,6 +58,11 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||
}
|
||||
formatter: FormatterStatus[]
|
||||
}>({
|
||||
provider_next: {
|
||||
all: [],
|
||||
default: {},
|
||||
connected: [],
|
||||
},
|
||||
config: {},
|
||||
status: "loading",
|
||||
agent: [],
|
||||
|
|
@ -241,6 +248,11 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||
setStore("provider_default", x.data!.default)
|
||||
})
|
||||
}),
|
||||
sdk.client.provider.list({ throwOnError: true }).then((x) => {
|
||||
batch(() => {
|
||||
setStore("provider_next", x.data!)
|
||||
})
|
||||
}),
|
||||
sdk.client.app.agents({ throwOnError: true }).then((x) => setStore("agent", x.data ?? [])),
|
||||
sdk.client.config.get({ throwOnError: true }).then((x) => setStore("config", x.data!)),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function DialogPrompt(props: DialogPromptProps) {
|
|||
})
|
||||
|
||||
onMount(() => {
|
||||
dialog.setSize("large")
|
||||
dialog.setSize("medium")
|
||||
setTimeout(() => {
|
||||
textarea.focus()
|
||||
}, 1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue