| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- 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 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>
- )
- }
|