Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/app/src/components/dialog-select-model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { Button } from "@opencode-ai/ui/button"
import { Tag } from "@opencode-ai/ui/tag"
import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import type { IconName } from "@opencode-ai/ui/icons/provider"
import { DialogSelectProvider } from "./dialog-select-provider"
import { DialogManageModels } from "./dialog-manage-models"

Expand Down Expand Up @@ -35,6 +37,12 @@ const ModelList: Component<{
filterKeys={["provider.name", "name", "id"]}
sortBy={(a, b) => a.name.localeCompare(b.name)}
groupBy={(x) => x.provider.name}
groupHeader={(group) => (
<div class="flex items-center gap-x-3">
<ProviderIcon data-slot="list-item-extra-icon" id={group.items[0].provider.id as IconName} />
<span>{group.category}</span>
</div>
)}
sortGroupsBy={(a, b) => {
if (a.category === "Recent" && b.category !== "Recent") return -1
if (b.category === "Recent" && a.category !== "Recent") return 1
Expand All @@ -52,7 +60,8 @@ const ModelList: Component<{
}}
>
{(i) => (
<div class="w-full flex items-center gap-x-2 text-13-regular">
<div class="w-full flex items-center gap-x-3 pl-1 text-13-regular">
<ProviderIcon data-slot="list-item-extra-icon" id={i.provider.id as IconName} />
<span class="truncate">{i.name}</span>
<Show when={i.provider.id === "opencode" && (!i.cost || i.cost?.input === 0)}>
<Tag>Free</Tag>
Expand Down
18 changes: 16 additions & 2 deletions packages/app/src/components/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { useSync } from "@/context/sync"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
import type { IconName } from "@opencode-ai/ui/icons/provider"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Select } from "@opencode-ai/ui/select"
Expand Down Expand Up @@ -1560,6 +1562,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
fallback={
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
<Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
<Show when={local.model.current()?.provider?.id}>
<ProviderIcon
id={local.model.current()!.provider.id as IconName}
class="size-4 shrink-0"
/>
</Show>
{local.model.current()?.name ?? "Select model"}
<Icon name="chevron-down" size="small" />
</Button>
Expand All @@ -1569,6 +1577,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<ModelSelectorPopover>
<TooltipKeybind placement="top" title="Choose model" keybind={command.keybind("model.choose")}>
<Button as="div" variant="ghost">
<Show when={local.model.current()?.provider?.id}>
<ProviderIcon
id={local.model.current()!.provider.id as IconName}
class="size-4 shrink-0"
/>
</Show>
{local.model.current()?.name ?? "Select model"}
<Icon name="chevron-down" size="small" />
</Button>
Expand All @@ -1583,10 +1597,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
>
<Button
variant="ghost"
class="text-text-base _hidden group-hover/prompt-input:inline-block"
class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
onClick={() => local.model.variant.cycle()}
>
<span class="capitalize text-12-regular">{local.model.variant.current() ?? "Default"}</span>
{local.model.variant.current() ?? "Default"}
</Button>
</TooltipKeybind>
</Show>
Expand Down
3 changes: 1 addition & 2 deletions packages/ui/src/components/button.css
Original file line number Diff line number Diff line change
Expand Up @@ -123,21 +123,20 @@

&[data-size="normal"] {
height: 24px;
line-height: 24px;
padding: 0 6px;
&[data-icon] {
padding: 0 12px 0 4px;
}

font-size: var(--font-size-small);
line-height: var(--line-height-large);
gap: 6px;

/* text-12-medium */
font-family: var(--font-family-sans);
font-size: var(--font-size-small);
font-style: normal;
font-weight: var(--font-weight-medium);
line-height: var(--line-height-large); /* 166.667% */
letter-spacing: var(--letter-spacing-normal);
}

Expand Down
12 changes: 9 additions & 3 deletions packages/ui/src/components/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@ export interface ListSearchProps {
autofocus?: boolean
}

export interface ListGroup<T> {
category: string
items: T[]
}

export interface ListProps<T> extends FilteredListProps<T> {
class?: string
children: (item: T) => JSX.Element
groupHeader?: (group: ListGroup<T>) => JSX.Element
emptyMessage?: string
onKeyEvent?: (event: KeyboardEvent, item: T | undefined) => void
onMove?: (item: T | undefined) => void
Expand Down Expand Up @@ -116,7 +122,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
setScrollRef,
})

function GroupHeader(props: { category: string }): JSX.Element {
function GroupHeader(groupProps: { category: string; children?: JSX.Element }): JSX.Element {
const [stuck, setStuck] = createSignal(false)
const [header, setHeader] = createSignal<HTMLDivElement | undefined>(undefined)

Expand All @@ -138,7 +144,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })

return (
<div data-slot="list-header" data-stuck={stuck()} ref={setHeader}>
{props.category}
{groupProps.children ?? groupProps.category}
</div>
)
}
Expand Down Expand Up @@ -185,7 +191,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
{(group) => (
<div data-slot="list-group">
<Show when={group.category}>
<GroupHeader category={group.category} />
<GroupHeader category={group.category}>{props.groupHeader?.(group)}</GroupHeader>
</Show>
<div data-slot="list-items">
<For each={group.items}>
Expand Down
10 changes: 9 additions & 1 deletion packages/ui/src/components/session-turn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
import { ProviderIcon } from "./provider-icon"
import type { IconName } from "./provider-icons/types"
import { IconButton } from "./icon-button"
import { Tooltip } from "./tooltip"
import { Card } from "./card"
Expand Down Expand Up @@ -498,7 +500,13 @@ export function SessionTurn(
<span data-slot="session-turn-badge">{(msg() as UserMessage).agent}</span>
</Show>
<Show when={(msg() as UserMessage).model?.modelID}>
<span data-slot="session-turn-badge">{(msg() as UserMessage).model?.modelID}</span>
<span data-slot="session-turn-badge" class="inline-flex items-center gap-1">
<ProviderIcon
id={(msg() as UserMessage).model!.providerID as IconName}
class="size-3.5 shrink-0"
/>
{(msg() as UserMessage).model?.modelID}
</span>
</Show>
<span data-slot="session-turn-badge">{(msg() as UserMessage).variant || "default"}</span>
</div>
Expand Down