refactor: clean up dialog-model.tsx per code review (#12983)
This commit is contained in:
@@ -2,7 +2,7 @@ import { createMemo, createSignal } from "solid-js"
|
|||||||
import { useLocal } from "@tui/context/local"
|
import { useLocal } from "@tui/context/local"
|
||||||
import { useSync } from "@tui/context/sync"
|
import { useSync } from "@tui/context/sync"
|
||||||
import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda"
|
import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda"
|
||||||
import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
|
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||||
import { useDialog } from "@tui/ui/dialog"
|
import { useDialog } from "@tui/ui/dialog"
|
||||||
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
|
import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
|
||||||
import { useKeybind } from "../context/keybind"
|
import { useKeybind } from "../context/keybind"
|
||||||
@@ -20,96 +20,51 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const keybind = useKeybind()
|
const keybind = useKeybind()
|
||||||
const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
|
|
||||||
const [query, setQuery] = createSignal("")
|
const [query, setQuery] = createSignal("")
|
||||||
|
|
||||||
const connected = useConnected()
|
const connected = useConnected()
|
||||||
const providers = createDialogProviderOptions()
|
const providers = createDialogProviderOptions()
|
||||||
|
|
||||||
const showExtra = createMemo(() => {
|
const showExtra = createMemo(() => connected() && !props.providerID)
|
||||||
if (!connected()) return false
|
|
||||||
if (props.providerID) return false
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
const options = createMemo(() => {
|
const options = createMemo(() => {
|
||||||
const q = query()
|
const needle = query().trim()
|
||||||
const needle = q.trim()
|
|
||||||
const showSections = showExtra() && needle.length === 0
|
const showSections = showExtra() && needle.length === 0
|
||||||
const favorites = connected() ? local.model.favorite() : []
|
const favorites = connected() ? local.model.favorite() : []
|
||||||
const recents = local.model.recent()
|
const recents = local.model.recent()
|
||||||
|
|
||||||
const recentList = showSections
|
function toOptions(items: typeof favorites, category: string) {
|
||||||
? recents.filter(
|
if (!showSections) return []
|
||||||
(item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID),
|
return items.flatMap((item) => {
|
||||||
)
|
const provider = sync.data.provider.find((x) => x.id === item.providerID)
|
||||||
: []
|
if (!provider) return []
|
||||||
|
const model = provider.models[item.modelID]
|
||||||
const favoriteOptions = showSections
|
if (!model) return []
|
||||||
? favorites.flatMap((item) => {
|
return [
|
||||||
const provider = sync.data.provider.find((x) => x.id === item.providerID)
|
{
|
||||||
if (!provider) return []
|
key: item,
|
||||||
const model = provider.models[item.modelID]
|
value: { providerID: provider.id, modelID: model.id },
|
||||||
if (!model) return []
|
title: model.name ?? item.modelID,
|
||||||
return [
|
description: provider.name,
|
||||||
{
|
category,
|
||||||
key: item,
|
disabled: provider.id === "opencode" && model.id.includes("-nano"),
|
||||||
value: {
|
footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
|
||||||
providerID: provider.id,
|
onSelect: () => {
|
||||||
modelID: model.id,
|
dialog.clear()
|
||||||
},
|
local.model.set({ providerID: provider.id, modelID: model.id }, { recent: true })
|
||||||
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 },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
})
|
]
|
||||||
: []
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const recentOptions = showSections
|
const favoriteOptions = toOptions(favorites, "Favorites")
|
||||||
? recentList.flatMap((item) => {
|
const recentOptions = toOptions(
|
||||||
const provider = sync.data.provider.find((x) => x.id === item.providerID)
|
recents.filter(
|
||||||
if (!provider) return []
|
(item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID),
|
||||||
const model = provider.models[item.modelID]
|
),
|
||||||
if (!model) return []
|
"Recent",
|
||||||
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,
|
|
||||||
},
|
|
||||||
{ recent: true },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
|
||||||
: []
|
|
||||||
|
|
||||||
const providerOptions = pipe(
|
const providerOptions = pipe(
|
||||||
sync.data.provider,
|
sync.data.provider,
|
||||||
@@ -123,45 +78,26 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
entries(),
|
entries(),
|
||||||
filter(([_, info]) => info.status !== "deprecated"),
|
filter(([_, info]) => info.status !== "deprecated"),
|
||||||
filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)),
|
filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)),
|
||||||
map(([model, info]) => {
|
map(([model, info]) => ({
|
||||||
const value = {
|
value: { providerID: provider.id, modelID: model },
|
||||||
providerID: provider.id,
|
title: info.name ?? model,
|
||||||
modelID: model,
|
description: favorites.some((item) => item.providerID === provider.id && item.modelID === model)
|
||||||
}
|
? "(Favorite)"
|
||||||
return {
|
: undefined,
|
||||||
value,
|
category: connected() ? provider.name : undefined,
|
||||||
title: info.name ?? model,
|
disabled: provider.id === "opencode" && model.includes("-nano"),
|
||||||
description: favorites.some(
|
footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
|
||||||
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
onSelect() {
|
||||||
)
|
dialog.clear()
|
||||||
? "(Favorite)"
|
local.model.set({ providerID: provider.id, modelID: model }, { recent: true })
|
||||||
: 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) => {
|
filter((x) => {
|
||||||
if (!showSections) return true
|
if (!showSections) return true
|
||||||
const value = x.value
|
if (favorites.some((item) => item.providerID === x.value.providerID && item.modelID === x.value.modelID))
|
||||||
const inFavorites = favorites.some(
|
return false
|
||||||
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
if (recents.some((item) => item.providerID === x.value.providerID && item.modelID === x.value.modelID))
|
||||||
)
|
return false
|
||||||
if (inFavorites) return false
|
|
||||||
const inRecents = recents.some(
|
|
||||||
(item) => item.providerID === value.providerID && item.modelID === value.modelID,
|
|
||||||
)
|
|
||||||
if (inRecents) return false
|
|
||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
sortBy(
|
sortBy(
|
||||||
@@ -175,21 +111,19 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
const popularProviders = !connected()
|
const popularProviders = !connected()
|
||||||
? pipe(
|
? pipe(
|
||||||
providers(),
|
providers(),
|
||||||
map((option) => {
|
map((option) => ({
|
||||||
return {
|
...option,
|
||||||
...option,
|
category: "Popular providers",
|
||||||
category: "Popular providers",
|
})),
|
||||||
}
|
|
||||||
}),
|
|
||||||
take(6),
|
take(6),
|
||||||
)
|
)
|
||||||
: []
|
: []
|
||||||
|
|
||||||
// Search shows a single merged list (favorites inline)
|
|
||||||
if (needle) {
|
if (needle) {
|
||||||
const filteredProviders = fuzzysort.go(needle, providerOptions, { keys: ["title", "category"] }).map((x) => x.obj)
|
return [
|
||||||
const filteredPopular = fuzzysort.go(needle, popularProviders, { keys: ["title"] }).map((x) => x.obj)
|
...fuzzysort.go(needle, providerOptions, { keys: ["title", "category"] }).map((x) => x.obj),
|
||||||
return [...filteredProviders, ...filteredPopular]
|
...fuzzysort.go(needle, popularProviders, { keys: ["title"] }).map((x) => x.obj),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders]
|
return [...favoriteOptions, ...recentOptions, ...providerOptions, ...popularProviders]
|
||||||
@@ -199,13 +133,11 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
props.providerID ? sync.data.provider.find((x) => x.id === props.providerID) : null,
|
props.providerID ? sync.data.provider.find((x) => x.id === props.providerID) : null,
|
||||||
)
|
)
|
||||||
|
|
||||||
const title = createMemo(() => {
|
const title = createMemo(() => provider()?.name ?? "Select model")
|
||||||
if (provider()) return provider()!.name
|
|
||||||
return "Select model"
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogSelect
|
<DialogSelect<ReturnType<typeof options>[number]["value"]>
|
||||||
|
options={options()}
|
||||||
keybind={[
|
keybind={[
|
||||||
{
|
{
|
||||||
keybind: keybind.all.model_provider_list?.[0],
|
keybind: keybind.all.model_provider_list?.[0],
|
||||||
@@ -223,12 +155,11 @@ export function DialogModel(props: { providerID?: string }) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
ref={setRef}
|
|
||||||
onFilter={setQuery}
|
onFilter={setQuery}
|
||||||
|
flat={true}
|
||||||
skipFilter={true}
|
skipFilter={true}
|
||||||
title={title()}
|
title={title()}
|
||||||
current={local.model.current()}
|
current={local.model.current()}
|
||||||
options={options()}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface DialogSelectProps<T> {
|
|||||||
title: string
|
title: string
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
options: DialogSelectOption<T>[]
|
options: DialogSelectOption<T>[]
|
||||||
|
flat?: boolean
|
||||||
ref?: (ref: DialogSelectRef<T>) => void
|
ref?: (ref: DialogSelectRef<T>) => void
|
||||||
onMove?: (option: DialogSelectOption<T>) => void
|
onMove?: (option: DialogSelectOption<T>) => void
|
||||||
onFilter?: (query: string) => void
|
onFilter?: (query: string) => void
|
||||||
@@ -100,7 +101,10 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
setStore("input", "keyboard")
|
setStore("input", "keyboard")
|
||||||
})
|
})
|
||||||
|
|
||||||
const grouped = createMemo(() => {
|
const flatten = createMemo(() => props.flat && store.filter.length > 0)
|
||||||
|
|
||||||
|
const grouped = createMemo<[string, DialogSelectOption<T>[]][]>(() => {
|
||||||
|
if (flatten()) return [["", filtered()]]
|
||||||
const result = pipe(
|
const result = pipe(
|
||||||
filtered(),
|
filtered(),
|
||||||
groupBy((x) => x.category ?? ""),
|
groupBy((x) => x.category ?? ""),
|
||||||
@@ -117,10 +121,16 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rows = createMemo(() => {
|
||||||
|
const headers = grouped().reduce((acc, [category], i) => {
|
||||||
|
if (!category) return acc
|
||||||
|
return acc + (i > 0 ? 2 : 1)
|
||||||
|
}, 0)
|
||||||
|
return flat().length + headers
|
||||||
|
})
|
||||||
|
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
const height = createMemo(() =>
|
const height = createMemo(() => Math.min(rows(), Math.floor(dimensions().height / 2) - 6))
|
||||||
Math.min(flat().length + grouped().length * 2 - 1, Math.floor(dimensions().height / 2) - 6),
|
|
||||||
)
|
|
||||||
|
|
||||||
const selected = createMemo(() => flat()[store.selected])
|
const selected = createMemo(() => flat()[store.selected])
|
||||||
|
|
||||||
@@ -311,7 +321,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||||||
>
|
>
|
||||||
<Option
|
<Option
|
||||||
title={option.title}
|
title={option.title}
|
||||||
footer={option.footer}
|
footer={flatten() ? (option.category ?? option.footer) : option.footer}
|
||||||
description={option.description !== category ? option.description : undefined}
|
description={option.description !== category ? option.description : undefined}
|
||||||
active={active()}
|
active={active()}
|
||||||
current={current()}
|
current={current()}
|
||||||
|
|||||||
Reference in New Issue
Block a user