Buckets:
| 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 Component, For, Show } from "solid-js" | |
| import { useLanguage } from "@/context/language" | |
| import { useModels } from "@/context/models" | |
| import { popularProviders } from "@/hooks/use-providers" | |
| import { SettingsList } from "./settings-list" | |
| import { SettingsServerPicker, SettingsServerScope } from "./settings-server-picker" | |
| type ModelItem = ReturnType<ReturnType<typeof useModels>["list"]>[number] | |
| const ListLoadingState: Component<{ label: string }> = (props) => { | |
| return ( | |
| <div class="flex flex-col items-center justify-center py-12 text-center"> | |
| <span class="text-14-regular text-text-weak">{props.label}</span> | |
| </div> | |
| ) | |
| } | |
| const ListEmptyState: Component<{ message: string; filter: string }> = (props) => { | |
| return ( | |
| <div class="flex flex-col items-center justify-center py-12 text-center"> | |
| <span class="text-14-regular text-text-weak">{props.message}</span> | |
| <Show when={props.filter}> | |
| <span class="text-14-regular text-text-strong mt-1">"{props.filter}"</span> | |
| </Show> | |
| </div> | |
| ) | |
| } | |
| export const SettingsModels: Component = () => { | |
| return ( | |
| <SettingsServerScope> | |
| <SettingsModelsContent /> | |
| </SettingsServerScope> | |
| ) | |
| } | |
| const SettingsModelsContent: Component = () => { | |
| const language = useLanguage() | |
| const models = useModels() | |
| const list = useFilteredList<ModelItem>({ | |
| items: (_filter) => models.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 px-4 pb-10 sm:px-10 sm:pb-10"> | |
| <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]"> | |
| <div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]"> | |
| <div class="flex items-center justify-between gap-4"> | |
| <h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2> | |
| <SettingsServerPicker /> | |
| </div> | |
| <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={ | |
| <ListLoadingState label={`${language.t("common.loading")}${language.t("common.loading.ellipsis")}`} /> | |
| } | |
| > | |
| <Show | |
| when={list.flat().length > 0} | |
| fallback={<ListEmptyState message={language.t("dialog.model.empty")} filter={list.filter()} />} | |
| > | |
| <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} class="size-5 shrink-0 icon-strong-base" /> | |
| <span class="text-14-medium text-text-strong">{group.items[0].provider.name}</span> | |
| </div> | |
| <SettingsList> | |
| <For each={group.items}> | |
| {(item) => { | |
| const key = { providerID: item.provider.id, modelID: item.id } | |
| return ( | |
| <div class="flex flex-wrap 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={models.visible(key)} | |
| onChange={(checked) => { | |
| models.setVisibility(key, checked) | |
| }} | |
| hideLabel | |
| > | |
| {item.name} | |
| </Switch> | |
| </div> | |
| </div> | |
| ) | |
| }} | |
| </For> | |
| </SettingsList> | |
| </div> | |
| )} | |
| </For> | |
| </Show> | |
| </Show> | |
| </div> | |
| </div> | |
| ) | |
| } | |
Xet Storage Details
- Size:
- 5.91 kB
- Xet hash:
- 7f9d8a54a40c5931b005d3a7d58ebbe2161637e7b3a585c06aa23ed92327aad0
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.