diff options
| author | Filip <[email protected]> | 2025-09-24 18:05:15 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-09-24 11:05:15 -0500 |
| commit | d3b6545e7c6069c9db031634b7890e6b8eb4de2a (patch) | |
| tree | 5457b28378cf453e2c138337fc1abee3b3423ed2 /packages/app/src/pages | |
| parent | 3f911b22b011af74426699dac079b33badf862b1 (diff) | |
| download | opencode-d3b6545e7c6069c9db031634b7890e6b8eb4de2a.tar.gz opencode-d3b6545e7c6069c9db031634b7890e6b8eb4de2a.zip | |
feat(app): added command palette (#2630)
Co-authored-by: Adam <[email protected]>
Diffstat (limited to 'packages/app/src/pages')
| -rw-r--r-- | packages/app/src/pages/index.tsx | 92 |
1 files changed, 72 insertions, 20 deletions
diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx index 50c729789..9133f40d8 100644 --- a/packages/app/src/pages/index.tsx +++ b/packages/app/src/pages/index.tsx @@ -1,8 +1,9 @@ -import { FileIcon, Icon, IconButton, Logo, Tooltip } from "@/ui" +import { Button, FileIcon, Icon, IconButton, Logo, Tooltip } from "@/ui" import { Tabs } from "@/ui/tabs" import { Select } from "@/components/select" import FileTree from "@/components/file-tree" import { For, Match, onCleanup, onMount, Show, Switch } from "solid-js" +import { SelectDialog } from "@/components/select-dialog" import { useLocal, useSDK } from "@/context" import { Code } from "@/components/code" import { @@ -28,6 +29,7 @@ export default function Page() { activeItem: undefined as string | undefined, prompt: "", dragging: undefined as "left" | "right" | undefined, + modelSelectOpen: false, }) let inputRef: HTMLInputElement | undefined = undefined @@ -43,6 +45,17 @@ export default function Page() { }) const handleKeyDown = (e: KeyboardEvent) => { + if (e.getModifierState(MOD) && e.shiftKey && e.key.toLowerCase() === "p") { + e.preventDefault() + setStore("modelSelectOpen", true) + return + } + if (e.getModifierState(MOD) && e.key.toLowerCase() === "p") { + e.preventDefault() + setStore("modelSelectOpen", true) + return + } + const inputFocused = document.activeElement === inputRef if (inputFocused) { if (e.key === "Escape") { @@ -190,7 +203,7 @@ export default function Page() { path: { id: session!.id }, body: { agent: local.agent.current()!.name, - model: local.model.current(), + model: { modelID: local.model.current()!.id, providerID: local.model.current()!.provider.id }, parts: [ { type: "text", @@ -265,7 +278,7 @@ export default function Page() { class="fixed top-0 right-0 h-full border-l border-border-subtle/30 flex flex-col overflow-hidden" style={`width: ${local.layout.rightWidth()}px`} > - <div class="relative flex-1 min-h-0 overflow-y-auto no-scrollbar"> + <div class="relative flex-1 min-h-0 overflow-y-auto overflow-x-hidden"> <Show when={local.session.active()} fallback={<SessionList />}> {(activeSession) => ( <div class="relative"> @@ -470,7 +483,7 @@ export default function Page() { type="text" value={store.prompt} onInput={(e) => setStore("prompt", e.currentTarget.value)} - placeholder="It all starts with a prompt..." + placeholder="Placeholder text..." class="w-full p-1 pb-4 text-text font-light placeholder-text-muted/70 text-sm focus:outline-none" /> <div class="flex justify-between items-center text-xs text-text-muted"> @@ -479,24 +492,13 @@ export default function Page() { options={local.agent.list().map((a) => a.name)} current={local.agent.current().name} onSelect={local.agent.set} - size="sm" class="uppercase" /> - <Select - options={local.model.list()} - current={local.model.current()} - onSelect={local.model.set} - label={(x) => x.modelID} - value={(x) => `${x.providerID}.${x.modelID}`} - filter={{ - keys: ["providerID", "modelID"], - placeholder: "Filter models", - }} - groupBy={(x) => x.providerID} - size="sm" - class="uppercase" - /> - <span class="text-text-muted/70">{local.model.parsed().provider}</span> + <Button onClick={() => setStore("modelSelectOpen", true)}> + {local.model.current()?.name ?? "Select model"} + <Icon name="chevron-down" size={24} class="text-text-muted" /> + </Button> + <span class="text-text-muted/70 whitespace-nowrap">{local.model.current()?.provider.name}</span> </div> <div class="flex gap-1 items-center"> <IconButton class="text-text-muted" size="xs" variant="ghost"> @@ -510,6 +512,56 @@ export default function Page() { </div> </form> </div> + <Show when={store.modelSelectOpen}> + <SelectDialog + key={(x) => `${x.provider.id}:${x.id}`} + items={local.model.list()} + current={local.model.current()} + render={(i) => ( + <div class="w-full flex items-center justify-between"> + <div class="flex items-center gap-x-2 text-text-muted grow min-w-0"> + <img src={`https://models.dev/logos/${i.provider.id}.svg`} class="size-4 invert opacity-40" /> + <span class="text-xs text-text whitespace-nowrap">{i.name}</span> + <span class="text-xs text-text-muted/80 whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0"> + {i.id} + </span> + </div> + <div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"> + <Tooltip forceMount={false} value="Reasoning"> + <Icon name="brain" size={16} classList={{ "text-accent": i.reasoning }} /> + </Tooltip> + <Tooltip forceMount={false} value="Tools"> + <Icon name="hammer" size={16} classList={{ "text-secondary": i.tool_call }} /> + </Tooltip> + <Tooltip forceMount={false} value="Attachments"> + <Icon name="photo" size={16} classList={{ "text-success": i.attachment }} /> + </Tooltip> + <div class="rounded-full bg-text-muted/20 text-text-muted/80 w-9 h-4 flex items-center justify-center text-[10px]"> + {new Intl.NumberFormat("en-US", { + notation: "compact", + compactDisplay: "short", + }).format(i.limit.context)} + </div> + <Tooltip forceMount={false} value={`$${i.cost?.input}/1M input, $${i.cost?.output}/1M output`}> + <div class="rounded-full bg-success/20 text-success/80 w-9 h-4 flex items-center justify-center text-[10px]"> + <Switch fallback="FREE"> + <Match when={i.cost?.input > 10}>$$$</Match> + <Match when={i.cost?.input > 1}>$$</Match> + <Match when={i.cost?.input > 0.1}>$</Match> + </Switch> + </div> + </Tooltip> + </div> + </div> + )} + filter={{ + keys: ["provider.name", "name", "id"], + }} + groupBy={(x) => x.provider.name} + onClose={() => setStore("modelSelectOpen", false)} + onSelect={(x) => local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined)} + /> + </Show> </div> ) } |
