diff options
| -rw-r--r-- | packages/app/src/components/dialog-select-model.tsx | 111 | ||||
| -rw-r--r-- | packages/app/src/components/prompt-input.tsx | 36 |
2 files changed, 92 insertions, 55 deletions
diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx index 54783386a..1130e2234 100644 --- a/packages/app/src/components/dialog-select-model.tsx +++ b/packages/app/src/components/dialog-select-model.tsx @@ -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 @@ -21,6 +24,70 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => { ) 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" action={ @@ -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" diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index a45a6d587..ab2a017ca 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -16,7 +16,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { Select } from "@opencode-ai/ui/select" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { useDialog } from "@opencode-ai/ui/context/dialog" -import { DialogSelectModel } from "@/components/dialog-select-model" +import { ModelSelectorPopover } from "@/components/dialog-select-model" import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid" import { useProviders } from "@/hooks/use-providers" import { useCommand } from "@/context/command" @@ -1367,20 +1367,26 @@ export const PromptInput: Component<PromptInputProps> = (props) => { variant="ghost" /> </TooltipKeybind> - <TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}> - <Button - as="div" - variant="ghost" - onClick={() => - dialog.show(() => - providers.paid().length > 0 ? <DialogSelectModel /> : <DialogSelectModelUnpaid />, - ) - } - > - {local.model.current()?.name ?? "Select model"} - <Icon name="chevron-down" size="small" /> - </Button> - </TooltipKeybind> + <Show + when={providers.paid().length > 0} + fallback={ + <TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}> + <Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}> + {local.model.current()?.name ?? "Select model"} + <Icon name="chevron-down" size="small" /> + </Button> + </TooltipKeybind> + } + > + <ModelSelectorPopover> + <TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}> + <Button as="div" variant="ghost"> + {local.model.current()?.name ?? "Select model"} + <Icon name="chevron-down" size="small" /> + </Button> + </TooltipKeybind> + </ModelSelectorPopover> + </Show> <Show when={local.model.variant.list().length > 0}> <TooltipKeybind placement="top" |
