models.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import { createMemo } from "solid-js"
  2. import { createStore } from "solid-js/store"
  3. import { DateTime } from "luxon"
  4. import { filter, firstBy, flat, groupBy, mapValues, pipe, uniqueBy, values } from "remeda"
  5. import { createSimpleContext } from "@opencode-ai/ui/context"
  6. import { useProviders } from "@/hooks/use-providers"
  7. import { Persist, persisted } from "@/utils/persist"
  8. export type ModelKey = { providerID: string; modelID: string }
  9. type Visibility = "show" | "hide"
  10. type User = ModelKey & { visibility: Visibility; favorite?: boolean }
  11. type Store = {
  12. user: User[]
  13. recent: ModelKey[]
  14. variant?: Record<string, string | undefined>
  15. }
  16. const RECENT_LIMIT = 5
  17. function modelKey(model: ModelKey) {
  18. return `${model.providerID}:${model.modelID}`
  19. }
  20. export const { use: useModels, provider: ModelsProvider } = createSimpleContext({
  21. name: "Models",
  22. gate: false,
  23. init: () => {
  24. const providers = useProviders()
  25. const [store, setStore, _, ready] = persisted(
  26. Persist.global("model", ["model.v1"]),
  27. createStore<Store>({
  28. user: [],
  29. recent: [],
  30. variant: {},
  31. }),
  32. )
  33. const available = createMemo(() =>
  34. providers.connected().flatMap((p) =>
  35. Object.values(p.models).map((m) => ({
  36. ...m,
  37. provider: p,
  38. })),
  39. ),
  40. )
  41. const release = createMemo(
  42. () =>
  43. new Map(
  44. available().map((model) => {
  45. const parsed = DateTime.fromISO(model.release_date)
  46. return [modelKey({ providerID: model.provider.id, modelID: model.id }), parsed] as const
  47. }),
  48. ),
  49. )
  50. const latest = createMemo(() =>
  51. pipe(
  52. available(),
  53. filter(
  54. (x) =>
  55. Math.abs(
  56. (release().get(modelKey({ providerID: x.provider.id, modelID: x.id })) ?? DateTime.invalid("invalid"))
  57. .diffNow()
  58. .as("months"),
  59. ) < 6,
  60. ),
  61. groupBy((x) => x.provider.id),
  62. mapValues((models) =>
  63. pipe(
  64. models,
  65. groupBy((x) => x.family),
  66. values(),
  67. (groups) =>
  68. groups.flatMap((g) => {
  69. const first = firstBy(g, [(x) => x.release_date, "desc"])
  70. return first ? [{ modelID: first.id, providerID: first.provider.id }] : []
  71. }),
  72. ),
  73. ),
  74. values(),
  75. flat(),
  76. ),
  77. )
  78. const latestSet = createMemo(() => new Set(latest().map((x) => modelKey(x))))
  79. const visibility = createMemo(() => {
  80. const map = new Map<string, Visibility>()
  81. for (const item of store.user) map.set(`${item.providerID}:${item.modelID}`, item.visibility)
  82. return map
  83. })
  84. const list = createMemo(() =>
  85. available().map((m) => ({
  86. ...m,
  87. name: m.name.replace("(latest)", "").trim(),
  88. latest: m.name.includes("(latest)"),
  89. })),
  90. )
  91. const find = (key: ModelKey) => list().find((m) => m.id === key.modelID && m.provider.id === key.providerID)
  92. function update(model: ModelKey, state: Visibility) {
  93. const index = store.user.findIndex((x) => x.modelID === model.modelID && x.providerID === model.providerID)
  94. if (index >= 0) {
  95. setStore("user", index, (current) => ({ ...current, visibility: state }))
  96. return
  97. }
  98. setStore("user", store.user.length, { ...model, visibility: state })
  99. }
  100. const visible = (model: ModelKey) => {
  101. const key = modelKey(model)
  102. const state = visibility().get(key)
  103. if (state === "hide") return false
  104. if (state === "show") return true
  105. if (latestSet().has(key)) return true
  106. const date = release().get(key)
  107. if (!date?.isValid) return true
  108. return false
  109. }
  110. const setVisibility = (model: ModelKey, state: boolean) => {
  111. update(model, state ? "show" : "hide")
  112. }
  113. const push = (model: ModelKey) => {
  114. const uniq = uniqueBy([model, ...store.recent], (x) => `${x.providerID}:${x.modelID}`)
  115. if (uniq.length > RECENT_LIMIT) uniq.pop()
  116. setStore("recent", uniq)
  117. }
  118. const variantKey = (model: ModelKey) => `${model.providerID}/${model.modelID}`
  119. const getVariant = (model: ModelKey) => store.variant?.[variantKey(model)]
  120. const setVariant = (model: ModelKey, value: string | undefined) => {
  121. const key = variantKey(model)
  122. if (!store.variant) {
  123. setStore("variant", { [key]: value })
  124. return
  125. }
  126. setStore("variant", key, value)
  127. }
  128. return {
  129. ready,
  130. list,
  131. find,
  132. visible,
  133. setVisibility,
  134. recent: {
  135. list: createMemo(() => store.recent),
  136. push,
  137. },
  138. variant: {
  139. get: getVariant,
  140. set: setVariant,
  141. },
  142. }
  143. },
  144. })