dialog-model.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { createMemo, createSignal } from "solid-js"
  2. import { useLocal } from "@tui/context/local"
  3. import { useSync } from "@tui/context/sync"
  4. import { map, pipe, flatMap, entries, filter, sortBy, take } from "remeda"
  5. import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
  6. import { useDialog } from "@tui/ui/dialog"
  7. import { createDialogProviderOptions, DialogProvider } from "./dialog-provider"
  8. import { Keybind } from "@/util/keybind"
  9. export function useConnected() {
  10. const sync = useSync()
  11. return createMemo(() =>
  12. sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
  13. )
  14. }
  15. export function DialogModel(props: { providerID?: string }) {
  16. const local = useLocal()
  17. const sync = useSync()
  18. const dialog = useDialog()
  19. const [ref, setRef] = createSignal<DialogSelectRef<unknown>>()
  20. const connected = useConnected()
  21. const providers = createDialogProviderOptions()
  22. const showExtra = createMemo(() => {
  23. if (!connected()) return false
  24. if (props.providerID) return false
  25. return true
  26. })
  27. const options = createMemo(() => {
  28. const query = ref()?.filter
  29. const favorites = showExtra() ? local.model.favorite() : []
  30. const recents = local.model.recent()
  31. const recentList = showExtra()
  32. ? recents
  33. .filter(
  34. (item) => !favorites.some((fav) => fav.providerID === item.providerID && fav.modelID === item.modelID),
  35. )
  36. .slice(0, 5)
  37. : []
  38. const favoriteOptions = !query
  39. ? favorites.flatMap((item) => {
  40. const provider = sync.data.provider.find((x) => x.id === item.providerID)
  41. if (!provider) return []
  42. const model = provider.models[item.modelID]
  43. if (!model) return []
  44. return [
  45. {
  46. key: item,
  47. value: {
  48. providerID: provider.id,
  49. modelID: model.id,
  50. },
  51. title: model.name ?? item.modelID,
  52. description: provider.name,
  53. category: "Favorites",
  54. disabled: provider.id === "opencode" && model.id.includes("-nano"),
  55. footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
  56. onSelect: () => {
  57. dialog.clear()
  58. local.model.set(
  59. {
  60. providerID: provider.id,
  61. modelID: model.id,
  62. },
  63. { recent: true },
  64. )
  65. },
  66. },
  67. ]
  68. })
  69. : []
  70. const recentOptions = !query
  71. ? recentList.flatMap((item) => {
  72. const provider = sync.data.provider.find((x) => x.id === item.providerID)
  73. if (!provider) return []
  74. const model = provider.models[item.modelID]
  75. if (!model) return []
  76. return [
  77. {
  78. key: item,
  79. value: {
  80. providerID: provider.id,
  81. modelID: model.id,
  82. },
  83. title: model.name ?? item.modelID,
  84. description: provider.name,
  85. category: "Recent",
  86. disabled: provider.id === "opencode" && model.id.includes("-nano"),
  87. footer: model.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
  88. onSelect: () => {
  89. dialog.clear()
  90. local.model.set(
  91. {
  92. providerID: provider.id,
  93. modelID: model.id,
  94. },
  95. { recent: true },
  96. )
  97. },
  98. },
  99. ]
  100. })
  101. : []
  102. return [
  103. ...favoriteOptions,
  104. ...recentOptions,
  105. ...pipe(
  106. sync.data.provider,
  107. sortBy(
  108. (provider) => provider.id !== "opencode",
  109. (provider) => provider.name,
  110. ),
  111. flatMap((provider) =>
  112. pipe(
  113. provider.models,
  114. entries(),
  115. filter(([_, info]) => info.status !== "deprecated"),
  116. filter(([_, info]) => (props.providerID ? info.providerID === props.providerID : true)),
  117. map(([model, info]) => {
  118. const value = {
  119. providerID: provider.id,
  120. modelID: model,
  121. }
  122. return {
  123. value,
  124. title: info.name ?? model,
  125. description: favorites.some(
  126. (item) => item.providerID === value.providerID && item.modelID === value.modelID,
  127. )
  128. ? "(Favorite)"
  129. : undefined,
  130. category: connected() ? provider.name : undefined,
  131. disabled: provider.id === "opencode" && model.includes("-nano"),
  132. footer: info.cost?.input === 0 && provider.id === "opencode" ? "Free" : undefined,
  133. onSelect() {
  134. dialog.clear()
  135. local.model.set(
  136. {
  137. providerID: provider.id,
  138. modelID: model,
  139. },
  140. { recent: true },
  141. )
  142. },
  143. }
  144. }),
  145. filter((x) => {
  146. if (query) return true
  147. const value = x.value
  148. const inFavorites = favorites.some(
  149. (item) => item.providerID === value.providerID && item.modelID === value.modelID,
  150. )
  151. if (inFavorites) return false
  152. const inRecents = recents.some(
  153. (item) => item.providerID === value.providerID && item.modelID === value.modelID,
  154. )
  155. if (inRecents) return false
  156. return true
  157. }),
  158. sortBy(
  159. (x) => x.footer !== "Free",
  160. (x) => x.title,
  161. ),
  162. ),
  163. ),
  164. ),
  165. ...(!connected()
  166. ? pipe(
  167. providers(),
  168. map((option) => {
  169. return {
  170. ...option,
  171. category: "Popular providers",
  172. }
  173. }),
  174. take(6),
  175. )
  176. : []),
  177. ]
  178. })
  179. const provider = createMemo(() =>
  180. props.providerID ? sync.data.provider.find((x) => x.id === props.providerID) : null,
  181. )
  182. const title = createMemo(() => {
  183. if (provider()) return provider()!.name
  184. return "Select model"
  185. })
  186. return (
  187. <DialogSelect
  188. keybind={[
  189. {
  190. keybind: Keybind.parse("ctrl+a")[0],
  191. title: connected() ? "Connect provider" : "View all providers",
  192. onTrigger() {
  193. dialog.replace(() => <DialogProvider />)
  194. },
  195. },
  196. {
  197. keybind: Keybind.parse("ctrl+f")[0],
  198. title: "Favorite",
  199. disabled: !connected(),
  200. onTrigger: (option) => {
  201. local.model.toggleFavorite(option.value as { providerID: string; modelID: string })
  202. },
  203. },
  204. ]}
  205. ref={setRef}
  206. title={title()}
  207. current={local.model.current()}
  208. options={options()}
  209. />
  210. )
  211. }