|
|
@@ -1,4 +1,5 @@
|
|
|
-import { Component, createMemo, Show } from "solid-js"
|
|
|
+import { Popover as Kobalte } from "@kobalte/core/popover"
|
|
|
+import { Component, createMemo, createSignal, JSX, Show } from "solid-js"
|
|
|
import { useLocal } from "@/context/local"
|
|
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
|
import { popularProviders } from "@/hooks/use-providers"
|
|
|
@@ -9,9 +10,11 @@ import { List } from "@opencode-ai/ui/list"
|
|
|
import { DialogSelectProvider } from "./dialog-select-provider"
|
|
|
import { DialogManageModels } from "./dialog-manage-models"
|
|
|
|
|
|
-export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
|
|
|
+const ModelList: Component<{
|
|
|
+ provider?: string
|
|
|
+ onSelect: () => void
|
|
|
+}> = (props) => {
|
|
|
const local = useLocal()
|
|
|
- const dialog = useDialog()
|
|
|
|
|
|
const models = createMemo(() =>
|
|
|
local.model
|
|
|
@@ -20,6 +23,70 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
|
|
|
.filter((m) => (props.provider ? m.provider.id === props.provider : true)),
|
|
|
)
|
|
|
|
|
|
+ return (
|
|
|
+ <List
|
|
|
+ class="flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0 p-1"
|
|
|
+ search={{ placeholder: "Search models", autofocus: true }}
|
|
|
+ emptyMessage="No model results"
|
|
|
+ key={(x) => `${x.provider.id}:${x.id}`}
|
|
|
+ items={models}
|
|
|
+ current={local.model.current()}
|
|
|
+ filterKeys={["provider.name", "name", "id"]}
|
|
|
+ sortBy={(a, b) => a.name.localeCompare(b.name)}
|
|
|
+ groupBy={(x) => x.provider.name}
|
|
|
+ sortGroupsBy={(a, b) => {
|
|
|
+ if (a.category === "Recent" && b.category !== "Recent") return -1
|
|
|
+ if (b.category === "Recent" && a.category !== "Recent") return 1
|
|
|
+ const aProvider = a.items[0].provider.id
|
|
|
+ const bProvider = b.items[0].provider.id
|
|
|
+ if (popularProviders.includes(aProvider) && !popularProviders.includes(bProvider)) return -1
|
|
|
+ if (!popularProviders.includes(aProvider) && popularProviders.includes(bProvider)) return 1
|
|
|
+ return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider)
|
|
|
+ }}
|
|
|
+ onSelect={(x) => {
|
|
|
+ local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
|
|
|
+ recent: true,
|
|
|
+ })
|
|
|
+ props.onSelect()
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {(i) => (
|
|
|
+ <div class="w-full flex items-center gap-x-2 text-13-regular">
|
|
|
+ <span class="truncate">{i.name}</span>
|
|
|
+ <Show when={i.provider.id === "opencode" && (!i.cost || i.cost?.input === 0)}>
|
|
|
+ <Tag>Free</Tag>
|
|
|
+ </Show>
|
|
|
+ <Show when={i.latest}>
|
|
|
+ <Tag>Latest</Tag>
|
|
|
+ </Show>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </List>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export const ModelSelectorPopover: Component<{
|
|
|
+ provider?: string
|
|
|
+ children: JSX.Element
|
|
|
+}> = (props) => {
|
|
|
+ const [open, setOpen] = createSignal(false)
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
|
|
|
+ <Kobalte.Trigger as="div">{props.children}</Kobalte.Trigger>
|
|
|
+ <Kobalte.Portal>
|
|
|
+ <Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none">
|
|
|
+ <Kobalte.Title class="sr-only">Select model</Kobalte.Title>
|
|
|
+ <ModelList provider={props.provider} onSelect={() => setOpen(false)} />
|
|
|
+ </Kobalte.Content>
|
|
|
+ </Kobalte.Portal>
|
|
|
+ </Kobalte>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
|
|
|
+ const dialog = useDialog()
|
|
|
+
|
|
|
return (
|
|
|
<Dialog
|
|
|
title="Select model"
|
|
|
@@ -34,43 +101,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
|
|
|
</Button>
|
|
|
}
|
|
|
>
|
|
|
- <List
|
|
|
- search={{ placeholder: "Search models", autofocus: true }}
|
|
|
- emptyMessage="No model results"
|
|
|
- key={(x) => `${x.provider.id}:${x.id}`}
|
|
|
- items={models}
|
|
|
- current={local.model.current()}
|
|
|
- filterKeys={["provider.name", "name", "id"]}
|
|
|
- sortBy={(a, b) => a.name.localeCompare(b.name)}
|
|
|
- groupBy={(x) => x.provider.name}
|
|
|
- sortGroupsBy={(a, b) => {
|
|
|
- if (a.category === "Recent" && b.category !== "Recent") return -1
|
|
|
- if (b.category === "Recent" && a.category !== "Recent") return 1
|
|
|
- const aProvider = a.items[0].provider.id
|
|
|
- const bProvider = b.items[0].provider.id
|
|
|
- if (popularProviders.includes(aProvider) && !popularProviders.includes(bProvider)) return -1
|
|
|
- if (!popularProviders.includes(aProvider) && popularProviders.includes(bProvider)) return 1
|
|
|
- return popularProviders.indexOf(aProvider) - popularProviders.indexOf(bProvider)
|
|
|
- }}
|
|
|
- onSelect={(x) => {
|
|
|
- local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
|
|
|
- recent: true,
|
|
|
- })
|
|
|
- dialog.close()
|
|
|
- }}
|
|
|
- >
|
|
|
- {(i) => (
|
|
|
- <div class="w-full flex items-center gap-x-3">
|
|
|
- <span>{i.name}</span>
|
|
|
- <Show when={i.provider.id === "opencode" && (!i.cost || i.cost?.input === 0)}>
|
|
|
- <Tag>Free</Tag>
|
|
|
- </Show>
|
|
|
- <Show when={i.latest}>
|
|
|
- <Tag>Latest</Tag>
|
|
|
- </Show>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </List>
|
|
|
+ <ModelList provider={props.provider} onSelect={() => dialog.close()} />
|
|
|
<Button
|
|
|
variant="ghost"
|
|
|
class="ml-3 mt-5 mb-6 text-text-base self-start"
|