diff options
| author | adamelmore <[email protected]> | 2026-01-26 07:23:01 -0600 |
|---|---|---|
| committer | adamelmore <[email protected]> | 2026-01-26 08:15:01 -0600 |
| commit | 1934ee13d8439ed38b9bcebb4e26e4d2d01f6f08 (patch) | |
| tree | 21159298036ce3729f77e18b6e95e6ebc6581652 | |
| parent | 6c1e18f111ae8bdbedbdc0c2d4367c09d318f94a (diff) | |
| download | opencode-1934ee13d8439ed38b9bcebb4e26e4d2d01f6f08.tar.gz opencode-1934ee13d8439ed38b9bcebb4e26e4d2d01f6f08.zip | |
wip(app): model settings
| -rw-r--r-- | packages/app/src/components/dialog-settings.tsx | 14 | ||||
| -rw-r--r-- | packages/app/src/components/settings-models.tsx | 131 |
2 files changed, 133 insertions, 12 deletions
diff --git a/packages/app/src/components/dialog-settings.tsx b/packages/app/src/components/dialog-settings.tsx index 9dd6efd68..5efee5a3c 100644 --- a/packages/app/src/components/dialog-settings.tsx +++ b/packages/app/src/components/dialog-settings.tsx @@ -6,12 +6,8 @@ import { useLanguage } from "@/context/language" import { usePlatform } from "@/context/platform" import { SettingsGeneral } from "./settings-general" import { SettingsKeybinds } from "./settings-keybinds" -import { SettingsPermissions } from "./settings-permissions" import { SettingsProviders } from "./settings-providers" import { SettingsModels } from "./settings-models" -import { SettingsAgents } from "./settings-agents" -import { SettingsCommands } from "./settings-commands" -import { SettingsMcp } from "./settings-mcp" export const DialogSettings: Component = () => { const language = useLanguage() @@ -45,6 +41,10 @@ export const DialogSettings: Component = () => { <Icon name="server" /> {language.t("settings.providers.title")} </Tabs.Trigger> + <Tabs.Trigger value="models"> + <Icon name="server" /> + {language.t("settings.models.title")} + </Tabs.Trigger> </div> </div> </div> @@ -64,9 +64,9 @@ export const DialogSettings: Component = () => { <Tabs.Content value="providers" class="no-scrollbar"> <SettingsProviders /> </Tabs.Content> - {/* <Tabs.Content value="models" class="no-scrollbar"> */} - {/* <SettingsModels /> */} - {/* </Tabs.Content> */} + <Tabs.Content value="models" class="no-scrollbar"> + <SettingsModels /> + </Tabs.Content> {/* <Tabs.Content value="agents" class="no-scrollbar"> */} {/* <SettingsAgents /> */} {/* </Tabs.Content> */} diff --git a/packages/app/src/components/settings-models.tsx b/packages/app/src/components/settings-models.tsx index 6a636879d..a3ba45f61 100644 --- a/packages/app/src/components/settings-models.tsx +++ b/packages/app/src/components/settings-models.tsx @@ -1,14 +1,135 @@ -import { Component } from "solid-js" +import { useFilteredList } from "@opencode-ai/ui/hooks" +import { ProviderIcon } from "@opencode-ai/ui/provider-icon" +import { Switch } from "@opencode-ai/ui/switch" +import { Icon } from "@opencode-ai/ui/icon" +import { IconButton } from "@opencode-ai/ui/icon-button" +import { TextField } from "@opencode-ai/ui/text-field" +import type { IconName } from "@opencode-ai/ui/icons/provider" +import { type Component, For, Show } from "solid-js" import { useLanguage } from "@/context/language" +import { type ModelKey, useLocal } from "@/context/local" +import { popularProviders } from "@/hooks/use-providers" + +type ModelItem = ReturnType<ReturnType<typeof useLocal>["model"]["list"]>[number] export const SettingsModels: Component = () => { + const local = useLocal() const language = useLanguage() + const list = useFilteredList<ModelItem>({ + items: (_filter) => local.model.list(), + key: (x) => `${x.provider.id}:${x.id}`, + filterKeys: ["provider.name", "name", "id"], + sortBy: (a, b) => a.name.localeCompare(b.name), + groupBy: (x) => x.provider.id, + sortGroupsBy: (a, b) => { + const aIndex = popularProviders.indexOf(a.category) + const bIndex = popularProviders.indexOf(b.category) + const aPopular = aIndex >= 0 + const bPopular = bIndex >= 0 + + if (aPopular && !bPopular) return -1 + if (!aPopular && bPopular) return 1 + if (aPopular && bPopular) return aIndex - bIndex + + const aName = a.items[0].provider.name + const bName = b.items[0].provider.name + return aName.localeCompare(bName) + }, + }) + return ( - <div class="flex flex-col h-full overflow-y-auto"> - <div class="flex flex-col gap-6 p-6 max-w-[600px]"> - <h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2> - <p class="text-14-regular text-text-weak">{language.t("settings.models.description")}</p> + <div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}> + <div + class="sticky top-0 z-10" + style={{ + background: + "linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)", + }} + > + <div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]"> + <h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2> + <div class="flex items-center gap-2 px-3 h-9 rounded-lg bg-surface-base"> + <Icon name="magnifying-glass" class="text-icon-weak-base flex-shrink-0" /> + <TextField + variant="ghost" + type="text" + value={list.filter()} + onChange={list.onInput} + placeholder={language.t("dialog.model.search.placeholder")} + spellcheck={false} + autocorrect="off" + autocomplete="off" + autocapitalize="off" + class="flex-1" + /> + <Show when={list.filter()}> + <IconButton icon="circle-x" variant="ghost" onClick={list.clear} /> + </Show> + </div> + </div> + </div> + + <div class="flex flex-col gap-8 max-w-[720px]"> + <Show + when={!list.grouped.loading} + fallback={ + <div class="flex flex-col items-center justify-center py-12 text-center"> + <span class="text-14-regular text-text-weak"> + {language.t("common.loading")} + {language.t("common.loading.ellipsis")} + </span> + </div> + } + > + <Show + when={list.flat().length > 0} + fallback={ + <div class="flex flex-col items-center justify-center py-12 text-center"> + <span class="text-14-regular text-text-weak">{language.t("dialog.model.empty")}</span> + <Show when={list.filter()}> + <span class="text-14-regular text-text-strong mt-1">"{list.filter()}"</span> + </Show> + </div> + } + > + <For each={list.grouped.latest}> + {(group) => ( + <div class="flex flex-col gap-1"> + <div class="flex items-center gap-2 pb-2"> + <ProviderIcon id={group.category as IconName} class="size-5 shrink-0 icon-strong-base" /> + <span class="text-14-medium text-text-strong">{group.items[0].provider.name}</span> + </div> + <div class="bg-surface-raised-base px-4 rounded-lg"> + <For each={group.items}> + {(item) => { + const key: ModelKey = { providerID: item.provider.id, modelID: item.id } + return ( + <div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none"> + <div class="min-w-0"> + <span class="text-14-regular text-text-strong truncate block">{item.name}</span> + </div> + <div class="flex-shrink-0"> + <Switch + checked={!!local.model.visible(key)} + onChange={(checked) => { + local.model.setVisibility(key, checked) + }} + hideLabel + > + {item.name} + </Switch> + </div> + </div> + ) + }} + </For> + </div> + </div> + )} + </For> + </Show> + </Show> </div> </div> ) |
