settings-models.tsx 5.3 KB

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