models.tsx 4.7 KB

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