@lobehub/chat 1.43.6 → 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/docs/self-hosting/server-database/docker-compose.mdx +2 -2
  4. package/locales/ar/common.json +1 -0
  5. package/locales/ar/modelProvider.json +176 -0
  6. package/locales/ar/setting.json +1 -0
  7. package/locales/bg-BG/common.json +1 -0
  8. package/locales/bg-BG/modelProvider.json +176 -0
  9. package/locales/bg-BG/setting.json +1 -0
  10. package/locales/de-DE/common.json +1 -0
  11. package/locales/de-DE/modelProvider.json +176 -0
  12. package/locales/de-DE/setting.json +1 -0
  13. package/locales/en-US/common.json +1 -0
  14. package/locales/en-US/modelProvider.json +176 -0
  15. package/locales/en-US/setting.json +1 -0
  16. package/locales/es-ES/common.json +1 -0
  17. package/locales/es-ES/modelProvider.json +176 -0
  18. package/locales/es-ES/setting.json +1 -0
  19. package/locales/fa-IR/common.json +1 -0
  20. package/locales/fa-IR/modelProvider.json +176 -0
  21. package/locales/fa-IR/setting.json +1 -0
  22. package/locales/fr-FR/common.json +1 -0
  23. package/locales/fr-FR/modelProvider.json +176 -0
  24. package/locales/fr-FR/setting.json +1 -0
  25. package/locales/it-IT/common.json +1 -0
  26. package/locales/it-IT/modelProvider.json +176 -0
  27. package/locales/it-IT/setting.json +1 -0
  28. package/locales/ja-JP/common.json +1 -0
  29. package/locales/ja-JP/modelProvider.json +176 -0
  30. package/locales/ja-JP/setting.json +1 -0
  31. package/locales/ko-KR/common.json +1 -0
  32. package/locales/ko-KR/modelProvider.json +176 -0
  33. package/locales/ko-KR/setting.json +1 -0
  34. package/locales/nl-NL/common.json +1 -0
  35. package/locales/nl-NL/modelProvider.json +176 -0
  36. package/locales/nl-NL/setting.json +1 -0
  37. package/locales/pl-PL/common.json +1 -0
  38. package/locales/pl-PL/modelProvider.json +176 -0
  39. package/locales/pl-PL/setting.json +1 -0
  40. package/locales/pt-BR/common.json +1 -0
  41. package/locales/pt-BR/modelProvider.json +176 -0
  42. package/locales/pt-BR/setting.json +1 -0
  43. package/locales/ru-RU/common.json +1 -0
  44. package/locales/ru-RU/modelProvider.json +176 -0
  45. package/locales/ru-RU/setting.json +1 -0
  46. package/locales/tr-TR/common.json +1 -0
  47. package/locales/tr-TR/modelProvider.json +176 -0
  48. package/locales/tr-TR/setting.json +1 -0
  49. package/locales/vi-VN/common.json +1 -0
  50. package/locales/vi-VN/modelProvider.json +176 -0
  51. package/locales/vi-VN/setting.json +1 -0
  52. package/locales/zh-CN/common.json +1 -0
  53. package/locales/zh-CN/modelProvider.json +176 -0
  54. package/locales/zh-CN/setting.json +1 -0
  55. package/locales/zh-TW/common.json +1 -0
  56. package/locales/zh-TW/modelProvider.json +176 -0
  57. package/locales/zh-TW/setting.json +1 -0
  58. package/package.json +4 -4
  59. package/src/app/(main)/(mobile)/me/settings/features/Category.tsx +1 -1
  60. package/src/app/(main)/(mobile)/me/settings/features/useCategory.tsx +12 -5
  61. package/src/app/(main)/changelog/features/VersionTag.tsx +1 -2
  62. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/Thread.tsx +1 -1
  63. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/ThreadItem.tsx +1 -2
  64. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/ChatItem/index.tsx +0 -1
  65. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/InboxWelcome/AgentsSuggest.tsx +1 -1
  66. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/InboxWelcome/QuestionSuggest.tsx +1 -1
  67. package/src/app/(main)/chat/(workspace)/@conversation/features/ZenModeToast/Toast.tsx +1 -1
  68. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ThreadItem/index.tsx +0 -2
  69. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicItem/index.tsx +0 -1
  70. package/src/app/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags.tsx +2 -3
  71. package/src/app/(main)/chat/@session/features/SessionListContent/CollapseGroup/index.tsx +1 -1
  72. package/src/app/(main)/chat/features/Migration/Start.tsx +1 -1
  73. package/src/app/(main)/discover/(detail)/assistant/[slug]/features/ConversationExample/TopicList.tsx +4 -3
  74. package/src/app/(main)/discover/(detail)/assistant/[slug]/features/Header.tsx +1 -1
  75. package/src/app/(main)/discover/(detail)/features/ShareButton.tsx +2 -1
  76. package/src/app/(main)/discover/(detail)/model/[...slugs]/features/Header.tsx +1 -1
  77. package/src/app/(main)/discover/(detail)/plugin/[slug]/features/Header.tsx +1 -1
  78. package/src/app/(main)/discover/(detail)/provider/[slug]/features/Header.tsx +1 -1
  79. package/src/app/(main)/discover/(list)/_layout/Desktop/Nav.tsx +0 -1
  80. package/src/app/(main)/discover/(list)/assistants/features/Card.tsx +1 -1
  81. package/src/app/(main)/discover/(list)/models/features/Card.tsx +1 -1
  82. package/src/app/(main)/discover/(list)/plugins/features/Card.tsx +1 -1
  83. package/src/app/(main)/discover/(list)/providers/features/Card.tsx +1 -1
  84. package/src/app/(main)/discover/components/GridLoadingCard.tsx +2 -1
  85. package/src/app/(main)/discover/components/Title.tsx +1 -1
  86. package/src/app/(main)/files/(content)/@menu/features/KnowledgeBase/EmptyStatus.tsx +1 -1
  87. package/src/app/(main)/files/(content)/@menu/features/KnowledgeBase/Item/index.tsx +0 -1
  88. package/src/app/(main)/files/(content)/@modal/(.)[id]/FullscreenModal.tsx +2 -2
  89. package/src/app/(main)/files/(content)/NotSupportClient.tsx +2 -2
  90. package/src/app/(main)/profile/_layout/Desktop/SideBar.tsx +1 -1
  91. package/src/app/(main)/profile/stats/features/ShareButton/Preview.tsx +5 -5
  92. package/src/app/(main)/repos/[id]/evals/components/Container.tsx +1 -1
  93. package/src/app/(main)/repos/[id]/evals/dataset/DatasetList/Item.tsx +0 -1
  94. package/src/app/(main)/settings/_layout/Desktop/SideBar.tsx +1 -1
  95. package/src/app/(main)/settings/about/features/ItemCard.tsx +3 -3
  96. package/src/app/(main)/settings/about/features/Version.tsx +1 -1
  97. package/src/app/(main)/settings/hooks/useCategory.tsx +22 -9
  98. package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +2 -1
  99. package/src/app/(main)/settings/llm/components/ProviderModelList/ModelFetcher.tsx +0 -1
  100. package/src/app/(main)/settings/provider/(detail)/[id]/index.tsx +19 -0
  101. package/src/app/(main)/settings/provider/(detail)/[id]/page.tsx +95 -0
  102. package/src/app/(main)/settings/provider/(detail)/azure/page.tsx +119 -0
  103. package/src/app/(main)/settings/provider/(detail)/bedrock/page.tsx +91 -0
  104. package/src/app/(main)/settings/provider/(detail)/cloudflare/page.tsx +58 -0
  105. package/src/app/(main)/settings/provider/(detail)/github/page.tsx +67 -0
  106. package/src/app/(main)/settings/provider/(detail)/huggingface/page.tsx +67 -0
  107. package/src/app/(main)/settings/provider/(detail)/ollama/Checker.tsx +73 -0
  108. package/src/app/(main)/settings/provider/(detail)/ollama/page.tsx +34 -0
  109. package/src/app/(main)/settings/provider/(detail)/openai/page.tsx +23 -0
  110. package/src/app/(main)/settings/provider/(detail)/wenxin/page.tsx +61 -0
  111. package/src/app/(main)/settings/provider/(list)/Footer.tsx +36 -0
  112. package/src/app/(main)/settings/provider/(list)/ProviderGrid/Card.tsx +134 -0
  113. package/src/app/(main)/settings/provider/(list)/ProviderGrid/index.tsx +91 -0
  114. package/src/app/(main)/settings/provider/(list)/index.tsx +19 -0
  115. package/src/app/(main)/settings/provider/ProviderMenu/AddNew.tsx +28 -0
  116. package/src/app/(main)/settings/provider/ProviderMenu/All.tsx +29 -0
  117. package/src/app/(main)/settings/provider/ProviderMenu/Item.tsx +69 -0
  118. package/src/app/(main)/settings/provider/ProviderMenu/List.tsx +76 -0
  119. package/src/app/(main)/settings/provider/ProviderMenu/SearchResult.tsx +43 -0
  120. package/src/app/(main)/settings/provider/ProviderMenu/SkeletonList.tsx +60 -0
  121. package/src/app/(main)/settings/provider/ProviderMenu/SortProviderModal/GroupItem.tsx +30 -0
  122. package/src/app/(main)/settings/provider/ProviderMenu/SortProviderModal/index.tsx +91 -0
  123. package/src/app/(main)/settings/provider/ProviderMenu/index.tsx +80 -0
  124. package/src/app/(main)/settings/provider/_layout/Desktop.tsx +37 -0
  125. package/src/app/(main)/settings/provider/_layout/Mobile.tsx +14 -0
  126. package/src/app/(main)/settings/provider/const.ts +20 -0
  127. package/src/app/(main)/settings/provider/features/CreateNewProvider/index.tsx +146 -0
  128. package/src/app/(main)/settings/provider/features/ModelList/CreateNewModelModal/Form.tsx +105 -0
  129. package/src/app/(main)/settings/provider/features/ModelList/CreateNewModelModal/index.tsx +69 -0
  130. package/src/app/(main)/settings/provider/features/ModelList/DisabledModels.tsx +29 -0
  131. package/src/app/(main)/settings/provider/features/ModelList/EmptyModels.tsx +101 -0
  132. package/src/app/(main)/settings/provider/features/ModelList/EnabledModelList/index.tsx +85 -0
  133. package/src/app/(main)/settings/provider/features/ModelList/ModelConfigModal/Form.tsx +109 -0
  134. package/src/app/(main)/settings/provider/features/ModelList/ModelConfigModal/index.tsx +76 -0
  135. package/src/app/(main)/settings/provider/features/ModelList/ModelItem.tsx +346 -0
  136. package/src/app/(main)/settings/provider/features/ModelList/ModelTitle/Search.tsx +37 -0
  137. package/src/app/(main)/settings/provider/features/ModelList/ModelTitle/index.tsx +145 -0
  138. package/src/app/(main)/settings/provider/features/ModelList/SearchResult.tsx +67 -0
  139. package/src/app/(main)/settings/provider/features/ModelList/SkeletonList.tsx +63 -0
  140. package/src/app/(main)/settings/provider/features/ModelList/SortModelModal/ListItem.tsx +20 -0
  141. package/src/app/(main)/settings/provider/features/ModelList/SortModelModal/index.tsx +96 -0
  142. package/src/app/(main)/settings/provider/features/ModelList/index.tsx +59 -0
  143. package/src/app/(main)/settings/provider/features/ProviderConfig/Checker.tsx +120 -0
  144. package/src/app/(main)/settings/provider/features/ProviderConfig/SkeletonInput.tsx +5 -0
  145. package/src/app/(main)/settings/provider/features/ProviderConfig/UpdateProviderInfo/SettingModal.tsx +137 -0
  146. package/src/app/(main)/settings/provider/features/ProviderConfig/UpdateProviderInfo/index.tsx +49 -0
  147. package/src/app/(main)/settings/provider/features/ProviderConfig/index.tsx +343 -0
  148. package/src/app/(main)/settings/provider/layout.tsx +21 -0
  149. package/src/app/(main)/settings/provider/page.tsx +17 -0
  150. package/src/app/(main)/settings/provider/type.ts +5 -0
  151. package/src/app/(main)/settings/sync/features/DeviceInfo/Card.tsx +1 -1
  152. package/src/app/(main)/settings/sync/features/DeviceInfo/index.tsx +1 -1
  153. package/src/app/@modal/(.)changelog/modal/features/ReadDetail.tsx +1 -1
  154. package/src/app/@modal/(.)changelog/modal/features/VersionTag.tsx +1 -2
  155. package/src/app/@modal/(.)changelog/modal/layout.tsx +1 -1
  156. package/src/components/Cell/index.tsx +1 -1
  157. package/src/components/DragUpload/index.tsx +2 -3
  158. package/src/components/FeatureList/index.tsx +1 -1
  159. package/src/components/FileParsingStatus/EmbeddingStatus.tsx +1 -1
  160. package/src/components/FileParsingStatus/index.tsx +1 -1
  161. package/src/components/FunctionModal/style.tsx +2 -2
  162. package/src/components/GoBack/index.tsx +1 -2
  163. package/src/components/HotKeys/index.tsx +1 -1
  164. package/src/components/InstantSwitch/index.tsx +28 -0
  165. package/src/components/Menu/index.tsx +1 -1
  166. package/src/components/ModelSelect/index.tsx +2 -3
  167. package/src/components/Notification/index.tsx +2 -1
  168. package/src/components/StatisticCard/index.tsx +5 -6
  169. package/src/config/aiModels/ai21.ts +38 -0
  170. package/src/config/aiModels/ai360.ts +71 -0
  171. package/src/config/aiModels/anthropic.ts +152 -0
  172. package/src/config/aiModels/azure.ts +86 -0
  173. package/src/config/aiModels/baichuan.ts +107 -0
  174. package/src/config/aiModels/bedrock.ts +315 -0
  175. package/src/config/aiModels/cloudflare.ts +88 -0
  176. package/src/config/aiModels/deepseek.ts +27 -0
  177. package/src/config/aiModels/fireworksai.ts +232 -0
  178. package/src/config/aiModels/giteeai.ts +137 -0
  179. package/src/config/aiModels/github.ts +273 -0
  180. package/src/config/aiModels/google.ts +317 -0
  181. package/src/config/aiModels/groq.ts +202 -0
  182. package/src/config/aiModels/higress.ts +2828 -0
  183. package/src/config/aiModels/huggingface.ts +56 -0
  184. package/src/config/aiModels/hunyuan.ts +151 -0
  185. package/src/config/aiModels/index.ts +98 -0
  186. package/src/config/aiModels/internlm.ts +40 -0
  187. package/src/config/aiModels/minimax.ts +55 -0
  188. package/src/config/aiModels/mistral.ts +172 -0
  189. package/src/config/aiModels/moonshot.ts +44 -0
  190. package/src/config/aiModels/novita.ts +124 -0
  191. package/src/config/aiModels/ollama.ts +412 -0
  192. package/src/config/aiModels/openai.ts +537 -0
  193. package/src/config/aiModels/openrouter.ts +252 -0
  194. package/src/config/aiModels/perplexity.ts +67 -0
  195. package/src/config/aiModels/qwen.ts +302 -0
  196. package/src/config/aiModels/sensenova.ts +114 -0
  197. package/src/config/aiModels/siliconcloud.ts +679 -0
  198. package/src/config/aiModels/spark.ts +68 -0
  199. package/src/config/aiModels/stepfun.ts +153 -0
  200. package/src/config/aiModels/taichu.ts +19 -0
  201. package/src/config/aiModels/togetherai.ts +334 -0
  202. package/src/config/aiModels/upstage.ts +37 -0
  203. package/src/config/aiModels/wenxin.ts +171 -0
  204. package/src/config/aiModels/xai.ts +72 -0
  205. package/src/config/aiModels/zeroone.ts +156 -0
  206. package/src/config/aiModels/zhipu.ts +235 -0
  207. package/src/config/featureFlags/schema.ts +3 -0
  208. package/src/config/modelProviders/anthropic.ts +1 -0
  209. package/src/config/modelProviders/github.ts +0 -1
  210. package/src/config/modelProviders/google.ts +1 -0
  211. package/src/config/modelProviders/stepfun.ts +2 -0
  212. package/src/database/migrations/0013_add_ai_infra.sql +44 -0
  213. package/src/database/migrations/meta/0013_snapshot.json +3598 -0
  214. package/src/database/migrations/meta/_journal.json +7 -0
  215. package/src/database/repositories/aiInfra/index.ts +115 -0
  216. package/src/database/schemas/aiInfra.ts +69 -0
  217. package/src/database/schemas/index.ts +1 -0
  218. package/src/database/server/models/__tests__/aiModel.test.ts +318 -0
  219. package/src/database/server/models/__tests__/aiProvider.test.ts +373 -0
  220. package/src/database/server/models/aiModel.ts +250 -0
  221. package/src/database/server/models/aiProvider.ts +234 -0
  222. package/src/features/AgentSetting/AgentPrompt/index.tsx +2 -2
  223. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +2 -1
  224. package/src/features/ChatInput/ActionBar/Tools/index.tsx +2 -3
  225. package/src/features/ChatInput/ActionBar/Upload/ServerMode.tsx +2 -3
  226. package/src/features/ChatInput/Desktop/FilePreview/FileItem/index.tsx +3 -2
  227. package/src/features/ChatInput/Desktop/FilePreview/FileList.tsx +2 -2
  228. package/src/features/ChatInput/Mobile/Files/FileItem/File.tsx +2 -2
  229. package/src/features/ChatInput/Mobile/InputArea/index.tsx +1 -1
  230. package/src/features/ChatInput/STT/common.tsx +1 -1
  231. package/src/features/Conversation/Error/style.tsx +2 -2
  232. package/src/features/Conversation/Messages/Assistant/FileChunks/Item/style.ts +2 -2
  233. package/src/features/Conversation/Messages/Assistant/FileChunks/index.tsx +1 -1
  234. package/src/features/Conversation/Messages/Assistant/ToolCallItem/Inspector/style.ts +2 -3
  235. package/src/features/Conversation/Messages/Assistant/ToolCallItem/style.ts +2 -3
  236. package/src/features/Conversation/Messages/User/FileListViewer/Item.tsx +0 -1
  237. package/src/features/Conversation/components/BackBottom/style.ts +2 -2
  238. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/Icon.tsx +2 -3
  239. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/index.tsx +3 -3
  240. package/src/features/Conversation/components/MarkdownElements/LobeThinking/Render.tsx +1 -1
  241. package/src/features/Conversation/components/OTPInput.tsx +2 -2
  242. package/src/features/DataImporter/Loading.tsx +1 -1
  243. package/src/features/FileManager/FileList/EmptyStatus.tsx +1 -1
  244. package/src/features/FileManager/FileList/index.tsx +1 -1
  245. package/src/features/FileManager/UploadDock/Item.tsx +1 -1
  246. package/src/features/FileManager/UploadDock/index.tsx +4 -4
  247. package/src/features/FileViewer/NotSupport/index.tsx +1 -1
  248. package/src/features/FileViewer/Renderer/MSDoc/index.tsx +0 -1
  249. package/src/features/FileViewer/Renderer/TXT/index.tsx +1 -1
  250. package/src/features/InitClientDB/EnableModal.tsx +1 -1
  251. package/src/features/InitClientDB/ErrorResult.tsx +1 -1
  252. package/src/features/InitClientDB/InitIndicator.tsx +1 -1
  253. package/src/features/KnowledgeBaseModal/AddFilesToKnowledgeBase/SelectForm.tsx +0 -1
  254. package/src/features/ModelSwitchPanel/index.tsx +2 -2
  255. package/src/features/PluginsUI/Render/Loading.tsx +0 -1
  256. package/src/features/Portal/Home/Body/Files/FileList/Item.tsx +1 -1
  257. package/src/features/Portal/Home/Body/Plugins/ArtifactList/Item/style.ts +1 -2
  258. package/src/features/Setting/SettingContainer.tsx +8 -1
  259. package/src/features/ShareModal/ShareImage/style.ts +2 -2
  260. package/src/features/ShareModal/style.ts +2 -2
  261. package/src/features/User/DataStatistics.tsx +1 -1
  262. package/src/hooks/useEnabledChatModels.ts +10 -1
  263. package/src/hooks/useModelSupportToolUse.ts +15 -0
  264. package/src/hooks/useModelSupportVision.ts +15 -0
  265. package/src/layout/AuthProvider/Clerk/useAppearance.ts +3 -3
  266. package/src/layout/GlobalProvider/AppTheme.tsx +1 -1
  267. package/src/layout/GlobalProvider/StoreInitialization.tsx +5 -0
  268. package/src/locales/default/common.ts +1 -0
  269. package/src/locales/default/modelProvider.ts +178 -0
  270. package/src/locales/default/setting.ts +1 -0
  271. package/src/server/modules/KeyVaultsEncrypt/index.ts +1 -1
  272. package/src/server/routers/lambda/aiModel.ts +128 -0
  273. package/src/server/routers/lambda/aiProvider.ts +127 -0
  274. package/src/server/routers/lambda/index.ts +4 -0
  275. package/src/services/__tests__/_auth.test.ts +16 -49
  276. package/src/services/__tests__/chat.test.ts +2 -0
  277. package/src/services/_auth.ts +42 -25
  278. package/src/services/aiModel.ts +52 -0
  279. package/src/services/aiProvider.ts +47 -0
  280. package/src/services/chat.ts +62 -18
  281. package/src/store/aiInfra/index.ts +2 -0
  282. package/src/store/aiInfra/initialState.ts +11 -0
  283. package/src/store/aiInfra/selectors.ts +2 -0
  284. package/src/store/aiInfra/slices/aiModel/action.ts +146 -0
  285. package/src/store/aiInfra/slices/aiModel/index.ts +3 -0
  286. package/src/store/aiInfra/slices/aiModel/initialState.ts +14 -0
  287. package/src/store/aiInfra/slices/aiModel/selectors.ts +63 -0
  288. package/src/store/aiInfra/slices/aiProvider/action.ts +208 -0
  289. package/src/store/aiInfra/slices/aiProvider/index.ts +3 -0
  290. package/src/store/aiInfra/slices/aiProvider/initialState.ts +32 -0
  291. package/src/store/aiInfra/slices/aiProvider/selectors.ts +99 -0
  292. package/src/store/aiInfra/store.ts +25 -0
  293. package/src/store/global/initialState.ts +1 -0
  294. package/src/store/serverConfig/selectors.test.ts +1 -0
  295. package/src/styles/global.ts +1 -1
  296. package/src/types/aiModel.ts +32 -6
  297. package/src/types/aiProvider.ts +11 -4
  298. package/src/utils/fetch/fetchSSE.ts +3 -1
@@ -91,6 +91,13 @@
91
91
  "when": 1731858381716,
92
92
  "tag": "0012_add_thread",
93
93
  "breakpoints": true
94
+ },
95
+ {
96
+ "idx": 13,
97
+ "version": "7",
98
+ "when": 1735834653361,
99
+ "tag": "0013_add_ai_infra",
100
+ "breakpoints": true
94
101
  }
95
102
  ],
96
103
  "version": "6"
@@ -0,0 +1,115 @@
1
+ import pMap from 'p-map';
2
+
3
+ import { DEFAULT_MODEL_PROVIDER_LIST } from '@/config/modelProviders';
4
+ import { AiModelModel } from '@/database/server/models/aiModel';
5
+ import { AiProviderModel } from '@/database/server/models/aiProvider';
6
+ import { LobeChatDatabase } from '@/database/type';
7
+ import { AIChatModelCard, AiModelSourceEnum, AiProviderModelListItem } from '@/types/aiModel';
8
+ import { AiProviderListItem, EnabledAiModel } from '@/types/aiProvider';
9
+ import { ProviderConfig } from '@/types/user/settings';
10
+ import { mergeArrayById } from '@/utils/merge';
11
+
12
+ export class AiInfraRepos {
13
+ private userId: string;
14
+ private db: LobeChatDatabase;
15
+ aiProviderModel: AiProviderModel;
16
+ private providerConfigs: Record<string, ProviderConfig>;
17
+ private aiModelModel: AiModelModel;
18
+
19
+ constructor(
20
+ db: LobeChatDatabase,
21
+ userId: string,
22
+ providerConfigs: Record<string, ProviderConfig>,
23
+ ) {
24
+ this.userId = userId;
25
+ this.db = db;
26
+ this.aiProviderModel = new AiProviderModel(db, userId);
27
+ this.aiModelModel = new AiModelModel(db, userId);
28
+ this.providerConfigs = providerConfigs;
29
+ }
30
+
31
+ /**
32
+ * Calculate the final providerList based on the known providerConfig
33
+ */
34
+ getAiProviderList = async () => {
35
+ const userProviders = await this.aiProviderModel.getAiProviderList();
36
+
37
+ // 1. 先创建一个基于 DEFAULT_MODEL_PROVIDER_LIST id 顺序的映射
38
+ const orderMap = new Map(DEFAULT_MODEL_PROVIDER_LIST.map((item, index) => [item.id, index]));
39
+
40
+ const builtinProviders = DEFAULT_MODEL_PROVIDER_LIST.map((item) => ({
41
+ description: item.description,
42
+ enabled:
43
+ userProviders.some((provider) => provider.id === item.id && provider.enabled) ||
44
+ this.providerConfigs[item.id]?.enabled,
45
+ id: item.id,
46
+ name: item.name,
47
+ source: 'builtin',
48
+ })) as AiProviderListItem[];
49
+
50
+ const mergedProviders = mergeArrayById(builtinProviders, userProviders);
51
+
52
+ // 3. 根据 orderMap 排序
53
+ return mergedProviders.sort((a, b) => {
54
+ const orderA = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
55
+ const orderB = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
56
+ return orderA - orderB;
57
+ });
58
+ };
59
+
60
+ getUserEnabledProviderList = async () => {
61
+ const list = await this.getAiProviderList();
62
+ return list
63
+ .filter((item) => item.enabled)
64
+ .sort((a, b) => a.sort! - b.sort!)
65
+ .map((item) => ({ id: item.id, name: item.name, source: item.source }));
66
+ };
67
+
68
+ getEnabledModels = async () => {
69
+ const providers = await this.getAiProviderList();
70
+ const enabledProviders = providers.filter((item) => item.enabled);
71
+
72
+ const userEnabledModels = await this.aiModelModel.getEnabledModels();
73
+ const modelList = await pMap(
74
+ enabledProviders,
75
+ async (provider) => {
76
+ const aiModels = await this.fetchBuiltinModels(provider.id);
77
+
78
+ return (aiModels || [])
79
+ .filter((i) => i.enabled)
80
+ .map<EnabledAiModel>((item) => ({
81
+ ...item,
82
+ abilities: item.abilities || {},
83
+ providerId: provider.id,
84
+ }));
85
+ },
86
+ { concurrency: 10 },
87
+ );
88
+
89
+ return [...modelList.flat(), ...userEnabledModels] as EnabledAiModel[];
90
+ };
91
+
92
+ getAiProviderModelList = async (providerId: string) => {
93
+ const aiModels = await this.aiModelModel.getModelListByProviderId(providerId);
94
+
95
+ const defaultModels: AiProviderModelListItem[] =
96
+ (await this.fetchBuiltinModels(providerId)) || [];
97
+
98
+ return mergeArrayById(defaultModels, aiModels) as AiProviderModelListItem[];
99
+ };
100
+
101
+ private fetchBuiltinModels = async (
102
+ providerId: string,
103
+ ): Promise<AiProviderModelListItem[] | undefined> => {
104
+ try {
105
+ const { default: providerModels } = await import(`@/config/aiModels/${providerId}`);
106
+ return (providerModels as AIChatModelCard[]).map<AiProviderModelListItem>((m) => ({
107
+ ...m,
108
+ enabled: m.enabled || false,
109
+ source: AiModelSourceEnum.Builtin,
110
+ }));
111
+ } catch {
112
+ // maybe provider id not exist
113
+ }
114
+ };
115
+ }
@@ -0,0 +1,69 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
2
+ import { boolean, integer, jsonb, pgTable, primaryKey, text, varchar } from 'drizzle-orm/pg-core';
3
+
4
+ import { timestamps } from '@/database/schemas/_helpers';
5
+ import { users } from '@/database/schemas/user';
6
+ import { AiProviderSettings } from '@/types/aiProvider';
7
+
8
+ export const aiProviders = pgTable(
9
+ 'ai_providers',
10
+ {
11
+ id: varchar('id', { length: 64 }).notNull(),
12
+ name: text('name'),
13
+
14
+ userId: text('user_id')
15
+ .references(() => users.id, { onDelete: 'cascade' })
16
+ .notNull(),
17
+
18
+ sort: integer('sort'),
19
+ enabled: boolean('enabled'),
20
+ fetchOnClient: boolean('fetch_on_client'),
21
+ checkModel: text('check_model'),
22
+ logo: text('logo'),
23
+ description: text('description'),
24
+
25
+ // need to be encrypted
26
+ keyVaults: text('key_vaults'),
27
+ source: varchar('source', { enum: ['builtin', 'custom'], length: 20 }),
28
+ settings: jsonb('settings')
29
+ .$defaultFn(() => ({}))
30
+ .$type<AiProviderSettings>(),
31
+
32
+ ...timestamps,
33
+ },
34
+ (table) => [primaryKey({ columns: [table.id, table.userId] })],
35
+ );
36
+
37
+ export type NewAiProviderItem = Omit<typeof aiProviders.$inferInsert, 'userId'>;
38
+ export type AiProviderSelectItem = typeof aiProviders.$inferSelect;
39
+
40
+ export const aiModels = pgTable(
41
+ 'ai_models',
42
+ {
43
+ id: varchar('id', { length: 150 }).notNull(),
44
+ displayName: varchar('display_name', { length: 200 }),
45
+ description: text('description'),
46
+ organization: varchar('organization', { length: 100 }),
47
+ enabled: boolean('enabled'),
48
+ providerId: varchar('provider_id', { length: 64 }).notNull(),
49
+ type: varchar('type', { length: 20 }).default('chat').notNull(),
50
+ sort: integer('sort'),
51
+
52
+ userId: text('user_id')
53
+ .references(() => users.id, { onDelete: 'cascade' })
54
+ .notNull(),
55
+ pricing: jsonb('pricing'),
56
+ parameters: jsonb('parameters').default({}),
57
+ config: jsonb('config'),
58
+ abilities: jsonb('abilities').default({}),
59
+ contextWindowTokens: integer('context_window_tokens'),
60
+ source: varchar('source', { enum: ['remote', 'custom', 'builtin'], length: 20 }),
61
+ releasedAt: varchar('released_at', { length: 10 }),
62
+
63
+ ...timestamps,
64
+ },
65
+ (table) => [primaryKey({ columns: [table.id, table.providerId, table.userId] })],
66
+ );
67
+
68
+ export type NewAiModelItem = Omit<typeof aiModels.$inferInsert, 'userId'>;
69
+ export type AiModelSelectItem = typeof aiModels.$inferSelect;
@@ -1,4 +1,5 @@
1
1
  export * from './agent';
2
+ export * from './aiInfra';
2
3
  export * from './asyncTask';
3
4
  export * from './file';
4
5
  export * from './message';
@@ -0,0 +1,318 @@
1
+ // @vitest-environment node
2
+ import { eq } from 'drizzle-orm/expressions';
3
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
+
5
+ import { getTestDBInstance } from '@/database/server/core/dbForTest';
6
+ import { AiProviderModelListItem } from '@/types/aiModel';
7
+
8
+ import { AiModelSelectItem, NewAiModelItem, aiModels, users } from '../../../schemas';
9
+ import { AiModelModel } from '../aiModel';
10
+
11
+ let serverDB = await getTestDBInstance();
12
+
13
+ const userId = 'ai-model-test-user-id';
14
+ const aiProviderModel = new AiModelModel(serverDB, userId);
15
+
16
+ beforeEach(async () => {
17
+ await serverDB.delete(users);
18
+ await serverDB.insert(users).values([{ id: userId }, { id: 'user2' }]);
19
+ });
20
+
21
+ afterEach(async () => {
22
+ await serverDB.delete(users).where(eq(users.id, userId));
23
+ await serverDB.delete(aiModels).where(eq(aiModels.userId, userId));
24
+ });
25
+
26
+ describe('AiModelModel', () => {
27
+ describe('create', () => {
28
+ it('should create a new ai provider', async () => {
29
+ const params: NewAiModelItem = {
30
+ organization: 'Qwen',
31
+ id: 'qvq',
32
+ providerId: 'openai',
33
+ };
34
+
35
+ const result = await aiProviderModel.create(params);
36
+ expect(result.id).toBeDefined();
37
+ expect(result).toMatchObject({ ...params, userId });
38
+
39
+ const group = await serverDB.query.aiModels.findFirst({
40
+ where: eq(aiModels.id, result.id),
41
+ });
42
+ expect(group).toMatchObject({ ...params, userId });
43
+ });
44
+ });
45
+ describe('delete', () => {
46
+ it('should delete a ai provider by id', async () => {
47
+ const { id } = await aiProviderModel.create({
48
+ organization: 'Qwen',
49
+ providerId: 'openai',
50
+ id: 'qvq',
51
+ });
52
+
53
+ await aiProviderModel.delete(id, 'openai');
54
+
55
+ const group = await serverDB.query.aiModels.findFirst({
56
+ where: eq(aiModels.id, id),
57
+ });
58
+ expect(group).toBeUndefined();
59
+ });
60
+ });
61
+ describe('deleteAll', () => {
62
+ it('should delete all ai providers for the user', async () => {
63
+ await aiProviderModel.create({ organization: 'Qwen', providerId: 'openai', id: 'qvq' });
64
+ await aiProviderModel.create({
65
+ organization: 'Qwen',
66
+ providerId: 'openai',
67
+ id: 'aihubmix-2',
68
+ });
69
+
70
+ await aiProviderModel.deleteAll();
71
+
72
+ const userGroups = await serverDB.query.aiModels.findMany({
73
+ where: eq(aiModels.userId, userId),
74
+ });
75
+ expect(userGroups).toHaveLength(0);
76
+ });
77
+ it('should only delete ai providers for the user, not others', async () => {
78
+ await aiProviderModel.create({ organization: 'Qwen', providerId: 'openai', id: 'qvq' });
79
+ await aiProviderModel.create({
80
+ organization: 'Qwen',
81
+ providerId: 'openai',
82
+ id: 'aihubmix-2',
83
+ });
84
+
85
+ const anotherAiModelModel = new AiModelModel(serverDB, 'user2');
86
+ await anotherAiModelModel.create({ id: 'qvq', providerId: 'openai' });
87
+
88
+ await aiProviderModel.deleteAll();
89
+
90
+ const userGroups = await serverDB.query.aiModels.findMany({
91
+ where: eq(aiModels.userId, userId),
92
+ });
93
+ const total = await serverDB.query.aiModels.findMany();
94
+ expect(userGroups).toHaveLength(0);
95
+ expect(total).toHaveLength(1);
96
+ });
97
+ });
98
+
99
+ describe('query', () => {
100
+ it('should query ai providers for the user', async () => {
101
+ await aiProviderModel.create({ organization: 'Qwen', providerId: 'openai', id: 'qvq' });
102
+ await aiProviderModel.create({
103
+ organization: 'Qwen',
104
+ providerId: 'openai',
105
+ id: 'aihubmix-2',
106
+ });
107
+
108
+ const userGroups = await aiProviderModel.query();
109
+ expect(userGroups).toHaveLength(2);
110
+ expect(userGroups[0].id).toBe('aihubmix-2');
111
+ expect(userGroups[1].id).toBe('qvq');
112
+ });
113
+ });
114
+
115
+ describe('findById', () => {
116
+ it('should find a ai provider by id', async () => {
117
+ const { id } = await aiProviderModel.create({
118
+ organization: 'Qwen',
119
+ providerId: 'openai',
120
+ id: 'qvq',
121
+ });
122
+
123
+ const group = await aiProviderModel.findById(id);
124
+ expect(group).toMatchObject({
125
+ id,
126
+ organization: 'Qwen',
127
+ providerId: 'openai',
128
+
129
+ userId,
130
+ });
131
+ });
132
+ });
133
+
134
+ describe('update', () => {
135
+ it('should update a ai provider', async () => {
136
+ const { id } = await aiProviderModel.create({
137
+ organization: 'Qwen',
138
+ providerId: 'openai',
139
+ id: 'qvq',
140
+ });
141
+
142
+ await aiProviderModel.update(id, 'openai', {
143
+ displayName: 'Updated Test Group',
144
+ contextWindowTokens: 3000,
145
+ });
146
+
147
+ const updatedGroup = await serverDB.query.aiModels.findFirst({
148
+ where: eq(aiModels.id, id),
149
+ });
150
+ expect(updatedGroup).toMatchObject({
151
+ id,
152
+ displayName: 'Updated Test Group',
153
+ contextWindowTokens: 3000,
154
+ userId,
155
+ });
156
+ });
157
+ });
158
+
159
+ describe('getModelListByProviderId', () => {
160
+ it('should get model list by provider id', async () => {
161
+ await aiProviderModel.create({
162
+ id: 'model1',
163
+ providerId: 'openai',
164
+ sort: 1,
165
+ enabled: true,
166
+ });
167
+ await aiProviderModel.create({
168
+ id: 'model2',
169
+ providerId: 'openai',
170
+ sort: 2,
171
+ enabled: false,
172
+ });
173
+
174
+ const models = await aiProviderModel.getModelListByProviderId('openai');
175
+ expect(models).toHaveLength(2);
176
+ expect(models[0].id).toBe('model1');
177
+ expect(models[1].id).toBe('model2');
178
+ });
179
+
180
+ it('should only return models for specified provider', async () => {
181
+ await aiProviderModel.create({
182
+ id: 'model1',
183
+ providerId: 'openai',
184
+ });
185
+ await aiProviderModel.create({
186
+ id: 'model2',
187
+ providerId: 'anthropic',
188
+ });
189
+
190
+ const models = await aiProviderModel.getModelListByProviderId('openai');
191
+ expect(models).toHaveLength(1);
192
+ expect(models[0].id).toBe('model1');
193
+ });
194
+ });
195
+
196
+ describe('getEnabledModels', () => {
197
+ it('should only return enabled models', async () => {
198
+ await serverDB.insert(aiModels).values([
199
+ { id: 'model1', providerId: 'openai', enabled: true, source: 'custom', userId },
200
+ { id: 'model2', providerId: 'openai', enabled: false, source: 'custom', userId },
201
+ ]);
202
+
203
+ const models = await aiProviderModel.getEnabledModels();
204
+ expect(models).toHaveLength(1);
205
+ expect(models[0].id).toBe('model1');
206
+ expect(models[0].enabled).toBe(true);
207
+ });
208
+ });
209
+
210
+ describe('toggleModelEnabled', () => {
211
+ it('should toggle model enabled status', async () => {
212
+ const model = await aiProviderModel.create({
213
+ id: 'model1',
214
+ providerId: 'openai',
215
+ enabled: true,
216
+ });
217
+
218
+ await aiProviderModel.toggleModelEnabled({
219
+ id: model.id,
220
+ providerId: 'openai',
221
+ enabled: false,
222
+ });
223
+
224
+ const updatedModel = await aiProviderModel.findById(model.id);
225
+ expect(updatedModel?.enabled).toBe(false);
226
+ });
227
+ });
228
+
229
+ describe('batchUpdateAiModels', () => {
230
+ it('should insert new models and update existing ones', async () => {
231
+ // Create an initial model
232
+ await aiProviderModel.create({
233
+ id: 'existing-model',
234
+ providerId: 'openai',
235
+ displayName: 'Old Name',
236
+ });
237
+
238
+ const models = [
239
+ {
240
+ id: 'existing-model',
241
+ displayName: 'Updated Name',
242
+ },
243
+ {
244
+ id: 'new-model',
245
+ displayName: 'New Model',
246
+ },
247
+ ] as AiProviderModelListItem[];
248
+
249
+ await aiProviderModel.batchUpdateAiModels('openai', models);
250
+
251
+ const allModels = await aiProviderModel.query();
252
+ expect(allModels).toHaveLength(2);
253
+ expect(allModels.find((m) => m.id === 'existing-model')?.displayName).toBe('Updated Name');
254
+ expect(allModels.find((m) => m.id === 'new-model')?.displayName).toBe('New Model');
255
+ });
256
+ });
257
+
258
+ describe('batchToggleAiModels', () => {
259
+ it('should toggle multiple models enabled status', async () => {
260
+ await aiProviderModel.create({
261
+ id: 'model1',
262
+ providerId: 'openai',
263
+ enabled: false,
264
+ });
265
+ await aiProviderModel.create({
266
+ id: 'model2',
267
+ providerId: 'openai',
268
+ enabled: false,
269
+ });
270
+
271
+ await aiProviderModel.batchToggleAiModels('openai', ['model1', 'model2'], true);
272
+
273
+ const models = await aiProviderModel.query();
274
+ expect(models.every((m) => m.enabled)).toBe(true);
275
+ });
276
+ });
277
+
278
+ describe('clearRemoteModels', () => {
279
+ it('should delete all remote models for a provider', async () => {
280
+ await serverDB.insert(aiModels).values([
281
+ { id: 'remote1', providerId: 'openai', source: 'remote', userId },
282
+ { id: 'custom1', providerId: 'openai', source: 'custom', userId },
283
+ ]);
284
+
285
+ await aiProviderModel.clearRemoteModels('openai');
286
+
287
+ const remainingModels = await aiProviderModel.query();
288
+ expect(remainingModels).toHaveLength(1);
289
+ expect(remainingModels[0].id).toBe('custom1');
290
+ });
291
+ });
292
+
293
+ describe('updateModelsOrder', () => {
294
+ it('should update the sort order of models', async () => {
295
+ await aiProviderModel.create({
296
+ id: 'model1',
297
+ providerId: 'openai',
298
+ sort: 1,
299
+ });
300
+ await aiProviderModel.create({
301
+ id: 'model2',
302
+ providerId: 'openai',
303
+ sort: 2,
304
+ });
305
+
306
+ const sortMap = [
307
+ { id: 'model1', sort: 2 },
308
+ { id: 'model2', sort: 1 },
309
+ ];
310
+
311
+ await aiProviderModel.updateModelsOrder('openai', sortMap);
312
+
313
+ const models = await aiProviderModel.getModelListByProviderId('openai');
314
+ expect(models[0].id).toBe('model2');
315
+ expect(models[1].id).toBe('model1');
316
+ });
317
+ });
318
+ });