@lobehub/chat 1.96.19 → 1.97.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 (509) hide show
  1. package/.env.example +7 -0
  2. package/CHANGELOG.md +50 -0
  3. package/apps/desktop/package.json +1 -1
  4. package/apps/desktop/src/main/core/Browser.ts +2 -1
  5. package/apps/desktop/src/main/utils/next-electron-rsc.ts +15 -8
  6. package/changelog/v1.json +18 -0
  7. package/locales/ar/discover.json +452 -12
  8. package/locales/ar/metadata.json +4 -0
  9. package/locales/ar/plugin.json +89 -2
  10. package/locales/ar/setting.json +1 -0
  11. package/locales/bg-BG/discover.json +452 -12
  12. package/locales/bg-BG/metadata.json +4 -0
  13. package/locales/bg-BG/plugin.json +89 -2
  14. package/locales/bg-BG/setting.json +1 -0
  15. package/locales/de-DE/discover.json +452 -12
  16. package/locales/de-DE/metadata.json +4 -0
  17. package/locales/de-DE/plugin.json +89 -2
  18. package/locales/de-DE/setting.json +1 -0
  19. package/locales/en-US/discover.json +452 -12
  20. package/locales/en-US/metadata.json +4 -0
  21. package/locales/en-US/plugin.json +89 -2
  22. package/locales/en-US/setting.json +1 -0
  23. package/locales/es-ES/discover.json +452 -12
  24. package/locales/es-ES/metadata.json +4 -0
  25. package/locales/es-ES/plugin.json +89 -2
  26. package/locales/es-ES/setting.json +1 -0
  27. package/locales/fa-IR/discover.json +452 -12
  28. package/locales/fa-IR/metadata.json +4 -0
  29. package/locales/fa-IR/plugin.json +89 -2
  30. package/locales/fa-IR/setting.json +1 -0
  31. package/locales/fr-FR/discover.json +452 -12
  32. package/locales/fr-FR/metadata.json +4 -0
  33. package/locales/fr-FR/plugin.json +89 -2
  34. package/locales/fr-FR/setting.json +1 -0
  35. package/locales/it-IT/discover.json +452 -12
  36. package/locales/it-IT/metadata.json +4 -0
  37. package/locales/it-IT/plugin.json +89 -2
  38. package/locales/it-IT/setting.json +1 -0
  39. package/locales/ja-JP/discover.json +452 -12
  40. package/locales/ja-JP/metadata.json +4 -0
  41. package/locales/ja-JP/plugin.json +89 -2
  42. package/locales/ja-JP/setting.json +1 -0
  43. package/locales/ko-KR/discover.json +452 -12
  44. package/locales/ko-KR/metadata.json +4 -0
  45. package/locales/ko-KR/plugin.json +89 -2
  46. package/locales/ko-KR/setting.json +1 -0
  47. package/locales/nl-NL/discover.json +452 -12
  48. package/locales/nl-NL/metadata.json +4 -0
  49. package/locales/nl-NL/plugin.json +89 -2
  50. package/locales/nl-NL/setting.json +1 -0
  51. package/locales/pl-PL/discover.json +452 -12
  52. package/locales/pl-PL/metadata.json +4 -0
  53. package/locales/pl-PL/plugin.json +89 -2
  54. package/locales/pl-PL/setting.json +1 -0
  55. package/locales/pt-BR/discover.json +452 -12
  56. package/locales/pt-BR/metadata.json +4 -0
  57. package/locales/pt-BR/plugin.json +89 -2
  58. package/locales/pt-BR/setting.json +1 -0
  59. package/locales/ru-RU/discover.json +452 -12
  60. package/locales/ru-RU/metadata.json +4 -0
  61. package/locales/ru-RU/plugin.json +89 -2
  62. package/locales/ru-RU/setting.json +1 -0
  63. package/locales/tr-TR/discover.json +452 -12
  64. package/locales/tr-TR/metadata.json +4 -0
  65. package/locales/tr-TR/plugin.json +89 -2
  66. package/locales/tr-TR/setting.json +1 -0
  67. package/locales/vi-VN/discover.json +452 -12
  68. package/locales/vi-VN/metadata.json +4 -0
  69. package/locales/vi-VN/plugin.json +89 -2
  70. package/locales/vi-VN/setting.json +1 -0
  71. package/locales/zh-CN/discover.json +452 -12
  72. package/locales/zh-CN/metadata.json +8 -4
  73. package/locales/zh-CN/plugin.json +89 -2
  74. package/locales/zh-CN/setting.json +1 -0
  75. package/locales/zh-TW/discover.json +452 -12
  76. package/locales/zh-TW/metadata.json +4 -0
  77. package/locales/zh-TW/plugin.json +89 -2
  78. package/locales/zh-TW/setting.json +1 -0
  79. package/next.config.ts +70 -19
  80. package/package.json +6 -2
  81. package/scripts/buildSitemapIndex/index.ts +9 -4
  82. package/src/app/(backend)/trpc/lambda/[trpc]/route.ts +5 -0
  83. package/src/app/[variants]/(main)/_layout/Mobile/index.tsx +5 -4
  84. package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatList/WelcomeChatItem/InboxWelcome/AgentsSuggest.tsx +31 -45
  85. package/src/app/[variants]/(main)/discover/(detail)/_layout/Desktop.tsx +8 -6
  86. package/src/app/[variants]/(main)/discover/(detail)/_layout/Mobile/Header.tsx +3 -2
  87. package/src/app/[variants]/(main)/discover/(detail)/_layout/Mobile/index.tsx +5 -1
  88. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/Client.tsx +40 -0
  89. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/DetailProvider.tsx +19 -0
  90. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Capabilities/Block.tsx +27 -0
  91. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Capabilities/Knowledge.tsx +33 -0
  92. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Capabilities/KnowledgeItem.tsx +58 -0
  93. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Capabilities/PluginItem.tsx +68 -0
  94. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Capabilities/Plugins.tsx +32 -0
  95. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Capabilities/index.tsx +37 -0
  96. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Nav.tsx +121 -0
  97. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/TagList.tsx +47 -0
  98. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/index.tsx +96 -0
  99. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Related/index.tsx +31 -0
  100. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/TagList.tsx +47 -0
  101. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/index.tsx +54 -0
  102. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/index.tsx +49 -0
  103. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Header.tsx +176 -0
  104. package/src/app/[variants]/(main)/discover/(detail)/assistant/{[slug]/features → [...slugs]/features/Sidebar/ActionButton}/AddAgent.tsx +22 -21
  105. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/ActionButton/index.tsx +31 -0
  106. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Related/Item.tsx +57 -0
  107. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Related/index.tsx +43 -0
  108. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Summary/index.tsx +38 -0
  109. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/TocList/index.tsx +77 -0
  110. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/index.tsx +46 -0
  111. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/loading.tsx +48 -0
  112. package/src/app/[variants]/(main)/discover/(detail)/{plugin/[slug] → assistant/[...slugs]}/page.tsx +39 -42
  113. package/src/app/[variants]/(main)/discover/(detail)/features/Breadcrumb.tsx +56 -0
  114. package/src/app/[variants]/(main)/discover/(detail)/features/DetailLayout.tsx +0 -1
  115. package/src/app/[variants]/(main)/discover/(detail)/features/MakedownRender.tsx +44 -0
  116. package/src/app/[variants]/(main)/discover/(detail)/features/ShareButton.tsx +4 -3
  117. package/src/app/[variants]/(main)/discover/(detail)/features/Toc/Heading.tsx +108 -0
  118. package/src/app/[variants]/(main)/discover/(detail)/features/Toc/index.tsx +92 -0
  119. package/src/app/[variants]/(main)/discover/(detail)/features/Toc/useToc.tsx +66 -0
  120. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/Client.tsx +43 -0
  121. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Details/Related/index.tsx +32 -0
  122. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Details/Versions/index.tsx +76 -0
  123. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Details/index.tsx +59 -0
  124. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/ActionButton/index.tsx +84 -0
  125. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/ConnectionTypeAlert.tsx +35 -0
  126. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/Related/Item.tsx +57 -0
  127. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/Related/index.tsx +44 -0
  128. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/ServerConfig.tsx +36 -0
  129. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/TocList/index.tsx +98 -0
  130. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/index.tsx +58 -0
  131. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/loading.tsx +1 -0
  132. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/page.tsx +103 -0
  133. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/Client.tsx +40 -0
  134. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/DetailProvider.tsx +19 -0
  135. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Details/Nav.tsx +90 -0
  136. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Details/Overview/ProviderList/index.tsx +179 -0
  137. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Details/Overview/index.tsx +22 -0
  138. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/{ParameterList → Details/Parameter}/ParameterItem.tsx +1 -1
  139. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/{ParameterList → Details/Parameter}/index.tsx +11 -11
  140. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Details/Related/index.tsx +31 -0
  141. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Details/index.tsx +47 -0
  142. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Header.tsx +84 -59
  143. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/ActionButton/ChatWithModel.tsx +92 -0
  144. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/ActionButton/index.tsx +32 -0
  145. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/Related/Item.tsx +60 -0
  146. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/Related/index.tsx +43 -0
  147. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/RelatedProviders/Item.tsx +60 -0
  148. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/RelatedProviders/index.tsx +34 -0
  149. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/index.tsx +44 -0
  150. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/loading.tsx +1 -0
  151. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/page.tsx +22 -45
  152. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/Client.tsx +40 -0
  153. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/DetailProvider.tsx +19 -0
  154. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Details/Guide/index.tsx +25 -0
  155. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Details/Nav.tsx +99 -0
  156. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Details/Overview/ModelList/index.tsx +142 -0
  157. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Details/Overview/index.tsx +23 -0
  158. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Details/Related/index.tsx +22 -0
  159. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Details/index.tsx +47 -0
  160. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Header.tsx +99 -0
  161. package/src/app/[variants]/(main)/discover/(detail)/provider/{[slug]/features → [...slugs]/features/Sidebar/ActionButton}/ProviderConfig.tsx +9 -14
  162. package/src/app/[variants]/(main)/discover/(detail)/provider/{[slug]/features/Actions.tsx → [...slugs]/features/Sidebar/ActionButton/index.tsx} +14 -18
  163. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/Related/Item.tsx +60 -0
  164. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/Related/index.tsx +34 -0
  165. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/RelatedModels/Item.tsx +60 -0
  166. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/RelatedModels/index.tsx +43 -0
  167. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/index.tsx +44 -0
  168. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/loading.tsx +1 -0
  169. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/page.tsx +103 -0
  170. package/src/app/[variants]/(main)/discover/(list)/(home)/Client.tsx +24 -21
  171. package/src/app/[variants]/(main)/discover/(list)/(home)/loading.tsx +1 -1
  172. package/src/app/[variants]/(main)/discover/(list)/(home)/page.tsx +13 -38
  173. package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/Nav.tsx +15 -8
  174. package/src/app/[variants]/(main)/discover/(list)/_layout/Mobile/Header.tsx +1 -1
  175. package/src/app/[variants]/(main)/discover/(list)/_layout/Mobile/Nav.tsx +2 -1
  176. package/src/app/[variants]/(main)/discover/(list)/assistant/Client.tsx +44 -0
  177. package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/index.tsx +84 -0
  178. package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/useCategory.tsx +112 -0
  179. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +189 -0
  180. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/TokenTag.tsx +70 -0
  181. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/index.tsx +33 -0
  182. package/src/app/[variants]/(main)/discover/(list)/assistant/page.tsx +46 -0
  183. package/src/app/[variants]/(main)/discover/(list)/features/Pagination.tsx +67 -0
  184. package/src/app/[variants]/(main)/discover/(list)/features/SortButton/index.tsx +184 -0
  185. package/src/app/[variants]/(main)/discover/(list)/mcp/Client.tsx +44 -0
  186. package/src/app/[variants]/(main)/discover/(list)/mcp/features/Category/index.tsx +83 -0
  187. package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/ConnectionTypeTag.tsx +51 -0
  188. package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/Item.tsx +225 -0
  189. package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/MetaInfo.tsx +33 -0
  190. package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/index.tsx +33 -0
  191. package/src/app/[variants]/(main)/discover/(list)/mcp/page.tsx +46 -0
  192. package/src/app/[variants]/(main)/discover/(list)/model/Client.tsx +44 -0
  193. package/src/app/[variants]/(main)/discover/(list)/{models → model}/_layout/Desktop.tsx +1 -7
  194. package/src/app/[variants]/(main)/discover/(list)/model/features/Category/index.tsx +82 -0
  195. package/src/app/[variants]/(main)/discover/(list)/model/features/Category/useCategory.tsx +41 -0
  196. package/src/app/[variants]/(main)/discover/(list)/model/features/List/Item.tsx +190 -0
  197. package/src/app/[variants]/(main)/discover/(list)/model/features/List/ModelTypeIcon.tsx +39 -0
  198. package/src/app/[variants]/(main)/discover/(list)/model/features/List/index.tsx +33 -0
  199. package/src/app/[variants]/(main)/discover/(list)/model/loading.tsx +1 -0
  200. package/src/app/[variants]/(main)/discover/(list)/model/page.tsx +44 -0
  201. package/src/app/[variants]/(main)/discover/(list)/provider/Client.tsx +43 -0
  202. package/src/app/[variants]/(main)/discover/(list)/provider/features/List/Item.tsx +136 -0
  203. package/src/app/[variants]/(main)/discover/(list)/provider/features/List/index.tsx +33 -0
  204. package/src/app/[variants]/(main)/discover/(list)/provider/loading.tsx +1 -0
  205. package/src/app/[variants]/(main)/discover/(list)/provider/page.tsx +44 -0
  206. package/src/app/[variants]/(main)/discover/_layout/Desktop/Header.tsx +1 -1
  207. package/src/app/[variants]/(main)/discover/components/CategoryContainer.tsx +7 -5
  208. package/src/app/[variants]/(main)/discover/components/CategoryMenu.tsx +28 -30
  209. package/src/app/[variants]/(main)/discover/components/ListLoading.tsx +56 -46
  210. package/src/app/[variants]/(main)/discover/components/Statistic.tsx +5 -6
  211. package/src/app/[variants]/(main)/discover/features/Search.tsx +62 -0
  212. package/src/app/[variants]/(main)/discover/features/Title.tsx +83 -0
  213. package/src/app/[variants]/(main)/discover/features/__tests__/calculateScore.test.ts +185 -0
  214. package/src/app/[variants]/(main)/discover/features/useNav.tsx +32 -12
  215. package/src/app/robots.tsx +7 -5
  216. package/src/app/sitemap.tsx +81 -13
  217. package/src/components/CopyableLabel/index.tsx +37 -0
  218. package/src/components/Descriptions/index.tsx +119 -0
  219. package/src/components/InlineTable/index.tsx +69 -0
  220. package/src/{features/PluginSettings/PluginSettingRender.tsx → components/JSONSchemaConfig/ItemRender.tsx} +3 -3
  221. package/src/components/MCPDepsIcon/Java.tsx +23 -0
  222. package/src/components/MCPDepsIcon/PowerShell.tsx +23 -0
  223. package/src/components/MCPDepsIcon/Terminal.tsx +23 -0
  224. package/src/components/MCPDepsIcon/UV.tsx +23 -0
  225. package/src/components/MCPDepsIcon/index.tsx +72 -0
  226. package/src/components/MCPStdioCommandInput/index.tsx +47 -0
  227. package/src/components/OfficialIcon.tsx +23 -0
  228. package/src/components/Plugins/MCPTag.tsx +18 -0
  229. package/src/components/Plugins/PluginTag.tsx +50 -0
  230. package/src/components/PublishedTime.tsx +71 -0
  231. package/src/config/__tests__/app.test.ts +6 -2
  232. package/src/const/discover.ts +8 -34
  233. package/src/database/models/message.ts +1 -0
  234. package/src/database/models/plugin.ts +13 -3
  235. package/src/envs/app.ts +1 -1
  236. package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
  237. package/src/features/ChatInput/ActionBar/Tools/ToolItem.tsx +1 -1
  238. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +5 -5
  239. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/index.tsx +25 -11
  240. package/src/features/MCP/MCPInstallProgress/InstallError/ErrorDetails.tsx +110 -0
  241. package/src/features/MCP/MCPInstallProgress/InstallError/index.tsx +51 -0
  242. package/src/features/MCP/MCPInstallProgress/MCPConfigForm.tsx +151 -0
  243. package/src/features/MCP/MCPInstallProgress/MCPDependenciesGuide.tsx +217 -0
  244. package/src/features/MCP/MCPInstallProgress/index.tsx +118 -0
  245. package/src/features/MCP/Scores.tsx +268 -0
  246. package/src/features/MCP/calculateScore.ts +324 -0
  247. package/src/features/MCP/useScoreList.ts +126 -0
  248. package/src/features/MCP/utils.ts +225 -0
  249. package/src/features/MCPPluginDetail/CollapseDesc.tsx +44 -0
  250. package/src/features/MCPPluginDetail/CollapseLayout.tsx +34 -0
  251. package/src/features/MCPPluginDetail/Deployment/Platform/index.tsx +47 -0
  252. package/src/features/MCPPluginDetail/Deployment/index.tsx +322 -0
  253. package/src/features/MCPPluginDetail/DetailProvider.tsx +19 -0
  254. package/src/features/MCPPluginDetail/Header.tsx +218 -0
  255. package/src/features/MCPPluginDetail/Nav.tsx +196 -0
  256. package/src/features/MCPPluginDetail/Overview/TagList.tsx +47 -0
  257. package/src/features/MCPPluginDetail/Overview/index.tsx +57 -0
  258. package/src/features/MCPPluginDetail/Schema/Block.tsx +50 -0
  259. package/src/features/MCPPluginDetail/Schema/Prompts.tsx +125 -0
  260. package/src/features/MCPPluginDetail/Schema/Resources.tsx +70 -0
  261. package/src/features/MCPPluginDetail/Schema/Tools.tsx +146 -0
  262. package/src/features/MCPPluginDetail/Schema/index.tsx +63 -0
  263. package/src/features/MCPPluginDetail/Schema/style.ts +9 -0
  264. package/src/features/MCPPluginDetail/Schema/types.ts +4 -0
  265. package/src/features/MCPPluginDetail/Score/GithubBadge/index.tsx +82 -0
  266. package/src/features/MCPPluginDetail/Score/ScoreItem.tsx +34 -0
  267. package/src/features/MCPPluginDetail/Score/ScoreList.tsx +24 -0
  268. package/src/features/MCPPluginDetail/Score/TotalScore.tsx +289 -0
  269. package/src/features/MCPPluginDetail/Score/index.tsx +88 -0
  270. package/src/features/PluginAvatar/index.tsx +1 -1
  271. package/src/features/PluginDetailModal/Meta.tsx +3 -4
  272. package/src/features/PluginDevModal/MCPManifestForm/MCPTypeSelect.tsx +3 -4
  273. package/src/features/PluginDevModal/MCPManifestForm/index.tsx +4 -41
  274. package/src/features/PluginDevModal/PluginPreview/EmptyState.tsx +5 -7
  275. package/src/features/PluginDevModal/PluginPreview/index.tsx +6 -6
  276. package/src/features/PluginSettings/index.tsx +2 -2
  277. package/src/features/PluginStore/AddPluginButton.tsx +1 -1
  278. package/src/features/PluginStore/Content.tsx +59 -0
  279. package/src/features/PluginStore/InstalledList/Detail/CustomPluginEmptyState.tsx +81 -0
  280. package/src/features/PluginStore/InstalledList/Detail/index.tsx +21 -0
  281. package/src/features/PluginStore/InstalledList/EditCustomPlugin.tsx +52 -0
  282. package/src/features/PluginStore/{PluginItem → InstalledList/List/Item}/Action.tsx +33 -13
  283. package/src/features/PluginStore/InstalledList/List/Item/index.tsx +62 -0
  284. package/src/features/PluginStore/InstalledList/List/index.tsx +77 -0
  285. package/src/features/PluginStore/InstalledList/index.tsx +85 -0
  286. package/src/features/PluginStore/Loading.tsx +2 -2
  287. package/src/features/PluginStore/McpList/Detail/Loading.tsx +44 -0
  288. package/src/features/PluginStore/McpList/Detail/Settings/index.tsx +381 -0
  289. package/src/features/PluginStore/McpList/Detail/index.tsx +71 -0
  290. package/src/features/PluginStore/McpList/List/Action.tsx +94 -0
  291. package/src/features/PluginStore/McpList/List/Item.tsx +84 -0
  292. package/src/features/PluginStore/McpList/List/index.tsx +97 -0
  293. package/src/features/PluginStore/McpList/index.tsx +54 -0
  294. package/src/features/PluginStore/PluginList/Detail/DetailProvider.tsx +19 -0
  295. package/src/features/PluginStore/PluginList/Detail/EmptyState.tsx +58 -0
  296. package/src/features/PluginStore/PluginList/Detail/Header.tsx +132 -0
  297. package/src/features/PluginStore/PluginList/Detail/InstallDetail/Nav.tsx +75 -0
  298. package/src/features/PluginStore/PluginList/Detail/InstallDetail/Settings.tsx +19 -0
  299. package/src/features/PluginStore/PluginList/Detail/InstallDetail/Tools.tsx +109 -0
  300. package/src/features/PluginStore/PluginList/Detail/InstallDetail/index.tsx +24 -0
  301. package/src/features/PluginStore/PluginList/Detail/Loading.tsx +44 -0
  302. package/src/features/PluginStore/PluginList/Detail/TagList.tsx +37 -0
  303. package/src/features/PluginStore/PluginList/Detail/index.tsx +39 -0
  304. package/src/features/PluginStore/PluginList/Detail/useCategory.tsx +76 -0
  305. package/src/features/PluginStore/PluginList/List/Action.tsx +72 -0
  306. package/src/features/PluginStore/PluginList/List/Item.tsx +94 -0
  307. package/src/features/PluginStore/PluginList/List/index.tsx +91 -0
  308. package/src/features/PluginStore/PluginList/index.tsx +47 -0
  309. package/src/features/PluginStore/Search/index.tsx +29 -0
  310. package/src/features/PluginStore/VirtuosoLoading.tsx +16 -0
  311. package/src/features/PluginStore/index.tsx +7 -31
  312. package/src/features/PluginTag/index.tsx +6 -4
  313. package/src/hooks/useInterceptingRoutes.test.ts +6 -0
  314. package/src/hooks/useMCPCategory.tsx +133 -0
  315. package/src/hooks/useQuery.ts +8 -0
  316. package/src/hooks/useQueryRoute.test.ts +7 -0
  317. package/src/hooks/useQueryRoute.ts +5 -3
  318. package/src/libs/mcp/client.ts +265 -13
  319. package/src/libs/mcp/types.ts +99 -0
  320. package/src/libs/trpc/client/desktop.ts +2 -3
  321. package/src/libs/trpc/lambda/context.ts +36 -5
  322. package/src/libs/trpc/lambda/index.ts +1 -1
  323. package/src/libs/trpc/lambda/init.ts +8 -1
  324. package/src/locales/default/discover.ts +457 -12
  325. package/src/locales/default/metadata.ts +9 -4
  326. package/src/locales/default/plugin.ts +89 -1
  327. package/src/locales/default/setting.ts +1 -0
  328. package/src/locales/resources.ts +1 -1
  329. package/src/middleware.ts +13 -3
  330. package/src/server/ld.ts +4 -3
  331. package/src/server/modules/AssistantStore/index.test.ts +10 -8
  332. package/src/server/modules/AssistantStore/index.ts +37 -17
  333. package/src/server/modules/PluginStore/index.test.ts +1 -1
  334. package/src/server/modules/PluginStore/index.ts +24 -1
  335. package/src/server/routers/desktop/mcp.ts +36 -2
  336. package/src/server/routers/edge/index.ts +0 -6
  337. package/src/server/routers/{edge → lambda}/config/index.ts +1 -1
  338. package/src/server/routers/lambda/index.ts +4 -0
  339. package/src/server/routers/lambda/market/index.ts +621 -0
  340. package/src/server/routers/lambda/plugin.ts +2 -0
  341. package/src/server/routers/tools/mcp.ts +22 -0
  342. package/src/server/services/discover/index.test.ts +573 -250
  343. package/src/server/services/discover/index.ts +1096 -241
  344. package/src/server/services/mcp/deps/MCPSystemDepsCheckService.ts +238 -0
  345. package/src/server/services/mcp/deps/checkers/ManualInstallationChecker.ts +20 -0
  346. package/src/server/services/mcp/deps/checkers/NpmInstallationChecker.ts +51 -0
  347. package/src/server/services/mcp/deps/checkers/PythonInstallationChecker.ts +69 -0
  348. package/src/server/services/mcp/deps/index.ts +14 -0
  349. package/src/server/services/mcp/deps/types.ts +49 -0
  350. package/src/server/services/mcp/index.ts +251 -33
  351. package/src/server/sitemap.test.ts +203 -46
  352. package/src/server/sitemap.ts +159 -52
  353. package/src/services/__tests__/global.test.ts +4 -7
  354. package/src/services/__tests__/tool.test.ts +1 -29
  355. package/src/services/discover.ts +365 -0
  356. package/src/services/global.ts +3 -3
  357. package/src/services/mcp.ts +131 -10
  358. package/src/services/plugin/_deprecated.ts +1 -1
  359. package/src/services/plugin/type.ts +3 -1
  360. package/src/services/tool.ts +9 -6
  361. package/src/store/chat/slices/plugin/action.test.ts +2 -3
  362. package/src/store/chat/slices/plugin/action.ts +22 -4
  363. package/src/store/discover/index.ts +1 -0
  364. package/src/store/discover/slices/assistant/action.ts +73 -0
  365. package/src/store/discover/slices/assistant/index.ts +1 -0
  366. package/src/store/discover/slices/mcp/action.ts +70 -0
  367. package/src/store/discover/slices/mcp/index.ts +1 -0
  368. package/src/store/discover/slices/model/action.ts +70 -0
  369. package/src/store/discover/slices/model/index.ts +1 -0
  370. package/src/store/discover/slices/plugin/action.ts +76 -0
  371. package/src/store/discover/slices/plugin/index.ts +1 -0
  372. package/src/store/discover/slices/provider/action.ts +61 -0
  373. package/src/store/discover/slices/provider/index.ts +1 -0
  374. package/src/store/discover/store.ts +39 -0
  375. package/src/store/tool/initialState.ts +8 -2
  376. package/src/store/tool/selectors/index.ts +2 -1
  377. package/src/store/tool/selectors/tool.test.ts +3 -1
  378. package/src/store/tool/selectors/tool.ts +14 -2
  379. package/src/store/tool/slices/mcpStore/action.ts +496 -0
  380. package/src/store/tool/slices/mcpStore/initialState.ts +40 -0
  381. package/src/store/tool/slices/mcpStore/selectors.ts +62 -0
  382. package/src/store/tool/slices/{store → oldStore}/action.test.ts +24 -19
  383. package/src/store/tool/slices/oldStore/action.ts +269 -0
  384. package/src/store/tool/slices/oldStore/index.ts +3 -0
  385. package/src/store/tool/slices/oldStore/initialState.ts +54 -0
  386. package/src/store/tool/slices/{store → oldStore}/selectors.test.ts +6 -3
  387. package/src/store/tool/slices/{store → oldStore}/selectors.ts +18 -5
  388. package/src/store/tool/slices/plugin/action.test.ts +2 -1
  389. package/src/store/tool/slices/plugin/action.ts +22 -4
  390. package/src/store/tool/slices/plugin/selectors.test.ts +12 -4
  391. package/src/store/tool/slices/plugin/selectors.ts +20 -9
  392. package/src/store/tool/store.ts +5 -2
  393. package/src/types/discover/assistants.ts +72 -0
  394. package/src/types/discover/index.ts +41 -0
  395. package/src/types/discover/mcp.ts +59 -0
  396. package/src/types/discover/models.ts +51 -0
  397. package/src/types/discover/plugins.ts +52 -0
  398. package/src/types/discover/providers.ts +47 -0
  399. package/src/types/message/tools.ts +3 -0
  400. package/src/types/plugins/index.ts +53 -0
  401. package/src/types/plugins/mcp.ts +188 -0
  402. package/src/types/plugins/mcpDeps.ts +30 -0
  403. package/src/types/tool/index.ts +10 -0
  404. package/src/types/tool/plugin.ts +3 -2
  405. package/src/types/tool/tool.ts +4 -1
  406. package/src/utils/client/cookie.ts +5 -2
  407. package/src/utils/locale.ts +2 -17
  408. package/src/utils/object.ts +10 -0
  409. package/src/utils/server/pageProps.ts +9 -0
  410. package/src/utils/toolCall.ts +5 -1
  411. package/src/utils/toolManifest.ts +1 -2
  412. package/vercel.json +1 -1
  413. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/features/Actions.tsx +0 -35
  414. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/features/ConversationExample/TopicList.tsx +0 -75
  415. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/features/ConversationExample/index.tsx +0 -105
  416. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/features/Header.tsx +0 -115
  417. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/features/InfoSidebar/SuggestionItem.tsx +0 -62
  418. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/features/InfoSidebar/ToolItem.tsx +0 -19
  419. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/features/InfoSidebar/index.tsx +0 -60
  420. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/features/SystemRole.tsx +0 -35
  421. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/features/Temp.tsx +0 -44
  422. package/src/app/[variants]/(main)/discover/(detail)/assistant/[slug]/page.tsx +0 -124
  423. package/src/app/[variants]/(main)/discover/(detail)/loading.tsx +0 -38
  424. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Actions.tsx +0 -40
  425. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/ChatWithModel.tsx +0 -93
  426. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/InfoSidebar/SuggestionItem.tsx +0 -74
  427. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/InfoSidebar/index.tsx +0 -45
  428. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/ProviderList/ProviderItem.tsx +0 -139
  429. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/ProviderList/index.tsx +0 -45
  430. package/src/app/[variants]/(main)/discover/(detail)/plugin/[slug]/features/Actions.tsx +0 -35
  431. package/src/app/[variants]/(main)/discover/(detail)/plugin/[slug]/features/Header.tsx +0 -119
  432. package/src/app/[variants]/(main)/discover/(detail)/plugin/[slug]/features/InfoSidebar/SuggestionItem.tsx +0 -64
  433. package/src/app/[variants]/(main)/discover/(detail)/plugin/[slug]/features/InfoSidebar/index.tsx +0 -45
  434. package/src/app/[variants]/(main)/discover/(detail)/plugin/[slug]/features/InstallPlugin.tsx +0 -83
  435. package/src/app/[variants]/(main)/discover/(detail)/plugin/[slug]/features/ParameterList.tsx +0 -95
  436. package/src/app/[variants]/(main)/discover/(detail)/plugin/[slug]/features/Schema.tsx +0 -23
  437. package/src/app/[variants]/(main)/discover/(detail)/provider/[slug]/features/Header.tsx +0 -73
  438. package/src/app/[variants]/(main)/discover/(detail)/provider/[slug]/features/InfoSidebar/SuggestionItem.tsx +0 -77
  439. package/src/app/[variants]/(main)/discover/(detail)/provider/[slug]/features/InfoSidebar/index.tsx +0 -45
  440. package/src/app/[variants]/(main)/discover/(detail)/provider/[slug]/features/ModelList/ModelItem.tsx +0 -152
  441. package/src/app/[variants]/(main)/discover/(detail)/provider/[slug]/features/ModelList/index.tsx +0 -60
  442. package/src/app/[variants]/(main)/discover/(detail)/provider/[slug]/page.tsx +0 -126
  443. package/src/app/[variants]/(main)/discover/(list)/(home)/features/AssistantList.tsx +0 -33
  444. package/src/app/[variants]/(main)/discover/(list)/(home)/features/ModelList.tsx +0 -19
  445. package/src/app/[variants]/(main)/discover/(list)/(home)/features/PluginList.tsx +0 -25
  446. package/src/app/[variants]/(main)/discover/(list)/assistants/[slug]/page.tsx +0 -75
  447. package/src/app/[variants]/(main)/discover/(list)/assistants/features/Card.tsx +0 -195
  448. package/src/app/[variants]/(main)/discover/(list)/assistants/features/Category.tsx +0 -48
  449. package/src/app/[variants]/(main)/discover/(list)/assistants/features/List.tsx +0 -98
  450. package/src/app/[variants]/(main)/discover/(list)/assistants/features/useCategory.tsx +0 -116
  451. package/src/app/[variants]/(main)/discover/(list)/assistants/page.tsx +0 -64
  452. package/src/app/[variants]/(main)/discover/(list)/loading.tsx +0 -39
  453. package/src/app/[variants]/(main)/discover/(list)/models/[slug]/page.tsx +0 -78
  454. package/src/app/[variants]/(main)/discover/(list)/models/features/Card.tsx +0 -118
  455. package/src/app/[variants]/(main)/discover/(list)/models/features/Category.tsx +0 -67
  456. package/src/app/[variants]/(main)/discover/(list)/models/features/List.tsx +0 -71
  457. package/src/app/[variants]/(main)/discover/(list)/models/loading.tsx +0 -1
  458. package/src/app/[variants]/(main)/discover/(list)/models/page.tsx +0 -73
  459. package/src/app/[variants]/(main)/discover/(list)/plugins/[slug]/page.tsx +0 -75
  460. package/src/app/[variants]/(main)/discover/(list)/plugins/features/Card.tsx +0 -161
  461. package/src/app/[variants]/(main)/discover/(list)/plugins/features/Category.tsx +0 -45
  462. package/src/app/[variants]/(main)/discover/(list)/plugins/features/List.tsx +0 -97
  463. package/src/app/[variants]/(main)/discover/(list)/plugins/features/useCategory.tsx +0 -80
  464. package/src/app/[variants]/(main)/discover/(list)/plugins/page.tsx +0 -64
  465. package/src/app/[variants]/(main)/discover/(list)/providers/features/Card.tsx +0 -119
  466. package/src/app/[variants]/(main)/discover/(list)/providers/features/List.tsx +0 -67
  467. package/src/app/[variants]/(main)/discover/(list)/providers/loading.tsx +0 -1
  468. package/src/app/[variants]/(main)/discover/(list)/providers/page.tsx +0 -64
  469. package/src/app/[variants]/(main)/discover/features/StoreSearchBar.tsx +0 -87
  470. package/src/app/[variants]/(main)/discover/loading.tsx +0 -3
  471. package/src/app/[variants]/(main)/discover/search/_layout/Desktop.tsx +0 -42
  472. package/src/app/[variants]/(main)/discover/search/_layout/Mobile/Header.tsx +0 -31
  473. package/src/app/[variants]/(main)/discover/search/_layout/Mobile/Nav.tsx +0 -56
  474. package/src/app/[variants]/(main)/discover/search/_layout/Mobile/index.tsx +0 -32
  475. package/src/app/[variants]/(main)/discover/search/features/AssistantsResult.tsx +0 -27
  476. package/src/app/[variants]/(main)/discover/search/features/Category.tsx +0 -41
  477. package/src/app/[variants]/(main)/discover/search/features/ModelsResult.tsx +0 -27
  478. package/src/app/[variants]/(main)/discover/search/features/PluginsResult.tsx +0 -27
  479. package/src/app/[variants]/(main)/discover/search/features/ProvidersResult.tsx +0 -26
  480. package/src/app/[variants]/(main)/discover/search/layout.tsx +0 -12
  481. package/src/app/[variants]/(main)/discover/search/loading.tsx +0 -1
  482. package/src/app/[variants]/(main)/discover/search/page.tsx +0 -82
  483. package/src/features/PluginStore/InstalledPluginList.tsx +0 -59
  484. package/src/features/PluginStore/OnlineList.tsx +0 -87
  485. package/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx +0 -55
  486. package/src/features/PluginStore/PluginItem/PluginTag.tsx +0 -29
  487. package/src/features/PluginStore/PluginItem/index.tsx +0 -83
  488. package/src/server/routers/edge/market/index.ts +0 -108
  489. package/src/services/__tests__/assistant.test.ts +0 -87
  490. package/src/services/assistant.ts +0 -25
  491. package/src/store/tool/slices/store/action.ts +0 -113
  492. package/src/store/tool/slices/store/initialState.ts +0 -17
  493. package/src/types/discover.ts +0 -179
  494. package/src/types/requestCache.ts +0 -3
  495. /package/src/app/[variants]/(main)/discover/(list)/{assistants → assistant}/_layout/Desktop.tsx +0 -0
  496. /package/src/app/[variants]/(main)/discover/(list)/{assistants → assistant}/_layout/Mobile.tsx +0 -0
  497. /package/src/app/[variants]/(main)/discover/(list)/{assistants → assistant}/layout.tsx +0 -0
  498. /package/src/app/[variants]/(main)/discover/(list)/{assistants → assistant}/loading.tsx +0 -0
  499. /package/src/app/[variants]/(main)/discover/(list)/{plugins → mcp}/_layout/Desktop.tsx +0 -0
  500. /package/src/app/[variants]/(main)/discover/(list)/{plugins → mcp}/_layout/Mobile.tsx +0 -0
  501. /package/src/app/[variants]/(main)/discover/(list)/{plugins → mcp}/layout.tsx +0 -0
  502. /package/src/app/[variants]/(main)/discover/(list)/{plugins → mcp}/loading.tsx +0 -0
  503. /package/src/app/[variants]/(main)/discover/(list)/{models → model}/_layout/Mobile.tsx +0 -0
  504. /package/src/app/[variants]/(main)/discover/(list)/{models → model}/features/const.ts +0 -0
  505. /package/src/app/[variants]/(main)/discover/(list)/{models → model}/layout.tsx +0 -0
  506. /package/src/{features/PluginStore/PluginItem → components/Plugins}/PluginAvatar.tsx +0 -0
  507. /package/src/server/routers/{edge → lambda}/config/__snapshots__/index.test.ts.snap +0 -0
  508. /package/src/server/routers/{edge → lambda}/config/index.test.ts +0 -0
  509. /package/src/store/tool/slices/{store → mcpStore}/index.ts +0 -0
@@ -1,354 +1,1209 @@
1
- import { cloneDeep, isString, merge, uniqBy } from 'lodash-es';
2
- import pMap from 'p-map';
1
+ import { CategoryItem, CategoryListQuery, MarketSDK } from '@lobehub/market-sdk';
2
+ import { CallReportRequest, InstallReportRequest } from '@lobehub/market-types';
3
+ import dayjs from 'dayjs';
4
+ import debug from 'debug';
5
+ import matter from 'gray-matter';
6
+ import { cloneDeep, countBy, isString, merge, uniq, uniqBy } from 'lodash-es';
7
+ import urlJoin from 'url-join';
3
8
 
9
+ import { LOBE_DEFAULT_MODEL_LIST } from '@/config/aiModels';
4
10
  import { DEFAULT_MODEL_PROVIDER_LIST } from '@/config/modelProviders';
5
11
  import {
6
12
  DEFAULT_DISCOVER_ASSISTANT_ITEM,
7
- DEFAULT_DISCOVER_MODEL_ITEM,
8
13
  DEFAULT_DISCOVER_PLUGIN_ITEM,
9
14
  DEFAULT_DISCOVER_PROVIDER_ITEM,
10
15
  } from '@/const/discover';
11
- import { DEFAULT_LANG } from '@/const/locale';
12
- import { Locales } from '@/locales/resources';
16
+ import { CURRENT_VERSION, isDesktop } from '@/const/version';
17
+ import { normalizeLocale } from '@/locales/resources';
13
18
  import { AssistantStore } from '@/server/modules/AssistantStore';
14
19
  import { PluginStore } from '@/server/modules/PluginStore';
15
20
  import {
16
- AssistantCategory,
21
+ AssistantListResponse,
22
+ AssistantQueryParams,
23
+ AssistantSorts,
24
+ CacheRevalidate,
25
+ CacheTag,
26
+ DiscoverAssistantDetail,
17
27
  DiscoverAssistantItem,
28
+ DiscoverMcpDetail,
29
+ DiscoverModelDetail,
18
30
  DiscoverModelItem,
19
- DiscoverPlugintem,
31
+ DiscoverPluginDetail,
32
+ DiscoverPluginItem,
33
+ DiscoverProviderDetail,
20
34
  DiscoverProviderItem,
21
- PluginCategory,
35
+ IdentifiersResponse,
36
+ McpListResponse,
37
+ McpQueryParams,
38
+ ModelListResponse,
39
+ ModelQueryParams,
40
+ ModelSorts,
41
+ PluginListResponse,
42
+ PluginQueryParams,
43
+ PluginSorts,
44
+ ProviderListResponse,
45
+ ProviderQueryParams,
46
+ ProviderSorts,
22
47
  } from '@/types/discover';
23
- import { getToolManifest } from '@/utils/toolManifest';
24
48
 
25
- const revalidate: number = 3600;
49
+ const log = debug('lobe-server:discover');
26
50
 
27
51
  export class DiscoverService {
28
52
  assistantStore = new AssistantStore();
29
53
  pluginStore = new PluginStore();
54
+ market: MarketSDK;
30
55
 
31
- // Assistants
32
- searchAssistant = async (locale: Locales, keywords: string): Promise<DiscoverAssistantItem[]> => {
33
- const list = await this.getAssistantList(locale);
34
- return list.filter((item) => {
35
- return [item.author, item.meta.title, item.meta.description, item.meta?.tags]
36
- .flat()
37
- .filter(Boolean)
38
- .join(',')
39
- .toLowerCase()
40
- .includes(decodeURIComponent(keywords).toLowerCase());
56
+ constructor({ accessToken }: { accessToken?: string } = {}) {
57
+ this.market = new MarketSDK({
58
+ accessToken,
59
+ baseURL: process.env.MARKET_BASE_URL,
41
60
  });
42
- };
61
+ log('DiscoverService initialized with market baseURL: %s', process.env.MARKET_BASE_URL);
62
+ }
43
63
 
44
- getAssistantCategory = async (
45
- locale: Locales,
46
- category: AssistantCategory,
47
- ): Promise<DiscoverAssistantItem[]> => {
48
- const list = await this.getAssistantList(locale);
49
- return list.filter((item) => item.meta.category === category);
50
- };
64
+ async registerClient({ userAgent }: { userAgent?: string }) {
65
+ const getDeviceId = async (): Promise<string> => {
66
+ // 1. Vercel 环境下使用 VERCEL_PROJECT_ID
67
+ if (process.env.VERCEL_PROJECT_ID) {
68
+ return process.env.VERCEL_PROJECT_ID;
69
+ }
51
70
 
52
- getAssistantList = async (locale: Locales): Promise<DiscoverAssistantItem[]> => {
53
- const json = await this.assistantStore.getAgentIndex(locale, revalidate);
71
+ // 2. 桌面端使用 machine-id
72
+ if (isDesktop) {
73
+ try {
74
+ // 动态导入
75
+ const { machineId } = await import('node-machine-id');
76
+ return await machineId();
77
+ } catch (error) {
78
+ console.error('Failed to get machine-id:', error);
79
+ }
80
+ }
54
81
 
55
- // @ts-expect-error 目前类型不一致,未来要统一
56
- return json.agents ?? [];
57
- };
82
+ return 'unknown-device';
83
+ };
58
84
 
59
- getAssistantById = async (
60
- locale: Locales,
61
- identifier: string,
62
- ): Promise<DiscoverAssistantItem | undefined> => {
63
- let res = await fetch(this.assistantStore.getAgentUrl(identifier, locale), {
64
- next: { revalidate: 12 * revalidate },
85
+ const deviceId = await getDeviceId();
86
+
87
+ const { client_id, client_secret } = await this.market.registerClient({
88
+ clientName: `LobeHub ${isDesktop ? 'Desktop' : 'Web'}`,
89
+ clientType: isDesktop ? 'desktop' : 'web',
90
+ deviceId,
91
+ platform: isDesktop ? process.platform : userAgent,
92
+ version: CURRENT_VERSION,
65
93
  });
66
94
 
67
- if (!res.ok) {
68
- res = await fetch(this.assistantStore.getAgentUrl(DEFAULT_LANG), {
69
- next: { revalidate: 12 * revalidate },
70
- });
71
- }
95
+ return { clientId: client_id, clientSecret: client_secret };
96
+ }
72
97
 
73
- if (!res.ok) return;
98
+ async fetchM2MToken(params: { clientId: string; clientSecret: string }) {
99
+ // 使用传入的客户端凭证创建新的 MarketSDK 实例
100
+ const tokenMarket = new MarketSDK({
101
+ baseURL: process.env.MARKET_BASE_URL,
102
+ clientId: params.clientId,
103
+ clientSecret: params.clientSecret,
104
+ });
74
105
 
75
- let assistant = await res.json();
106
+ const tokenInfo = await tokenMarket.fetchM2MToken();
76
107
 
77
- if (!assistant) return;
108
+ return {
109
+ accessToken: tokenInfo.accessToken,
110
+ expiresIn: tokenInfo.expiresIn,
111
+ };
112
+ }
78
113
 
79
- assistant = merge(cloneDeep(DEFAULT_DISCOVER_ASSISTANT_ITEM), assistant);
114
+ // ============================== Helper Methods ==============================
80
115
 
81
- const categoryItems = await this.getAssistantCategory(
82
- locale,
83
- assistant.meta.category || AssistantCategory.General,
84
- );
116
+ /**
117
+ * 计算 ModelAbilities 的完整度分数
118
+ * 分数越高表示 abilities 越全
119
+ */
120
+ private calculateAbilitiesScore = (abilities?: any): number => {
121
+ if (!abilities) return 0;
85
122
 
86
- assistant = {
87
- ...assistant,
88
- suggestions: categoryItems
89
- .filter((item) => item.identifier !== assistant.identifier)
90
- .slice(0, 5) as any,
123
+ let score = 0;
124
+ const abilityWeights = {
125
+ files: 1,
126
+ functionCall: 1,
127
+ imageOutput: 1,
128
+ reasoning: 1,
129
+ search: 1,
130
+ vision: 1,
91
131
  };
92
132
 
93
- return assistant;
133
+ Object.entries(abilityWeights).forEach(([ability, weight]) => {
134
+ if (abilities[ability]) {
135
+ score += weight;
136
+ }
137
+ });
138
+
139
+ log('calculateAbilitiesScore: abilities=%O, score=%d', abilities, score);
140
+ return score;
94
141
  };
95
142
 
96
- getAssistantByIds = async (
97
- locale: Locales,
98
- identifiers: string[],
99
- ): Promise<DiscoverAssistantItem[]> => {
100
- const list = await pMap(
101
- identifiers,
102
- async (identifier) => this.getAssistantById(locale, identifier),
103
- {
104
- concurrency: 5,
105
- },
143
+ /**
144
+ * 在模型数组中选择 abilities 最全的模型
145
+ * 组合最全的 abilities 和最大的 contextWindowTokens
146
+ */
147
+ private selectModelWithBestAbilities = (models: DiscoverModelItem[]): DiscoverModelItem => {
148
+ log('selectModelWithBestAbilities: input models count=%d', models.length);
149
+ if (models.length === 1) return models[0];
150
+
151
+ // 找到最全的 abilities
152
+ let bestAbilities: Record<string, boolean> = {};
153
+ let maxAbilitiesScore = 0;
154
+ models.forEach((model) => {
155
+ const score = this.calculateAbilitiesScore(model.abilities);
156
+ if (score > maxAbilitiesScore) {
157
+ maxAbilitiesScore = score;
158
+ bestAbilities = { ...(model.abilities as Record<string, boolean>) };
159
+ } else if (score === maxAbilitiesScore && model.abilities) {
160
+ // 合并相同分数的 abilities,确保获得最全的组合
161
+ const abilities = model.abilities as Record<string, boolean>;
162
+ Object.keys(abilities).forEach((key) => {
163
+ if (abilities[key]) {
164
+ bestAbilities[key] = true;
165
+ }
166
+ });
167
+ }
168
+ });
169
+
170
+ // 找到最大的 contextWindowTokens
171
+ const maxContextWindowTokens = Math.max(
172
+ ...models.map((model) => model.contextWindowTokens || 0),
106
173
  );
107
174
 
108
- return list.filter(Boolean) as DiscoverAssistantItem[];
175
+ // 找到最新的 releasedAt
176
+ const latestReleasedAt = models
177
+ .map((model) => model.releasedAt)
178
+ .filter(Boolean)
179
+ .sort((a, b) => new Date(b!).getTime() - new Date(a!).getTime())[0];
180
+
181
+ // 找到最短的 identifier
182
+ const shortestIdentifier = models
183
+ .map((model) => model.identifier)
184
+ .reduce((shortest, current) => (current.length < shortest.length ? current : shortest));
185
+
186
+ // 选择一个基础模型(通常选择第一个)
187
+ const baseModel = models[0];
188
+
189
+ // 组装最终模型,使用最佳的各项属性
190
+ const result: DiscoverModelItem = {
191
+ ...baseModel,
192
+ abilities: bestAbilities as any,
193
+ contextWindowTokens: maxContextWindowTokens || baseModel.contextWindowTokens,
194
+ identifier: shortestIdentifier,
195
+ releasedAt: latestReleasedAt || baseModel.releasedAt,
196
+ };
197
+
198
+ log('selectModelWithBestAbilities: selected model=%O', {
199
+ abilities: result.abilities,
200
+ contextWindowTokens: result.contextWindowTokens,
201
+ identifier: result.identifier,
202
+ releasedAt: result.releasedAt,
203
+ });
204
+ return result;
109
205
  };
110
206
 
111
- // Tools
112
-
113
- searchPlugin = async (locale: Locales, keywords: string): Promise<DiscoverPlugintem[]> => {
114
- const list = await this.getPluginList(locale);
115
- return list.filter((item) => {
116
- return [item.author, item.meta.title, item.meta.description, item.meta?.tags]
117
- .flat()
118
- .filter(Boolean)
119
- .join(',')
120
- .toLowerCase()
121
- .includes(decodeURIComponent(keywords).toLowerCase());
207
+ // ============================== Assistant Market ==============================
208
+
209
+ private _getAssistantList = async (locale?: string): Promise<DiscoverAssistantItem[]> => {
210
+ log('_getAssistantList: locale=%s', locale);
211
+ const normalizedLocale = normalizeLocale(locale);
212
+ const list = await this.assistantStore.getAgentIndex(normalizedLocale);
213
+ if (!list || !Array.isArray(list)) {
214
+ log('_getAssistantList: no valid list found, returning empty array');
215
+ return [];
216
+ }
217
+ const result = list.map(({ meta, ...item }) => ({ ...item, ...meta }));
218
+ log('_getAssistantList: returning %d items', result.length);
219
+ return result;
220
+ };
221
+
222
+ getAssistantCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
223
+ log('getAssistantCategories: params=%O', params);
224
+ const { q, locale } = params;
225
+ let list = await this._getAssistantList(locale);
226
+ if (q) {
227
+ const originalCount = list.length;
228
+ list = list.filter((item) => {
229
+ return [item.author, item.title, item.description, item?.tags]
230
+ .flat()
231
+ .filter(Boolean)
232
+ .join(',')
233
+ .toLowerCase()
234
+ .includes(decodeURIComponent(q).toLowerCase());
235
+ });
236
+ log(
237
+ 'getAssistantCategories: filtered by query "%s", %d -> %d items',
238
+ q,
239
+ originalCount,
240
+ list.length,
241
+ );
242
+ }
243
+ const categoryCounts = countBy(list, (item) => item.category);
244
+ const result = Object.entries(categoryCounts)
245
+ .filter(([category]) => Boolean(category)) // 过滤掉空值
246
+ .map(([category, count]) => ({
247
+ category,
248
+ count,
249
+ }));
250
+ log('getAssistantCategories: returning %d categories', result.length);
251
+ return result;
252
+ };
253
+
254
+ getAssistantDetail = async (params: {
255
+ identifier: string;
256
+ locale?: string;
257
+ }): Promise<DiscoverAssistantDetail | undefined> => {
258
+ log('getAssistantDetail: params=%O', params);
259
+ const { locale, identifier } = params;
260
+ const normalizedLocale = normalizeLocale(locale);
261
+ let data = await this.assistantStore.getAgent(identifier, normalizedLocale);
262
+ if (!data) {
263
+ log('getAssistantDetail: assistant not found for identifier=%s', identifier);
264
+ return;
265
+ }
266
+ const { meta, ...item } = data;
267
+ const assistant = merge(cloneDeep(DEFAULT_DISCOVER_ASSISTANT_ITEM), { ...item, ...meta });
268
+ const list = await this.getAssistantList({
269
+ category: assistant.category,
270
+ locale,
271
+ page: 1,
272
+ pageSize: 7,
122
273
  });
274
+ const result = {
275
+ ...assistant,
276
+ related: list.items.filter((item) => item.identifier !== assistant.identifier).slice(0, 6),
277
+ };
278
+ log('getAssistantDetail: returning assistant with %d related items', result.related.length);
279
+ return result;
123
280
  };
124
281
 
125
- getPluginCategory = async (
126
- locale: Locales,
127
- category: PluginCategory,
128
- ): Promise<DiscoverPlugintem[]> => {
129
- const list = await this.getPluginList(locale);
130
- return list.filter((item) => item.meta.category === category);
282
+ getAssistantIdentifiers = async (): Promise<IdentifiersResponse> => {
283
+ log('getAssistantIdentifiers: fetching identifiers');
284
+ const list = await this._getAssistantList();
285
+ const result = list.map((item) => {
286
+ return {
287
+ identifier: item.identifier,
288
+ lastModified: item.createdAt,
289
+ };
290
+ });
291
+ log('getAssistantIdentifiers: returning %d identifiers', result.length);
292
+ return result;
131
293
  };
132
294
 
133
- getPluginList = async (locale: Locales): Promise<DiscoverPlugintem[]> => {
134
- try {
135
- let res = await fetch(this.pluginStore.getPluginIndexUrl(locale), {
136
- next: { revalidate: 12 * revalidate },
295
+ getAssistantList = async (params: AssistantQueryParams = {}): Promise<AssistantListResponse> => {
296
+ log('getAssistantList: params=%O', params);
297
+ const {
298
+ locale,
299
+ category,
300
+ order = 'desc',
301
+ page = 1,
302
+ pageSize = 20,
303
+ q,
304
+ sort = AssistantSorts.CreatedAt,
305
+ } = params;
306
+ let list = await this._getAssistantList(locale);
307
+ const originalCount = list.length;
308
+
309
+ if (category) {
310
+ list = list.filter((item) => item.category === category);
311
+ log(
312
+ 'getAssistantList: filtered by category "%s", %d -> %d items',
313
+ category,
314
+ originalCount,
315
+ list.length,
316
+ );
317
+ }
318
+
319
+ if (q) {
320
+ const beforeFilter = list.length;
321
+ list = list.filter((item) => {
322
+ return [item.author, item.title, item.description, item?.tags]
323
+ .flat()
324
+ .filter(Boolean)
325
+ .join(',')
326
+ .toLowerCase()
327
+ .includes(decodeURIComponent(q).toLowerCase());
137
328
  });
329
+ log('getAssistantList: filtered by query "%s", %d -> %d items', q, beforeFilter, list.length);
330
+ }
138
331
 
139
- if (!res.ok) {
140
- res = await fetch(this.pluginStore.getPluginIndexUrl(DEFAULT_LANG), {
141
- next: { revalidate: 12 * revalidate },
142
- });
332
+ if (sort) {
333
+ log('getAssistantList: sorting by %s %s', sort, order);
334
+ switch (sort) {
335
+ case AssistantSorts.CreatedAt: {
336
+ list = list.sort((a, b) => {
337
+ if (order === 'asc') {
338
+ return dayjs(a.createdAt).unix() - dayjs(b.createdAt).unix();
339
+ } else {
340
+ return dayjs(b.createdAt).unix() - dayjs(a.createdAt).unix();
341
+ }
342
+ });
343
+ break;
344
+ }
345
+ case AssistantSorts.KnowledgeCount: {
346
+ list = list.sort((a, b) => {
347
+ if (order === 'asc') {
348
+ return a.knowledgeCount - b.knowledgeCount;
349
+ } else {
350
+ return b.knowledgeCount - a.knowledgeCount;
351
+ }
352
+ });
353
+ break;
354
+ }
355
+ case AssistantSorts.PluginCount: {
356
+ list = list.sort((a, b) => {
357
+ if (order === 'asc') {
358
+ return a.pluginCount - b.pluginCount;
359
+ } else {
360
+ return b.pluginCount - a.pluginCount;
361
+ }
362
+ });
363
+ break;
364
+ }
365
+ case AssistantSorts.TokenUsage: {
366
+ list = list.sort((a, b) => {
367
+ if (order === 'asc') {
368
+ return a.tokenUsage - b.tokenUsage;
369
+ } else {
370
+ return b.tokenUsage - a.tokenUsage;
371
+ }
372
+ });
373
+ break;
374
+ }
375
+ case AssistantSorts.Identifier: {
376
+ list = list.sort((a, b) => {
377
+ if (order !== 'desc') {
378
+ return a.identifier.localeCompare(b.identifier);
379
+ } else {
380
+ return b.identifier.localeCompare(a.identifier);
381
+ }
382
+ });
383
+ break;
384
+ }
385
+ case AssistantSorts.Title: {
386
+ list = list.sort((a, b) => {
387
+ if (order === 'desc') {
388
+ return (a.title || a.identifier).localeCompare(b.title || b.identifier);
389
+ } else {
390
+ return (b.title || b.identifier).localeCompare(a.title || a.identifier);
391
+ }
392
+ });
393
+ break;
394
+ }
143
395
  }
396
+ }
144
397
 
145
- if (!res.ok) return [];
398
+ const result = {
399
+ currentPage: page,
400
+ items: list.slice((page - 1) * pageSize, page * pageSize),
401
+ pageSize,
402
+ totalCount: list.length,
403
+ totalPages: Math.ceil(list.length / pageSize),
404
+ };
405
+ log(
406
+ 'getAssistantList: returning page %d/%d with %d items',
407
+ page,
408
+ result.totalPages,
409
+ result.items.length,
410
+ );
411
+ return result;
412
+ };
146
413
 
147
- const json = await res.json();
414
+ // ============================== MCP Market ==============================
148
415
 
149
- return json.plugins ?? [];
150
- } catch (e) {
151
- console.error('[getPluginListError] failed to fetch plugin list, error detail:');
152
- console.error(e);
153
- return [];
154
- }
416
+ getMcpCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
417
+ log('getMcpCategories: params=%O', params);
418
+ const { locale } = params;
419
+ const normalizedLocale = normalizeLocale(locale);
420
+ const result = await this.market.plugins.getCategories(
421
+ {
422
+ ...params,
423
+ locale: normalizedLocale,
424
+ },
425
+ {
426
+ next: {
427
+ revalidate: 3600,
428
+ },
429
+ },
430
+ );
431
+ log('getMcpCategories: returning %d categories', result.length);
432
+ return result;
155
433
  };
156
434
 
157
- getPluginByIds = async (locale: Locales, identifiers: string[]): Promise<DiscoverPlugintem[]> => {
158
- let list = await pMap(
159
- identifiers,
160
- async (identifier) => this.getPluginById(locale, identifier),
435
+ getMcpDetail = async (params: {
436
+ identifier: string;
437
+ locale?: string;
438
+ version?: string;
439
+ }): Promise<DiscoverMcpDetail> => {
440
+ log('getMcpDetail: params=%O', params);
441
+ const { locale } = params;
442
+ const normalizedLocale = normalizeLocale(locale);
443
+ const mcp = await this.market.plugins.getPluginDetail(
444
+ { ...params, locale: normalizedLocale },
161
445
  {
162
- concurrency: 5,
446
+ next: {
447
+ revalidate: 3600,
448
+ },
163
449
  },
164
450
  );
451
+ const list = await this.getMcpList({
452
+ category: mcp.category,
453
+ locale,
454
+ page: 1,
455
+ pageSize: 7,
456
+ });
457
+ const result = {
458
+ ...mcp,
459
+ related: list.items.filter((item) => item.identifier !== mcp.identifier).slice(0, 6),
460
+ };
461
+ log('getMcpDetail: returning mcp with %d related items', result.related.length);
462
+ return result;
463
+ };
165
464
 
166
- return list.filter(Boolean) as DiscoverPlugintem[];
465
+ getMcpIdentifiers = async (): Promise<IdentifiersResponse> => {
466
+ log('getMcpIdentifiers: fetching identifiers');
467
+ const result = await this.market.plugins.getPublishedIdentifiers({
468
+ cache: 'force-cache',
469
+ next: {
470
+ revalidate: CacheRevalidate.List,
471
+ tags: [CacheTag.Discover, CacheTag.MCP],
472
+ },
473
+ });
474
+ log('getMcpIdentifiers: returning %d identifiers', result.length);
475
+ return result;
167
476
  };
168
477
 
169
- getPluginById = async (
170
- locale: Locales,
171
- identifier: string,
172
- withManifest?: boolean,
173
- ): Promise<DiscoverPlugintem | undefined> => {
174
- const list = await this.getPluginList(locale);
175
- let plugin = list.find((item) => item.identifier === identifier) as DiscoverPlugintem;
478
+ getMcpList = async (params: McpQueryParams = {}): Promise<McpListResponse> => {
479
+ log('getMcpList: params=%O', params);
480
+ const { locale } = params;
481
+ const normalizedLocale = normalizeLocale(locale);
482
+ const result = await this.market.plugins.getPluginList(
483
+ {
484
+ ...params,
485
+ locale: normalizedLocale,
486
+ },
487
+ {
488
+ next: {
489
+ revalidate: CacheRevalidate.List,
490
+ tags: [CacheTag.Discover, CacheTag.MCP],
491
+ },
492
+ },
493
+ );
494
+ log('getMcpList: returning %d items on page %d', result.items.length, result.currentPage);
495
+ return result;
496
+ };
176
497
 
177
- if (!plugin) return;
498
+ getMcpManifest = async (params: { identifier: string; locale?: string; version?: string }) => {
499
+ log('getMcpManifest: params=%O', params);
500
+ const { locale } = params;
501
+ const normalizedLocale = normalizeLocale(locale);
502
+ const result = await this.market.plugins.getPluginManifest(
503
+ {
504
+ ...params,
505
+ locale: normalizedLocale,
506
+ },
507
+ {
508
+ next: {
509
+ revalidate: CacheRevalidate.List,
510
+ tags: [CacheTag.Discover, CacheTag.MCP],
511
+ },
512
+ },
513
+ );
514
+ log('getMcpManifest: returning manifest for %s', params.identifier);
515
+ return result;
516
+ };
517
+
518
+ // ============================== MCP Analytics ==============================
519
+
520
+ /**
521
+ * report MCP plugin result marketplace
522
+ */
523
+ reportPluginInstallation = async (params: InstallReportRequest) => {
524
+ await this.market.plugins.reportInstallation(params);
525
+ };
526
+
527
+ /**
528
+ * report plugin call result to marketplace
529
+ */
530
+ reportCall = async (params: CallReportRequest) => {
531
+ await this.market.plugins.reportCall(params);
532
+ };
533
+
534
+ // ============================== Plugin Market ==============================
535
+
536
+ private _getPluginList = async (locale?: string): Promise<DiscoverPluginItem[]> => {
537
+ log('_getPluginList: locale=%s', locale);
538
+ const normalizedLocale = normalizeLocale(locale);
539
+ const list = await this.pluginStore.getPluginList(normalizedLocale);
540
+ if (!list || !Array.isArray(list)) {
541
+ log('_getPluginList: no valid list found, returning empty array');
542
+ return [];
543
+ }
544
+ const result = list.map(({ meta, ...item }) => ({ ...item, ...meta }));
545
+ log('_getPluginList: returning %d items', result.length);
546
+ return result;
547
+ };
178
548
 
179
- plugin = merge(cloneDeep(DEFAULT_DISCOVER_PLUGIN_ITEM), plugin);
549
+ getLegacyPluginList = async ({ locale }: { locale?: string } = {}): Promise<any> => {
550
+ log('getLegacyPluginList: locale=%s', locale);
551
+ const normalizedLocale = normalizeLocale(locale);
552
+ const result = await this.pluginStore.getPluginList(normalizedLocale);
553
+ log('getLegacyPluginList: returning plugin list');
554
+ return result;
555
+ };
180
556
 
181
- if (withManifest) {
182
- const manifest = isString(plugin?.manifest)
183
- ? await getToolManifest(plugin.manifest)
184
- : plugin?.manifest;
557
+ getPluginCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
558
+ log('getPluginCategories: params=%O', params);
559
+ const { q, locale } = params;
560
+ let list = await this._getPluginList(locale);
561
+ if (q) {
562
+ const originalCount = list.length;
563
+ list = list.filter((item) => {
564
+ return [item.author, item.title, item.description, item?.tags]
565
+ .flat()
566
+ .filter(Boolean)
567
+ .join(',')
568
+ .toLowerCase()
569
+ .includes(decodeURIComponent(q).toLowerCase());
570
+ });
571
+ log(
572
+ 'getPluginCategories: filtered by query "%s", %d -> %d items',
573
+ q,
574
+ originalCount,
575
+ list.length,
576
+ );
577
+ }
578
+ const categoryCounts = countBy(list, (item) => item.category);
579
+ const result = Object.entries(categoryCounts)
580
+ .filter(([category]) => Boolean(category)) // 过滤掉空值
581
+ .map(([category, count]) => ({
582
+ category,
583
+ count,
584
+ }));
585
+ log('getPluginCategories: returning %d categories', result.length);
586
+ return result;
587
+ };
185
588
 
186
- plugin = {
187
- ...plugin,
188
- manifest,
189
- } as DiscoverPlugintem;
589
+ getPluginDetail = async (params: {
590
+ identifier: string;
591
+ locale?: string;
592
+ withManifest?: boolean;
593
+ }): Promise<DiscoverPluginDetail | undefined> => {
594
+ log('getPluginDetail: params=%O', params);
595
+ const { locale, identifier, withManifest } = params;
596
+ const all = await this._getPluginList(locale);
597
+ let raw = all.find((item) => item.identifier === identifier);
598
+ if (!raw) {
599
+ log('getPluginDetail: plugin not found for identifier=%s', identifier);
600
+ return;
190
601
  }
191
602
 
192
- const categoryItems = await this.getPluginCategory(
603
+ raw = merge(cloneDeep(DEFAULT_DISCOVER_PLUGIN_ITEM), raw);
604
+ const list = await this.getPluginList({
605
+ category: raw.category,
193
606
  locale,
194
- plugin.meta.category || PluginCategory.Tools,
195
- );
607
+ page: 1,
608
+ pageSize: 7,
609
+ });
196
610
 
197
- plugin = {
198
- ...plugin,
199
- suggestions: categoryItems
200
- .filter((item) => item.identifier !== plugin.identifier)
201
- .slice(0, 5) as any,
202
- } as DiscoverPlugintem;
611
+ let plugin = {
612
+ ...raw,
613
+ related: list.items.filter((item) => item.identifier !== raw.identifier).slice(0, 6),
614
+ };
615
+
616
+ if (!withManifest || !plugin?.manifest || !isString(plugin?.manifest)) {
617
+ log('getPluginDetail: returning plugin without manifest processing');
618
+ return plugin;
619
+ }
620
+
621
+ // 在 Edge Runtime 环境中使用了 Node.js 的 path 模块,但 Edge Runtime 不支持所有 Node.js API
622
+ // 这个函数使用了 @lobehub/chat-plugin-sdk/openapi,该包最终依赖了 @apidevtools/swagger-parser,而这个包在 Edge Runtime 环境中使用了不被支持的 Node.js path 模块。
623
+ // try {
624
+ // const manifest = await getToolManifest(plugin.manifest);
625
+ //
626
+ // return {
627
+ // ...plugin,
628
+ // manifest,
629
+ // };
630
+ // } catch {
631
+ // return plugin;
632
+ // }
203
633
 
204
634
  return plugin;
205
635
  };
206
636
 
207
- // Providers
637
+ getPluginIdentifiers = async (): Promise<IdentifiersResponse> => {
638
+ log('getPluginIdentifiers: fetching identifiers');
639
+ const list = await this._getPluginList();
640
+ const result = list.map((item) => {
641
+ return {
642
+ identifier: item.identifier,
643
+ lastModified: item.createdAt,
644
+ };
645
+ });
646
+ log('getPluginIdentifiers: returning %d identifiers', result.length);
647
+ return result;
648
+ };
649
+
650
+ getPluginList = async (params: PluginQueryParams = {}): Promise<PluginListResponse> => {
651
+ log('getPluginList: params=%O', params);
652
+ const {
653
+ locale,
654
+ category,
655
+ order = 'desc',
656
+ page = 1,
657
+ pageSize = 20,
658
+ q,
659
+ sort = PluginSorts.CreatedAt,
660
+ } = params;
661
+
662
+ let list = await this._getPluginList(locale);
663
+ const originalCount = list.length;
664
+
665
+ if (category) {
666
+ list = list.filter((item) => item.category === category);
667
+ log(
668
+ 'getPluginList: filtered by category "%s", %d -> %d items',
669
+ category,
670
+ originalCount,
671
+ list.length,
672
+ );
673
+ }
674
+
675
+ if (q) {
676
+ const beforeFilter = list.length;
677
+ list = list.filter((item) => {
678
+ return [item.author, item.title, item.description, item?.tags]
679
+ .flat()
680
+ .filter(Boolean)
681
+ .join(',')
682
+ .toLowerCase()
683
+ .includes(decodeURIComponent(q).toLowerCase());
684
+ });
685
+ log('getPluginList: filtered by query "%s", %d -> %d items', q, beforeFilter, list.length);
686
+ }
687
+
688
+ if (sort) {
689
+ log('getPluginList: sorting by %s %s', sort, order);
690
+ switch (sort) {
691
+ case PluginSorts.CreatedAt: {
692
+ list = list.sort((a, b) => {
693
+ if (order === 'asc') {
694
+ return dayjs(a.createdAt).unix() - dayjs(b.createdAt).unix();
695
+ } else {
696
+ return dayjs(b.createdAt).unix() - dayjs(a.createdAt).unix();
697
+ }
698
+ });
699
+ break;
700
+ }
701
+ case PluginSorts.Identifier: {
702
+ list = list.sort((a, b) => {
703
+ if (order === 'desc') {
704
+ return a.identifier.localeCompare(b.identifier);
705
+ } else {
706
+ return b.identifier.localeCompare(a.identifier);
707
+ }
708
+ });
709
+ break;
710
+ }
711
+ case PluginSorts.Title: {
712
+ list = list.sort((a, b) => {
713
+ if (order === 'desc') {
714
+ return a.title.localeCompare(b.title);
715
+ } else {
716
+ return b.title.localeCompare(a.title);
717
+ }
718
+ });
719
+ break;
720
+ }
721
+ }
722
+ }
208
723
 
209
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
210
- getProviderList = async (_locale: Locales): Promise<DiscoverProviderItem[]> => {
211
- const list = DEFAULT_MODEL_PROVIDER_LIST.filter((item) => item.chatModels.length > 0);
212
- return list.map((item) => {
724
+ const result = {
725
+ currentPage: page,
726
+ items: list.slice((page - 1) * pageSize, page * pageSize),
727
+ pageSize,
728
+ totalCount: list.length,
729
+ totalPages: Math.ceil(list.length / pageSize),
730
+ };
731
+ log(
732
+ 'getPluginList: returning page %d/%d with %d items',
733
+ page,
734
+ result.totalPages,
735
+ result.items.length,
736
+ );
737
+ return result;
738
+ };
739
+
740
+ // ============================== Providers ==============================
741
+
742
+ private _getProviderList = async (): Promise<DiscoverProviderItem[]> => {
743
+ log('_getProviderList: fetching provider list');
744
+ const result = DEFAULT_MODEL_PROVIDER_LIST.map((item) => {
745
+ const models = uniq(
746
+ LOBE_DEFAULT_MODEL_LIST.filter((m) => m.providerId === item.id).map((m) => m.id),
747
+ );
213
748
  const provider = {
749
+ ...item,
214
750
  identifier: item.id,
215
- meta: {
216
- ...item,
217
- title: item.name,
218
- },
219
- models: item.chatModels.map((item) => item.id),
751
+ modelCount: models.length,
752
+ models,
220
753
  };
221
- return merge(cloneDeep(DEFAULT_DISCOVER_PROVIDER_ITEM), provider) as DiscoverProviderItem;
754
+ return merge(cloneDeep(DEFAULT_DISCOVER_PROVIDER_ITEM), provider);
222
755
  });
756
+ log('_getProviderList: returning %d providers', result.length);
757
+ return result;
223
758
  };
224
759
 
225
- searchProvider = async (locale: Locales, keywords: string): Promise<DiscoverProviderItem[]> => {
226
- const list = await this.getProviderList(locale);
227
- return list.filter((item) => {
228
- return [item.identifier, item.meta.title]
229
- .filter(Boolean)
230
- .join(',')
231
- .toLowerCase()
232
- .includes(decodeURIComponent(keywords).toLowerCase());
760
+ getProviderDetail = async (params: {
761
+ identifier: string;
762
+ locale?: string;
763
+ withReadme?: boolean;
764
+ }): Promise<DiscoverProviderDetail | undefined> => {
765
+ log('getProviderDetail: params=%O', params);
766
+ const { identifier, locale, withReadme } = params;
767
+ const all = await this._getProviderList();
768
+ let provider = all.find((item) => item.identifier === identifier);
769
+ if (!provider) {
770
+ log('getProviderDetail: provider not found for identifier=%s', identifier);
771
+ return;
772
+ }
773
+
774
+ const list = await this.getProviderList({
775
+ page: 1,
776
+ pageSize: 7,
233
777
  });
234
- };
235
778
 
236
- getProviderById = async (
237
- locale: Locales,
238
- id: string,
239
- ): Promise<DiscoverProviderItem | undefined> => {
240
- const list = await this.getProviderList(locale);
241
- let provider = list.find((item) => item.identifier === id);
779
+ let readme;
242
780
 
243
- if (!provider) return;
781
+ if (withReadme) {
782
+ log('getProviderDetail: fetching readme for provider=%s', identifier);
783
+ try {
784
+ const normalizedLocale = normalizeLocale(locale);
785
+ const readmeUrl = urlJoin(
786
+ 'https://raw.githubusercontent.com/lobehub/lobe-chat/refs/heads/main/docs/usage/providers',
787
+ normalizedLocale === 'zh-CN' ? `${identifier}.zh-CN.mdx` : `${identifier}.mdx`,
788
+ );
789
+ log('getProviderDetail: readme URL=%s', readmeUrl);
790
+ const res = await fetch(readmeUrl, {
791
+ next: {
792
+ tags: [CacheTag.Discover, CacheTag.Providers],
793
+ },
794
+ });
244
795
 
245
- provider = {
796
+ const data = await res.text();
797
+ const { content } = matter(data);
798
+ readme = content.trimEnd();
799
+ log('getProviderDetail: readme loaded successfully, length=%d', readme.length);
800
+ } catch (error) {
801
+ log(
802
+ 'getProviderDetail: failed to load readme for provider=%s, error: %O',
803
+ identifier,
804
+ error,
805
+ );
806
+ }
807
+ }
808
+
809
+ const result = {
246
810
  ...provider,
247
- suggestions: list
248
- .filter((item) => item.identifier !== provider?.identifier)
249
- .slice(0, 5) as any,
811
+ models: uniqBy(
812
+ LOBE_DEFAULT_MODEL_LIST.filter((m) => m.providerId === provider.id),
813
+ (item) => item.id,
814
+ ),
815
+ readme,
816
+ related: list.items.filter((item) => item.identifier !== provider.identifier).slice(0, 6),
250
817
  };
818
+ log(
819
+ 'getProviderDetail: returning provider with %d models and %d related items',
820
+ result.models.length,
821
+ result.related.length,
822
+ );
823
+ return result;
824
+ };
251
825
 
252
- return merge(cloneDeep(DEFAULT_DISCOVER_PROVIDER_ITEM), provider) as DiscoverProviderItem;
826
+ getProviderIdentifiers = async (): Promise<IdentifiersResponse> => {
827
+ log('getProviderIdentifiers: fetching identifiers');
828
+ const list = await this._getProviderList();
829
+ const result = list.map((item) => {
830
+ return {
831
+ identifier: item.identifier,
832
+ lastModified: dayjs().toISOString(),
833
+ };
834
+ });
835
+ log('getProviderIdentifiers: returning %d identifiers', result.length);
836
+ return result;
253
837
  };
254
838
 
255
- getProviderByIds = async (
256
- locale: Locales,
257
- identifiers: string[],
258
- ): Promise<DiscoverProviderItem[]> => {
259
- const list = await pMap(
260
- identifiers,
261
- async (identifier) => this.getProviderById(locale, identifier),
262
- {
263
- concurrency: 5,
264
- },
265
- );
839
+ getProviderList = async (params: ProviderQueryParams = {}): Promise<ProviderListResponse> => {
840
+ log('getProviderList: params=%O', params);
841
+ const { page = 1, pageSize = 20, q, sort = ProviderSorts.Default, order = 'desc' } = params;
842
+ let list = await this._getProviderList();
843
+ const originalCount = list.length;
844
+
845
+ if (q) {
846
+ list = list.filter((item) => {
847
+ return [item.identifier, item.description, item.name]
848
+ .filter(Boolean)
849
+ .join(',')
850
+ .toLowerCase()
851
+ .includes(decodeURIComponent(q).toLowerCase());
852
+ });
853
+ log('getProviderList: filtered by query "%s", %d -> %d items', q, originalCount, list.length);
854
+ }
855
+
856
+ if (sort) {
857
+ log('getProviderList: sorting by %s %s', sort, order);
858
+ switch (sort) {
859
+ case ProviderSorts.Identifier: {
860
+ list = list.sort((a, b) => {
861
+ if (order === 'desc') {
862
+ return a.identifier.localeCompare(b.identifier);
863
+ } else {
864
+ return b.identifier.localeCompare(a.identifier);
865
+ }
866
+ });
867
+ break;
868
+ }
869
+ case ProviderSorts.ModelCount: {
870
+ list = list.sort((a, b) => {
871
+ if (order === 'asc') {
872
+ return a.modelCount - b.modelCount;
873
+ } else {
874
+ return b.modelCount - a.modelCount;
875
+ }
876
+ });
877
+ break;
878
+ }
879
+ }
880
+ }
266
881
 
267
- return list.filter(Boolean) as DiscoverProviderItem[];
882
+ const result = {
883
+ currentPage: page,
884
+ items: list.slice((page - 1) * pageSize, page * pageSize),
885
+ pageSize,
886
+ totalCount: list.length,
887
+ totalPages: Math.ceil(list.length / pageSize),
888
+ };
889
+ log(
890
+ 'getProviderList: returning page %d/%d with %d items',
891
+ page,
892
+ result.totalPages,
893
+ result.items.length,
894
+ );
895
+ return result;
268
896
  };
269
897
 
270
- // Models
271
-
272
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
273
- private _getModelList = async (locale: Locales): Promise<DiscoverModelItem[]> => {
274
- const list = DEFAULT_MODEL_PROVIDER_LIST.filter((item) => item.chatModels.length > 0);
275
- const providers = await this.getProviderList(locale);
276
-
277
- return list.flatMap((provider) => {
278
- return provider.chatModels.map((item) => {
279
- const ids = item.id.split('/')[1] || item.id;
280
- const providerIds = providers
281
- .filter((provider) => provider.models.join('').includes(ids))
282
- .map((provider) => provider.identifier);
283
- const model = {
284
- identifier: item.id,
285
- meta: {
286
- ...item,
287
- category: provider.id,
288
- title: item.displayName || item.id,
289
- },
290
- providers: providerIds,
291
- suggestions: [],
292
- };
293
- return merge(cloneDeep(DEFAULT_DISCOVER_MODEL_ITEM), model) as DiscoverModelItem;
294
- });
898
+ // ============================== Models ==============================
899
+
900
+ private _getRawModelList = async (): Promise<DiscoverModelItem[]> => {
901
+ log('_getRawModelList: fetching raw model list');
902
+ const result = LOBE_DEFAULT_MODEL_LIST.map((item) => {
903
+ const identifier = (item.id.split('/').at(-1) || item.id).toLowerCase();
904
+ const providers = uniq(
905
+ LOBE_DEFAULT_MODEL_LIST.filter(
906
+ (m) =>
907
+ m.id.toLowerCase() === identifier ||
908
+ m.id.includes(`/${identifier}`) ||
909
+ m.displayName?.toLowerCase() === item.displayName?.toLowerCase(),
910
+ ).map((m) => m.providerId),
911
+ );
912
+ const model = {
913
+ ...item,
914
+ category: item.providerId,
915
+ identifier,
916
+ providerCount: providers.length,
917
+ providers,
918
+ };
919
+ // 使用简单的合并而不是 DEFAULT_DISCOVER_MODEL_ITEM,避免类型冲突
920
+ return {
921
+ ...model,
922
+ abilities: model.abilities || {},
923
+ } as DiscoverModelItem;
295
924
  });
925
+ log('_getRawModelList: returning %d raw models', result.length);
926
+ return result;
296
927
  };
297
928
 
298
- getModelList = async (locale: Locales): Promise<DiscoverModelItem[]> => {
299
- const list = await this._getModelList(locale);
929
+ private _getModelList = async (category?: string): Promise<DiscoverModelItem[]> => {
930
+ log('_getModelList: category=%s', category);
931
+ let list = await this._getRawModelList();
932
+ const originalCount = list.length;
933
+
934
+ if (category) {
935
+ list = list.filter((item) => item.providerId === category);
936
+ log(
937
+ '_getModelList: filtered by category "%s", %d -> %d items',
938
+ category,
939
+ originalCount,
940
+ list.length,
941
+ );
942
+ }
300
943
 
301
- return uniqBy(list, (item) => {
302
- const ids = item.identifier.split('/');
303
- return ids[1] || item.identifier;
944
+ // 优化去重逻辑:选择 abilities 最全的模型
945
+ // 1. identifier 分组
946
+ const identifierGroups = new Map<string, DiscoverModelItem[]>();
947
+ list.forEach((item) => {
948
+ const key = item.identifier;
949
+ if (!identifierGroups.has(key)) {
950
+ identifierGroups.set(key, []);
951
+ }
952
+ identifierGroups.get(key)!.push(item);
304
953
  });
305
- };
306
954
 
307
- searchModel = async (locale: Locales, keywords: string): Promise<DiscoverModelItem[]> => {
308
- const list = await this.getModelList(locale);
309
- return list.filter((item) => {
310
- return [item.identifier, item.meta.title, item.meta.description, item.providers]
311
- .flat()
312
- .filter(Boolean)
313
- .join(',')
314
- .toLowerCase()
315
- .includes(decodeURIComponent(keywords).toLowerCase());
955
+ log(
956
+ '_getModelList: grouped %d items into %d identifier groups',
957
+ list.length,
958
+ identifierGroups.size,
959
+ );
960
+
961
+ // 2. 从每个 identifier 组中选择 abilities 最全的
962
+ let deduplicatedByIdentifier = Array.from(identifierGroups.values()).map((models) =>
963
+ this.selectModelWithBestAbilities(models),
964
+ );
965
+
966
+ // 3. 按 displayName 分组
967
+ const displayNameGroups = new Map<string, DiscoverModelItem[]>();
968
+ deduplicatedByIdentifier.forEach((item) => {
969
+ const key = item.displayName?.toLowerCase() || '';
970
+ if (!displayNameGroups.has(key)) {
971
+ displayNameGroups.set(key, []);
972
+ }
973
+ displayNameGroups.get(key)!.push(item);
316
974
  });
975
+
976
+ log(
977
+ '_getModelList: grouped %d items into %d displayName groups',
978
+ deduplicatedByIdentifier.length,
979
+ displayNameGroups.size,
980
+ );
981
+
982
+ // 4. 从每个 displayName 组中选择 abilities 最全的
983
+ const finalList: DiscoverModelItem[] = Array.from(displayNameGroups.values()).map((models) =>
984
+ this.selectModelWithBestAbilities(models),
985
+ );
986
+
987
+ log('_getModelList: returning %d deduplicated models', finalList.length);
988
+ return finalList;
317
989
  };
318
990
 
319
- getModelCategory = async (locale: Locales, category: string): Promise<DiscoverModelItem[]> => {
320
- const list = await this._getModelList(locale);
321
- return list.filter((item) => item.meta.category === category);
991
+ getModelCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
992
+ log('getModelCategories: params=%O', params);
993
+ const { q } = params;
994
+ let list = LOBE_DEFAULT_MODEL_LIST;
995
+ if (q) {
996
+ const originalCount = list.length;
997
+ list = list.filter((item) => {
998
+ return [item.id, item.displayName, item.description]
999
+ .flat()
1000
+ .filter(Boolean)
1001
+ .join(',')
1002
+ .toLowerCase()
1003
+ .includes(decodeURIComponent(q).toLowerCase());
1004
+ });
1005
+ log(
1006
+ 'getModelCategories: filtered by query "%s", %d -> %d items',
1007
+ q,
1008
+ originalCount,
1009
+ list.length,
1010
+ );
1011
+ }
1012
+ const categoryCounts = countBy(list, (item) => item.providerId);
1013
+ const result = Object.entries(categoryCounts)
1014
+ .filter(([category]) => Boolean(category)) // 过滤掉空值
1015
+ .map(([category, count]) => ({
1016
+ category,
1017
+ count,
1018
+ }));
1019
+ log('getModelCategories: returning %d categories', result.length);
1020
+ return result;
322
1021
  };
323
1022
 
324
- getModelById = async (locale: Locales, id: string): Promise<DiscoverModelItem | undefined> => {
325
- const list = await this.getModelList(locale);
326
- let model = list.find((item) => item.identifier === id);
1023
+ getModelDetail = async (params: {
1024
+ identifier: string;
1025
+ }): Promise<DiscoverModelDetail | undefined> => {
1026
+ log('getModelDetail: params=%O', params);
1027
+ const { identifier } = params;
1028
+ const all = await this._getModelList();
1029
+ let model = all.find((item) => item.identifier.toLowerCase() === identifier.toLowerCase());
1030
+
1031
+ if (!model) {
1032
+ log('getModelDetail: model not found in deduplicated list, searching raw list');
1033
+ const raw = await this._getRawModelList();
1034
+ model = raw.find((item) => item.identifier.toLowerCase() === identifier.toLowerCase());
1035
+ }
1036
+
1037
+ if (!model) {
1038
+ log('getModelDetail: model not found for identifier=%s', identifier);
1039
+ return;
1040
+ }
327
1041
 
328
- if (!model) return;
1042
+ const providers = DEFAULT_MODEL_PROVIDER_LIST.filter((item) =>
1043
+ model.providers?.includes(item.id),
1044
+ );
1045
+ log('getModelDetail: found %d providers for model %s', providers.length, model.identifier);
329
1046
 
330
- const categoryItems = model?.meta?.category
331
- ? await this.getModelCategory(locale, model.meta.category)
332
- : [];
1047
+ const list = await this.getModelList({
1048
+ page: 1,
1049
+ pageSize: 7,
1050
+ q: model.identifier.split('-')[0],
1051
+ });
333
1052
 
334
- model = {
1053
+ const result = {
335
1054
  ...model,
336
- suggestions: categoryItems
337
- .filter((item) => item.identifier !== model?.identifier)
338
- .slice(0, 5) as any,
1055
+ providers: providers.map((item) => ({
1056
+ ...item,
1057
+ model: LOBE_DEFAULT_MODEL_LIST.find((m) => {
1058
+ if (m.providerId !== item.id) return false;
1059
+ return (
1060
+ m.id.toLowerCase() === model.identifier.toLowerCase() ||
1061
+ m.id.toLowerCase().includes(`/${model.identifier.toLowerCase()}`) ||
1062
+ m.displayName?.toLowerCase() === model.displayName?.toLowerCase()
1063
+ );
1064
+ }),
1065
+ })),
1066
+ related: list.items
1067
+ .filter(
1068
+ (item) => item.identifier !== model.identifier && item.displayName !== model?.displayName,
1069
+ )
1070
+ .slice(0, 6),
339
1071
  };
1072
+ log(
1073
+ 'getModelDetail: returning model with %d providers and %d related items',
1074
+ result.providers.length,
1075
+ result.related.length,
1076
+ );
1077
+ return result;
1078
+ };
340
1079
 
341
- return merge(cloneDeep(DEFAULT_DISCOVER_MODEL_ITEM), model);
1080
+ getModelIdentifiers = async (): Promise<IdentifiersResponse> => {
1081
+ log('getModelIdentifiers: fetching identifiers');
1082
+ const list = await this._getModelList();
1083
+ const result = list.map((item) => {
1084
+ return {
1085
+ identifier: item.identifier,
1086
+ lastModified: item.releasedAt || dayjs().toISOString(),
1087
+ };
1088
+ });
1089
+ log('getModelIdentifiers: returning %d identifiers', result.length);
1090
+ return result;
342
1091
  };
343
1092
 
344
- getModelByIds = async (locale: Locales, identifiers: string[]): Promise<DiscoverModelItem[]> => {
345
- const list = await pMap(
346
- identifiers,
347
- async (identifier) => this.getModelById(locale, identifier),
348
- {
349
- concurrency: 5,
350
- },
1093
+ getModelList = async (params: ModelQueryParams = {}): Promise<ModelListResponse> => {
1094
+ log('getModelList: params=%O', params);
1095
+ const {
1096
+ category,
1097
+ order = 'desc',
1098
+ page = 1,
1099
+ pageSize = 20,
1100
+ q,
1101
+ sort = ModelSorts.ReleasedAt,
1102
+ } = params;
1103
+ let list = await this._getModelList(category);
1104
+
1105
+ // if (category) {
1106
+ // list = list.filter((item) => item.category === category);
1107
+ // }
1108
+
1109
+ if (q) {
1110
+ const beforeFilter = list.length;
1111
+ list = list.filter((item) => {
1112
+ return [item.identifier, item.displayName, item.description]
1113
+ .flat()
1114
+ .filter(Boolean)
1115
+ .join(',')
1116
+ .toLowerCase()
1117
+ .includes(decodeURIComponent(q).toLowerCase());
1118
+ });
1119
+ log('getModelList: filtered by query "%s", %d -> %d items', q, beforeFilter, list.length);
1120
+ }
1121
+
1122
+ if (sort) {
1123
+ log('getModelList: sorting by %s %s', sort, order);
1124
+ switch (sort) {
1125
+ case ModelSorts.ReleasedAt: {
1126
+ list = list.sort((a, b) => {
1127
+ if (order === 'asc') {
1128
+ return dayjs(a.releasedAt).unix() - dayjs(b.releasedAt).unix();
1129
+ } else {
1130
+ return dayjs(b.releasedAt).unix() - dayjs(a.releasedAt).unix();
1131
+ }
1132
+ });
1133
+ break;
1134
+ }
1135
+ case ModelSorts.Identifier: {
1136
+ list = list.sort((a, b) => {
1137
+ if (order === 'desc') {
1138
+ return a.identifier.localeCompare(b.identifier);
1139
+ } else {
1140
+ return b.identifier.localeCompare(a.identifier);
1141
+ }
1142
+ });
1143
+ break;
1144
+ }
1145
+ case ModelSorts.InputPrice: {
1146
+ list = list.sort((a, b) => {
1147
+ if (order === 'asc') {
1148
+ return (
1149
+ (a.pricing?.input || a.pricing?.audioInput || 0) -
1150
+ (b.pricing?.input || b.pricing?.audioInput || 0)
1151
+ );
1152
+ } else {
1153
+ return (
1154
+ (b.pricing?.input || b.pricing?.audioInput || 0) -
1155
+ (a.pricing?.input || a.pricing?.audioInput || 0)
1156
+ );
1157
+ }
1158
+ });
1159
+ break;
1160
+ }
1161
+ case ModelSorts.OutputPrice: {
1162
+ list = list.sort((a, b) => {
1163
+ if (order === 'asc') {
1164
+ return (a.pricing?.output || 0) - (b.pricing?.output || 0);
1165
+ } else {
1166
+ return (b.pricing?.output || 0) - (a.pricing?.output || 0);
1167
+ }
1168
+ });
1169
+ break;
1170
+ }
1171
+ case ModelSorts.ContextWindowTokens: {
1172
+ list = list.sort((a, b) => {
1173
+ if (order === 'asc') {
1174
+ return (a.contextWindowTokens || 0) - (b.contextWindowTokens || 0);
1175
+ } else {
1176
+ return (b.contextWindowTokens || 0) - (a.contextWindowTokens || 0);
1177
+ }
1178
+ });
1179
+ break;
1180
+ }
1181
+ case ModelSorts.ProviderCount: {
1182
+ list = list.sort((a, b) => {
1183
+ if (order === 'asc') {
1184
+ return a.providerCount - b.providerCount;
1185
+ } else {
1186
+ return b.providerCount - a.providerCount;
1187
+ }
1188
+ });
1189
+ break;
1190
+ }
1191
+ }
1192
+ }
1193
+
1194
+ const result = {
1195
+ currentPage: page,
1196
+ items: list.slice((page - 1) * pageSize, page * pageSize),
1197
+ pageSize,
1198
+ totalCount: list.length,
1199
+ totalPages: Math.ceil(list.length / pageSize),
1200
+ };
1201
+ log(
1202
+ 'getModelList: returning page %d/%d with %d items',
1203
+ page,
1204
+ result.totalPages,
1205
+ result.items.length,
351
1206
  );
352
- return list.filter(Boolean) as DiscoverModelItem[];
1207
+ return result;
353
1208
  };
354
1209
  }