Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions packages/opencode/src/cli/cmd/tui/component/dialog-model-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { map, pipe, flatMap, entries, filter, sortBy } from "remeda"

export type ModelRef = {
providerID: string
modelID: string
}

export type ModelInfo = {
id: string
name?: string
providerID?: string
status?: string
cost?: {
input?: number
}
}

export type ProviderInfo = {
id: string
name: string
models: Record<string, ModelInfo>
}

export function buildSectionOptions(input: {
items: ModelRef[]
category: string
providers: ProviderInfo[]
showSections: boolean
}) {
if (!input.showSections) return []
return input.items.flatMap((item) => {
const provider = input.providers.find((x) => x.id === item.providerID)
if (!provider) return []
const model = provider.models[item.modelID]
if (!model) return []
return [
{
key: item,
value: { providerID: provider.id, modelID: model.id, section: input.category },
title: model.name ?? item.modelID,
description: provider.name,
category: input.category,
disabled: provider.id === "opencode" && model.id.includes("-nano"),
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
},
]
})
}

export function buildProviderOptions(input: {
providers: ProviderInfo[]
favorites: ModelRef[]
connected: boolean
providerID?: string
}) {
return pipe(
input.providers,
sortBy(
(provider) => provider.id !== "opencode",
(provider) => provider.name,
),
flatMap((provider) =>
pipe(
provider.models,
entries(),
filter(([_, info]) => info.status !== "deprecated"),
filter(([_, info]) => (input.providerID ? info.providerID === input.providerID : true)),
map(([model, info]) => ({
value: { providerID: provider.id, modelID: model },
title: info.name ?? model,
description: input.favorites.some((item) => item.providerID === provider.id && item.modelID === model)
? "(Favorite)"
: undefined,
category: input.connected ? provider.name : undefined,
disabled: provider.id === "opencode" && model.includes("-nano"),
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
})),
sortBy(
(x) => x.footer !== "Free",
(x) => x.title,
),
),
),
)
}
112 changes: 40 additions & 72 deletions packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { createMemo, 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"
import { map, pipe, take } from "remeda"
import { DialogSelect } from "@tui/ui/dialog-select"
import { useDialog } from "@tui/ui/dialog"
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
import { useKeybind } from "../context/keybind"
import { buildSectionOptions, buildProviderOptions } from "./dialog-model-utils"
import * as fuzzysort from "fuzzysort"

export function useConnected() {
Expand Down Expand Up @@ -33,80 +34,46 @@ export function DialogModel(props: { providerID?: string }) {
const favorites = connected() ? local.model.favorite() : []
const recents = local.model.recent()

function toOptions(items: typeof favorites, category: string) {
if (!showSections) return []
return items.flatMap((item) => {
const provider = sync.data.provider.find((x) => x.id === item.providerID)
if (!provider) return []
const model = provider.models[item.modelID]
if (!model) return []
return [
{
key: item,
value: { providerID: provider.id, modelID: model.id },
title: model.name ?? item.modelID,
description: provider.name,
category,
disabled: provider.id === "opencode" && model.id.includes("-nano"),
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
onSelect: () => {
dialog.clear()
local.model.set({ providerID: provider.id, modelID: model.id }, { recent: true })
},
},
]
})
}
const favoriteOptions = buildSectionOptions({
items: favorites,
category: "Favorites",
providers: sync.data.provider,
showSections,
}).map((option) => ({
...option,
onSelect: () => {
dialog.clear()
local.model.set({ providerID: option.value.providerID, modelID: option.value.modelID }, { recent: true })
},
}))

const favoriteOptions = toOptions(favorites, "Favorites")
const recentOptions = toOptions(
recents.filter(
const recentOptions = buildSectionOptions({
items: recents.filter(
(item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID),
),
"Recent",
)
category: "Recent",
providers: sync.data.provider,
showSections,
}).map((option) => ({
...option,
onSelect: () => {
dialog.clear()
local.model.set({ providerID: option.value.providerID, modelID: option.value.modelID }, { recent: true })
},
}))

const providerOptions = pipe(
sync.data.provider,
sortBy(
(provider) => provider.id !== "opencode",
(provider) => provider.name,
),
flatMap((provider) =>
pipe(
provider.models,
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() ? provider.name : undefined,
disabled: provider.id === "opencode" && model.includes("-nano"),
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) => {
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
return true
}),
sortBy(
(x) => x.footer !== "Free",
(x) => x.title,
),
),
),
)
const providerOptions = buildProviderOptions({
providers: sync.data.provider,
favorites,
connected: connected(),
providerID: props.providerID,
}).map((option) => ({
...option,
onSelect() {
dialog.clear()
local.model.set(option.value, { recent: true })
},
}))

const popularProviders = !connected()
? pipe(
Expand Down Expand Up @@ -151,7 +118,8 @@ export function DialogModel(props: { providerID?: string }) {
title: "Favorite",
disabled: !connected(),
onTrigger: (option) => {
local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
const value = option.value as { providerID: string; modelID: string }
local.model.toggleFavorite({ providerID: value.providerID, modelID: value.modelID })
},
},
]}
Expand Down
Loading
Loading