Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
180 lines
6.1 KiB
TypeScript
180 lines
6.1 KiB
TypeScript
import { createMemo, createSignal } from "solid-js"
|
|
import { useLocal } from "../context/local"
|
|
import { DialogSelect } from "../ui/dialog-select"
|
|
import { useDialog } from "../ui/dialog"
|
|
import { DialogIntegration } from "./dialog-integration"
|
|
import { DialogVariant } from "./dialog-variant"
|
|
import * as fuzzysort from "fuzzysort"
|
|
import { useConnected } from "./use-connected"
|
|
import { useData } from "../context/data"
|
|
|
|
export function DialogModel(props: { providerID?: string }) {
|
|
const local = useLocal()
|
|
const data = useData()
|
|
const dialog = useDialog()
|
|
const [query, setQuery] = createSignal("")
|
|
|
|
const connected = useConnected()
|
|
const providers = createMemo(() => new Map((data.location.provider.list() ?? []).map((item) => [item.id, item])))
|
|
const models = createMemo(() => data.location.model.list() ?? [])
|
|
|
|
const showExtra = createMemo(() => connected() && !props.providerID)
|
|
|
|
const options = createMemo(() => {
|
|
const needle = query().trim()
|
|
const showSections = showExtra() && needle.length === 0
|
|
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 model = models().find((model) => model.providerID === item.providerID && model.id === item.modelID)
|
|
if (!model) return []
|
|
const provider = providers().get(model.providerID)
|
|
return [
|
|
{
|
|
key: item,
|
|
value: { providerID: model.providerID, modelID: model.id },
|
|
title: model.name,
|
|
releaseDate: model.time.released,
|
|
description: provider?.name ?? model.providerID,
|
|
category,
|
|
footer: free(model) ? "Free" : undefined,
|
|
onSelect: () => {
|
|
onSelect(model.providerID, model.id)
|
|
},
|
|
},
|
|
]
|
|
})
|
|
}
|
|
|
|
const favoriteOptions = toOptions(favorites, "Favorites")
|
|
const recentOptions = toOptions(
|
|
recents.filter(
|
|
(item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID),
|
|
),
|
|
"Recent",
|
|
)
|
|
|
|
const modelOptions = sortModelOptions(
|
|
models()
|
|
.filter((model) => model.status !== "deprecated")
|
|
.filter((model) => (props.providerID ? model.providerID === props.providerID : true))
|
|
.map((model) => {
|
|
const provider = providers().get(model.providerID)
|
|
return {
|
|
value: { providerID: model.providerID, modelID: model.id },
|
|
providerID: model.providerID,
|
|
providerName: provider?.name ?? model.providerID,
|
|
title: model.name,
|
|
releaseDate: model.time.released,
|
|
description: favorites.some((item) => item.providerID === model.providerID && item.modelID === model.id)
|
|
? "(Favorite)"
|
|
: undefined,
|
|
category: connected() ? (provider?.name ?? model.providerID) : undefined,
|
|
footer: free(model) ? "Free" : undefined,
|
|
onSelect() {
|
|
onSelect(model.providerID, model.id)
|
|
},
|
|
}
|
|
})
|
|
.filter((option) => {
|
|
if (!showSections) return true
|
|
if (
|
|
favorites.some(
|
|
(item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID,
|
|
)
|
|
)
|
|
return false
|
|
if (
|
|
recents.some((item) => item.providerID === option.value.providerID && item.modelID === option.value.modelID)
|
|
)
|
|
return false
|
|
return true
|
|
}),
|
|
)
|
|
|
|
if (needle) {
|
|
return fuzzysort.go(needle, modelOptions, { keys: ["title", "category"] }).map((item) => item.obj)
|
|
}
|
|
|
|
return [...favoriteOptions, ...recentOptions, ...modelOptions]
|
|
})
|
|
|
|
const provider = createMemo(() => (props.providerID ? providers().get(props.providerID) : undefined))
|
|
|
|
const title = createMemo(() => {
|
|
const value = provider()
|
|
if (!value) return "Select model"
|
|
return value.name
|
|
})
|
|
|
|
function onSelect(providerID: string, modelID: string) {
|
|
local.model.set({ providerID, modelID }, { recent: true })
|
|
const list = local.model.variant.list()
|
|
const cur = local.model.variant.current()
|
|
if (cur && list.includes(cur)) {
|
|
dialog.clear()
|
|
return
|
|
}
|
|
if (list.length > 0) {
|
|
dialog.replace(() => <DialogVariant />)
|
|
return
|
|
}
|
|
dialog.clear()
|
|
}
|
|
|
|
return (
|
|
<DialogSelect<ReturnType<typeof options>[number]["value"]>
|
|
options={options()}
|
|
actions={[
|
|
{
|
|
command: "model.dialog.provider",
|
|
title: connected() ? "Connect integration" : "View all integrations",
|
|
onTrigger() {
|
|
dialog.replace(() => (
|
|
<DialogIntegration
|
|
onConnected={(providerID) => dialog.replace(() => <DialogModel providerID={providerID} />)}
|
|
/>
|
|
))
|
|
},
|
|
},
|
|
{
|
|
command: "model.dialog.favorite",
|
|
title: "Favorite",
|
|
hidden: !connected(),
|
|
onTrigger: (option) => {
|
|
local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
|
|
},
|
|
},
|
|
]}
|
|
onFilter={setQuery}
|
|
flat={true}
|
|
skipFilter={true}
|
|
title={title()}
|
|
current={local.model.current()}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export function sortModelOptions<
|
|
T extends { providerID?: string; providerName?: string; releaseDate: string | number; title: string },
|
|
>(options: T[]) {
|
|
return options.toSorted((a, b) => {
|
|
const provider = Number(a.providerID !== "opencode") - Number(b.providerID !== "opencode")
|
|
if (provider !== 0) return provider
|
|
|
|
const name = (a.providerName ?? "").localeCompare(b.providerName ?? "")
|
|
if (name !== 0) return name
|
|
|
|
const release = Number(b.releaseDate) - Number(a.releaseDate)
|
|
if (release !== 0) return release
|
|
|
|
return a.title.localeCompare(b.title)
|
|
})
|
|
}
|
|
|
|
function free(model: { cost: Array<{ input: number }> }) {
|
|
return model.cost.length > 0 && model.cost.every((cost) => cost.input === 0)
|
|
}
|