|
|
@@ -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()}
|