settings-models.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { useFilteredList } from "@opencode-ai/ui/hooks"
  2. import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
  3. import { Switch } from "@opencode-ai/ui/switch"
  4. import { Icon } from "@opencode-ai/ui/icon"
  5. import { IconButton } from "@opencode-ai/ui/icon-button"
  6. import { TextField } from "@opencode-ai/ui/text-field"
  7. import { type Component, For, Show } from "solid-js"
  8. import { useLanguage } from "@/context/language"
  9. import { useModels } from "@/context/models"
  10. import { popularProviders } from "@/hooks/use-providers"
  11. import { SettingsList } from "./settings-list"
  12. type ModelItem = ReturnType<ReturnType<typeof useModels>["list"]>[number]
  13. const ListLoadingState: Component<{ label: string }> = (props) => {
  14. return (
  15. <div class="flex flex-col items-center justify-center py-12 text-center">
  16. <span class="text-14-regular text-text-weak">{props.label}</span>
  17. </div>
  18. )
  19. }
  20. const ListEmptyState: Component<{ message: string; filter: string }> = (props) => {
  21. return (
  22. <div class="flex flex-col items-center justify-center py-12 text-center">
  23. <span class="text-14-regular text-text-weak">{props.message}</span>
  24. <Show when={props.filter}>
  25. <span class="text-14-regular text-text-strong mt-1">&quot;{props.filter}&quot;</span>
  26. </Show>
  27. </div>
  28. )
  29. }
  30. export const SettingsModels: Component = () => {
  31. const language = useLanguage()
  32. const models = useModels()
  33. const list = useFilteredList<ModelItem>({
  34. items: (_filter) => models.list(),
  35. key: (x) => `${x.provider.id}:${x.id}`,
  36. filterKeys: ["provider.name", "name", "id"],
  37. sortBy: (a, b) => a.name.localeCompare(b.name),
  38. groupBy: (x) => x.provider.id,
  39. sortGroupsBy: (a, b) => {
  40. const aIndex = popularProviders.indexOf(a.category)
  41. const bIndex = popularProviders.indexOf(b.category)
  42. const aPopular = aIndex >= 0
  43. const bPopular = bIndex >= 0
  44. if (aPopular && !bPopular) return -1
  45. if (!aPopular && bPopular) return 1
  46. if (aPopular && bPopular) return aIndex - bIndex
  47. const aName = a.items[0].provider.name
  48. const bName = b.items[0].provider.name
  49. return aName.localeCompare(bName)
  50. },
  51. })
  52. return (
  53. <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
  54. <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
  55. <div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
  56. <h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2>
  57. <div class="flex items-center gap-2 px-3 h-9 rounded-lg bg-surface-base">
  58. <Icon name="magnifying-glass" class="text-icon-weak-base flex-shrink-0" />
  59. <TextField
  60. variant="ghost"
  61. type="text"
  62. value={list.filter()}
  63. onChange={list.onInput}
  64. placeholder={language.t("dialog.model.search.placeholder")}
  65. spellcheck={false}
  66. autocorrect="off"
  67. autocomplete="off"
  68. autocapitalize="off"
  69. class="flex-1"
  70. />
  71. <Show when={list.filter()}>
  72. <IconButton icon="circle-x" variant="ghost" onClick={list.clear} />
  73. </Show>
  74. </div>
  75. </div>
  76. </div>
  77. <div class="flex flex-col gap-8 max-w-[720px]">
  78. <Show
  79. when={!list.grouped.loading}
  80. fallback={
  81. <ListLoadingState label={`${language.t("common.loading")}${language.t("common.loading.ellipsis")}`} />
  82. }
  83. >
  84. <Show
  85. when={list.flat().length > 0}
  86. fallback={<ListEmptyState message={language.t("dialog.model.empty")} filter={list.filter()} />}
  87. >
  88. <For each={list.grouped.latest}>
  89. {(group) => (
  90. <div class="flex flex-col gap-1">
  91. <div class="flex items-center gap-2 pb-2">
  92. <ProviderIcon id={group.category} class="size-5 shrink-0 icon-strong-base" />
  93. <span class="text-14-medium text-text-strong">{group.items[0].provider.name}</span>
  94. </div>
  95. <SettingsList>
  96. <For each={group.items}>
  97. {(item) => {
  98. const key = { providerID: item.provider.id, modelID: item.id }
  99. return (
  100. <div class="flex flex-wrap items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
  101. <div class="min-w-0">
  102. <span class="text-14-regular text-text-strong truncate block">{item.name}</span>
  103. </div>
  104. <div class="flex-shrink-0">
  105. <Switch
  106. checked={models.visible(key)}
  107. onChange={(checked) => {
  108. models.setVisibility(key, checked)
  109. }}
  110. hideLabel
  111. >
  112. {item.name}
  113. </Switch>
  114. </div>
  115. </div>
  116. )
  117. }}
  118. </For>
  119. </SettingsList>
  120. </div>
  121. )}
  122. </For>
  123. </Show>
  124. </Show>
  125. </div>
  126. </div>
  127. )
  128. }