summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorDaniel Gray <[email protected]>2025-12-23 12:55:47 -0600
committerGitHub <[email protected]>2025-12-23 12:55:47 -0600
commit52048c327dfa4cb7a44d8004413e3bb9c2dd3836 (patch)
treead4db572f159cd04a2abd195d17706195a0ed96e /packages
parent4e1a9b62167f018108053e18122f12447d35139c (diff)
downloadopencode-52048c327dfa4cb7a44d8004413e3bb9c2dd3836.tar.gz
opencode-52048c327dfa4cb7a44d8004413e3bb9c2dd3836.zip
fix: favorites and recents stay visible when filtering models (#6053)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx257
-rw-r--r--packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx4
2 files changed, 135 insertions, 126 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx
index 38fd57458..fc0559cd6 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-model.tsx
@@ -6,6 +6,7 @@ import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
import { useDialog } from "@tui/ui/dialog"
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
import { Keybind } from "@/util/keybind"
+import * as fuzzysort from "fuzzysort"
export function useConnected() {
const sync = useSync()
@@ -19,6 +20,7 @@ export function DialogModel(props: { providerID?: string }) {
const sync = useSync()
const dialog = useDialog()
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
+ const [query, setQuery] = createSignal("")
const connected = useConnected()
const providers = createDialogProviderOptions()
@@ -30,7 +32,7 @@ export function DialogModel(props: { providerID?: string }) {
})
const options = createMemo(() => {
- const query = ref()?.filter
+ const q = query()
const favorites = showExtra() ? local.model.favorite() : []
const recents = local.model.recent()
@@ -42,148 +44,151 @@ export function DialogModel(props: { providerID?: string }) {
.slice(0, 5)
: []
- const favoriteOptions = !query
- ? favorites.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: {
+ const favoriteOptions = favorites.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: "Favorites",
+ 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,
},
- title: model.name ?? item.modelID,
- description: provider.name,
- category: "Favorites",
- 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 },
- )
- },
- },
- ]
- })
- : []
+ { recent: true },
+ )
+ },
+ },
+ ]
+ })
- const recentOptions = !query
- ? recentList.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: {
+ const recentOptions = recentList.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: "Recent",
+ 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,
},
- title: model.name ?? item.modelID,
- description: provider.name,
- category: "Recent",
- disabled: provider.id === "opencode" && model.id.includes("-nano"),
- footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
- onSelect: () => {
+ { 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]) => {
+ const value = {
+ providerID: provider.id,
+ modelID: model,
+ }
+ return {
+ value,
+ title: info.name ?? model,
+ description: favorites.some(
+ (item) => item.providerID === value.providerID && item.modelID === value.modelID,
+ )
+ ? "(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.id,
+ modelID: model,
},
{ recent: true },
)
},
- },
- ]
- })
- : []
-
- return [
- ...favoriteOptions,
- ...recentOptions,
- ...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]) => {
- const value = {
- providerID: provider.id,
- modelID: model,
- }
- return {
- value,
- title: info.name ?? model,
- description: favorites.some(
- (item) => item.providerID === value.providerID && item.modelID === value.modelID,
- )
- ? "(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 (query) return true
- const value = x.value
- const inFavorites = favorites.some(
- (item) => item.providerID === value.providerID && item.modelID === value.modelID,
- )
- if (inFavorites) return false
- const inRecents = recents.some(
- (item) => item.providerID === value.providerID && item.modelID === value.modelID,
- )
- if (inRecents) return false
- return true
- }),
- sortBy(
- (x) => x.footer !== "Free",
- (x) => x.title,
- ),
+ }
+ }),
+ filter((x) => {
+ const value = x.value
+ const inFavorites = favorites.some(
+ (item) => item.providerID === value.providerID && item.modelID === value.modelID,
+ )
+ if (inFavorites) return false
+ const inRecents = recents.some(
+ (item) => item.providerID === value.providerID && item.modelID === value.modelID,
+ )
+ if (inRecents) return false
+ return true
+ }),
+ sortBy(
+ (x) => x.footer !== "Free",
+ (x) => x.title,
),
),
),
- ...(!connected()
- ? pipe(
- providers(),
- map((option) => {
- return {
- ...option,
- category: "Popular providers",
- }
- }),
- take(6),
- )
- : []),
- ]
+ )
+
+ const popularProviders = !connected()
+ ? pipe(
+ providers(),
+ map((option) => {
+ return {
+ ...option,
+ category: "Popular providers",
+ }
+ }),
+ take(6),
+ )
+ : []
+
+ // Apply fuzzy filtering to each section separately, maintaining section order
+ if (q) {
+ const filteredFavorites = fuzzysort.go(q, favoriteOptions, { keys: ["title"] }).map((x) => x.obj)
+ const filteredRecents = fuzzysort.go(q, recentOptions, { keys: ["title"] }).map((x) => x.obj)
+ const filteredProviders = fuzzysort.go(q, providerOptions, { keys: ["title", "category"] }).map((x) => x.obj)
+ const filteredPopular = fuzzysort.go(q, popularProviders, { keys: ["title"] }).map((x) => x.obj)
+ return [...filteredFavorites, ...filteredRecents, ...filteredProviders, ...filteredPopular]
+ }
+
+ return [...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders]
})
const provider = createMemo(() =>
@@ -215,6 +220,8 @@ export function DialogModel(props: { providerID?: string }) {
},
]}
ref={setRef}
+ onFilter={setQuery}
+ skipFilter={true}
title={title()}
current={local.model.current()}
options={options()}
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
index ff9745b90..1e764d66b 100644
--- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
+++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx
@@ -19,6 +19,7 @@ export interface DialogSelectProps<T> {
onMove?: (option: DialogSelectOption<T>) => void
onFilter?: (query: string) => void
onSelect?: (option: DialogSelectOption<T>) => void
+ skipFilter?: boolean
keybind?: {
keybind: Keybind.Info
title: string
@@ -74,7 +75,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
const result = pipe(
props.options,
filter((x) => x.disabled !== true),
- (x) => (!needle ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj)),
+ (x) =>
+ !needle || props.skipFilter ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj),
)
return result
})