@lobehub/lobehub 2.0.0-next.337 → 2.0.0-next.339

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 (270) hide show
  1. package/.gitattributes +35 -0
  2. package/CHANGELOG.md +69 -0
  3. package/changelog/v1.json +24 -0
  4. package/locales/ar/plugin.json +12 -2
  5. package/locales/ar/providers.json +1 -0
  6. package/locales/ar/setting.json +77 -1
  7. package/locales/bg-BG/models.json +5 -10
  8. package/locales/bg-BG/plugin.json +12 -2
  9. package/locales/bg-BG/providers.json +1 -0
  10. package/locales/bg-BG/setting.json +78 -2
  11. package/locales/de-DE/models.json +51 -9
  12. package/locales/de-DE/plugin.json +12 -2
  13. package/locales/de-DE/providers.json +1 -0
  14. package/locales/de-DE/setting.json +78 -2
  15. package/locales/en-US/models.json +11 -10
  16. package/locales/en-US/plugin.json +14 -4
  17. package/locales/en-US/providers.json +1 -0
  18. package/locales/en-US/setting.json +97 -2
  19. package/locales/es-ES/plugin.json +12 -2
  20. package/locales/es-ES/providers.json +1 -0
  21. package/locales/es-ES/setting.json +78 -2
  22. package/locales/fa-IR/plugin.json +12 -2
  23. package/locales/fa-IR/providers.json +1 -0
  24. package/locales/fa-IR/setting.json +78 -2
  25. package/locales/fr-FR/plugin.json +12 -2
  26. package/locales/fr-FR/providers.json +1 -0
  27. package/locales/fr-FR/setting.json +78 -2
  28. package/locales/it-IT/plugin.json +12 -2
  29. package/locales/it-IT/providers.json +1 -0
  30. package/locales/it-IT/setting.json +78 -2
  31. package/locales/ja-JP/plugin.json +12 -2
  32. package/locales/ja-JP/providers.json +1 -0
  33. package/locales/ja-JP/setting.json +78 -2
  34. package/locales/ko-KR/plugin.json +12 -2
  35. package/locales/ko-KR/providers.json +1 -0
  36. package/locales/ko-KR/setting.json +78 -2
  37. package/locales/nl-NL/models.json +4 -9
  38. package/locales/nl-NL/plugin.json +12 -2
  39. package/locales/nl-NL/providers.json +1 -0
  40. package/locales/nl-NL/setting.json +78 -2
  41. package/locales/pl-PL/plugin.json +12 -2
  42. package/locales/pl-PL/providers.json +1 -0
  43. package/locales/pl-PL/setting.json +78 -2
  44. package/locales/pt-BR/plugin.json +12 -2
  45. package/locales/pt-BR/providers.json +1 -0
  46. package/locales/pt-BR/setting.json +78 -2
  47. package/locales/ru-RU/plugin.json +12 -2
  48. package/locales/ru-RU/providers.json +1 -0
  49. package/locales/ru-RU/setting.json +78 -2
  50. package/locales/tr-TR/plugin.json +12 -2
  51. package/locales/tr-TR/providers.json +1 -0
  52. package/locales/tr-TR/setting.json +78 -2
  53. package/locales/vi-VN/plugin.json +12 -2
  54. package/locales/vi-VN/providers.json +1 -0
  55. package/locales/vi-VN/setting.json +77 -1
  56. package/locales/zh-CN/auth.json +1 -0
  57. package/locales/zh-CN/plugin.json +12 -2
  58. package/locales/zh-CN/providers.json +1 -0
  59. package/locales/zh-CN/setting.json +97 -2
  60. package/locales/zh-TW/plugin.json +12 -2
  61. package/locales/zh-TW/providers.json +1 -0
  62. package/locales/zh-TW/setting.json +78 -2
  63. package/package.json +1 -1
  64. package/packages/agent-runtime/src/groupOrchestration/GroupOrchestrationSupervisor.ts +2 -0
  65. package/packages/agent-runtime/src/groupOrchestration/__tests__/GroupOrchestrationSupervisor.test.ts +3 -1
  66. package/packages/agent-runtime/src/groupOrchestration/types.ts +5 -0
  67. package/packages/const/src/index.ts +1 -0
  68. package/packages/const/src/klavis.ts +144 -0
  69. package/packages/const/src/lobehubSkill.ts +34 -0
  70. package/packages/const/src/recommendedSkill.ts +17 -0
  71. package/packages/model-runtime/src/core/contextBuilders/anthropic.test.ts +38 -0
  72. package/packages/model-runtime/src/core/contextBuilders/anthropic.ts +20 -1
  73. package/packages/model-runtime/src/core/contextBuilders/google.test.ts +42 -0
  74. package/packages/model-runtime/src/core/contextBuilders/google.ts +17 -0
  75. package/packages/model-runtime/src/providers/google/index.ts +14 -14
  76. package/packages/model-runtime/src/providers/moonshot/index.ts +1 -1
  77. package/packages/model-runtime/src/providers/openai/index.ts +3 -3
  78. package/packages/types/src/discover/index.ts +1 -1
  79. package/scripts/electronWorkflow/modifiers/dynamicToStatic.mts +273 -0
  80. package/scripts/electronWorkflow/modifiers/index.mts +10 -0
  81. package/scripts/electronWorkflow/modifiers/nextConfig.mts +1 -0
  82. package/scripts/electronWorkflow/modifiers/nextDynamicToStatic.mts +233 -0
  83. package/scripts/electronWorkflow/modifiers/removeSuspense.mts +124 -0
  84. package/scripts/electronWorkflow/modifiers/routes.mts +14 -2
  85. package/scripts/electronWorkflow/modifiers/settingsContentToStatic.mts +148 -0
  86. package/scripts/electronWorkflow/modifiers/wrapChildrenWithClientOnly.mts +73 -0
  87. package/src/app/[variants]/(main)/agent/cron/[cronId]/CronConfig.ts +16 -16
  88. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobSaveButton.tsx +1 -1
  89. package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobScheduleConfig.tsx +5 -2
  90. package/src/app/[variants]/(main)/community/features/Search.tsx +1 -1
  91. package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +131 -0
  92. package/src/app/[variants]/(main)/home/features/InputArea/index.tsx +34 -27
  93. package/src/app/[variants]/(main)/settings/features/SettingHeader.tsx +8 -4
  94. package/src/app/[variants]/(main)/settings/features/SettingsContent.tsx +3 -0
  95. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +6 -0
  96. package/src/{features/PluginStore/InstalledList/List/Item/Action.tsx → app/[variants]/(main)/settings/skill/features/Actions.tsx} +45 -40
  97. package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +353 -0
  98. package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +344 -0
  99. package/src/app/[variants]/(main)/settings/skill/features/McpSkillItem.tsx +116 -0
  100. package/src/app/[variants]/(main)/settings/skill/features/SkillList.tsx +244 -0
  101. package/src/app/[variants]/(main)/settings/skill/index.tsx +35 -0
  102. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +27 -0
  103. package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +8 -17
  104. package/src/app/[variants]/(mobile)/settings/_layout/index.tsx +6 -1
  105. package/src/app/[variants]/(mobile)/settings/provider/_layout/index.tsx +22 -0
  106. package/src/components/Plugins/PluginTag.tsx +23 -35
  107. package/src/components/client/ClientOnly.tsx +6 -2
  108. package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
  109. package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +8 -32
  110. package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +8 -30
  111. package/src/features/ChatInput/ActionBar/Tools/PopoverContent.tsx +48 -59
  112. package/src/features/ChatInput/ActionBar/Tools/index.tsx +5 -23
  113. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +158 -56
  114. package/src/features/IntegrationDetailModal/index.tsx +293 -0
  115. package/src/features/{PluginStore/McpList/Detail → MCP/MCPDetail}/index.tsx +15 -6
  116. package/src/features/MCP/MCPSettings/McpSettingsModal.tsx +58 -0
  117. package/src/features/{PluginStore/McpList/Detail/Settings → MCP/MCPSettings}/index.tsx +39 -27
  118. package/src/features/PluginDetailModal/index.tsx +2 -2
  119. package/src/features/PluginDevModal/index.tsx +16 -40
  120. package/src/features/ProfileEditor/AgentTool.tsx +2 -2
  121. package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/index.tsx +1 -1
  122. package/src/features/{PluginStore/AddPluginButton.tsx → SkillStore/AddSkillButton.tsx} +3 -3
  123. package/src/features/SkillStore/CommunityList/Item.tsx +158 -0
  124. package/src/features/SkillStore/CommunityList/index.tsx +101 -0
  125. package/src/features/SkillStore/Content.tsx +59 -0
  126. package/src/features/{PluginStore/PluginEmpty.tsx → SkillStore/Empty.tsx} +8 -8
  127. package/src/features/SkillStore/LobeHubList/Item.tsx +118 -0
  128. package/src/features/SkillStore/LobeHubList/index.tsx +187 -0
  129. package/src/features/SkillStore/LobeHubList/useSkillConnect.ts +239 -0
  130. package/src/features/SkillStore/Search/index.tsx +43 -0
  131. package/src/features/{PluginStore → SkillStore}/index.tsx +14 -10
  132. package/src/features/SkillStore/style.ts +27 -0
  133. package/src/locales/default/plugin.ts +15 -4
  134. package/src/locales/default/setting.ts +204 -2
  135. package/src/services/chat/mecha/agentConfigResolver.test.ts +197 -0
  136. package/src/services/chat/mecha/agentConfigResolver.ts +44 -17
  137. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +40 -37
  138. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +78 -0
  139. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +50 -16
  140. package/src/store/global/initialState.ts +1 -0
  141. package/src/store/tool/slices/lobehubSkillStore/action.test.ts +914 -0
  142. package/src/store/tool/slices/lobehubSkillStore/selectors.test.ts +548 -0
  143. package/.cursor/skills/vercel-react-best-practices/AGENTS.md +0 -2410
  144. package/.cursor/skills/vercel-react-best-practices/SKILL.md +0 -125
  145. package/.cursor/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
  146. package/.cursor/skills/vercel-react-best-practices/rules/advanced-use-latest.md +0 -49
  147. package/.cursor/skills/vercel-react-best-practices/rules/async-api-routes.md +0 -38
  148. package/.cursor/skills/vercel-react-best-practices/rules/async-defer-await.md +0 -80
  149. package/.cursor/skills/vercel-react-best-practices/rules/async-dependencies.md +0 -36
  150. package/.cursor/skills/vercel-react-best-practices/rules/async-parallel.md +0 -28
  151. package/.cursor/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
  152. package/.cursor/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -59
  153. package/.cursor/skills/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
  154. package/.cursor/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
  155. package/.cursor/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
  156. package/.cursor/skills/vercel-react-best-practices/rules/bundle-preload.md +0 -50
  157. package/.cursor/skills/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
  158. package/.cursor/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
  159. package/.cursor/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
  160. package/.cursor/skills/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
  161. package/.cursor/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -57
  162. package/.cursor/skills/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
  163. package/.cursor/skills/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
  164. package/.cursor/skills/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
  165. package/.cursor/skills/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
  166. package/.cursor/skills/vercel-react-best-practices/rules/js-early-exit.md +0 -50
  167. package/.cursor/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
  168. package/.cursor/skills/vercel-react-best-practices/rules/js-index-maps.md +0 -37
  169. package/.cursor/skills/vercel-react-best-practices/rules/js-length-check-first.md +0 -49
  170. package/.cursor/skills/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
  171. package/.cursor/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
  172. package/.cursor/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
  173. package/.cursor/skills/vercel-react-best-practices/rules/rendering-activity.md +0 -26
  174. package/.cursor/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
  175. package/.cursor/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
  176. package/.cursor/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
  177. package/.cursor/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
  178. package/.cursor/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
  179. package/.cursor/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
  180. package/.cursor/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
  181. package/.cursor/skills/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
  182. package/.cursor/skills/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
  183. package/.cursor/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
  184. package/.cursor/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
  185. package/.cursor/skills/vercel-react-best-practices/rules/rerender-memo.md +0 -44
  186. package/.cursor/skills/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
  187. package/.cursor/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
  188. package/.cursor/skills/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
  189. package/.cursor/skills/vercel-react-best-practices/rules/server-cache-react.md +0 -76
  190. package/.cursor/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
  191. package/.cursor/skills/vercel-react-best-practices/rules/server-serialization.md +0 -38
  192. package/src/features/PluginStore/Content.tsx +0 -54
  193. package/src/features/PluginStore/InstalledList/Detail/CustomPluginEmptyState.tsx +0 -79
  194. package/src/features/PluginStore/InstalledList/Detail/index.tsx +0 -21
  195. package/src/features/PluginStore/InstalledList/List/Item/index.tsx +0 -61
  196. package/src/features/PluginStore/InstalledList/List/index.tsx +0 -72
  197. package/src/features/PluginStore/InstalledList/index.tsx +0 -90
  198. package/src/features/PluginStore/McpList/List/Action.tsx +0 -119
  199. package/src/features/PluginStore/McpList/List/Item.tsx +0 -83
  200. package/src/features/PluginStore/McpList/List/index.tsx +0 -93
  201. package/src/features/PluginStore/McpList/index.tsx +0 -58
  202. package/src/features/PluginStore/PluginList/Detail/DetailProvider.tsx +0 -19
  203. package/src/features/PluginStore/PluginList/Detail/EmptyState.tsx +0 -56
  204. package/src/features/PluginStore/PluginList/Detail/Header.tsx +0 -130
  205. package/src/features/PluginStore/PluginList/Detail/InstallDetail/Nav.tsx +0 -73
  206. package/src/features/PluginStore/PluginList/Detail/InstallDetail/Settings.tsx +0 -19
  207. package/src/features/PluginStore/PluginList/Detail/InstallDetail/Tools.tsx +0 -111
  208. package/src/features/PluginStore/PluginList/Detail/InstallDetail/index.tsx +0 -24
  209. package/src/features/PluginStore/PluginList/Detail/Loading.tsx +0 -42
  210. package/src/features/PluginStore/PluginList/Detail/TagList.tsx +0 -35
  211. package/src/features/PluginStore/PluginList/Detail/index.tsx +0 -39
  212. package/src/features/PluginStore/PluginList/Detail/useCategory.tsx +0 -76
  213. package/src/features/PluginStore/PluginList/List/Action.tsx +0 -78
  214. package/src/features/PluginStore/PluginList/List/Item.tsx +0 -92
  215. package/src/features/PluginStore/PluginList/List/index.tsx +0 -94
  216. package/src/features/PluginStore/PluginList/index.tsx +0 -46
  217. package/src/features/PluginStore/Search/index.tsx +0 -40
  218. /package/{.codex/skills → .agents}/vercel-react-best-practices/AGENTS.md +0 -0
  219. /package/{.codex/skills → .agents}/vercel-react-best-practices/SKILL.md +0 -0
  220. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -0
  221. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/advanced-use-latest.md +0 -0
  222. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-api-routes.md +0 -0
  223. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-defer-await.md +0 -0
  224. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-dependencies.md +0 -0
  225. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-parallel.md +0 -0
  226. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -0
  227. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -0
  228. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-conditional.md +0 -0
  229. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -0
  230. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -0
  231. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-preload.md +0 -0
  232. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-event-listeners.md +0 -0
  233. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -0
  234. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -0
  235. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-swr-dedup.md +0 -0
  236. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -0
  237. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-function-results.md +0 -0
  238. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-property-access.md +0 -0
  239. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-storage.md +0 -0
  240. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-combine-iterations.md +0 -0
  241. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-early-exit.md +0 -0
  242. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -0
  243. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-index-maps.md +0 -0
  244. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-length-check-first.md +0 -0
  245. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-min-max-loop.md +0 -0
  246. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -0
  247. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -0
  248. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-activity.md +0 -0
  249. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -0
  250. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -0
  251. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -0
  252. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -0
  253. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -0
  254. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -0
  255. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -0
  256. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-dependencies.md +0 -0
  257. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-derived-state.md +0 -0
  258. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -0
  259. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -0
  260. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-memo.md +0 -0
  261. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-transitions.md +0 -0
  262. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -0
  263. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-cache-lru.md +0 -0
  264. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-cache-react.md +0 -0
  265. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -0
  266. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-serialization.md +0 -0
  267. /package/src/{features/PluginStore/InstalledList → app/[variants]/(main)/settings/skill/features}/EditCustomPlugin.tsx +0 -0
  268. /package/src/features/{PluginStore/McpList/Detail → MCP/MCPDetail}/Loading.tsx +0 -0
  269. /package/src/features/{PluginStore → SkillStore}/Loading.tsx +0 -0
  270. /package/src/features/{PluginStore → SkillStore}/VirtuosoLoading.tsx +0 -0
@@ -0,0 +1,344 @@
1
+ 'use client';
2
+
3
+ import { type LobehubSkillProviderType } from '@lobechat/const';
4
+ import { ActionIcon, Avatar, DropdownMenu, Flexbox, Icon } from '@lobehub/ui';
5
+ import { App, Button } from 'antd';
6
+ import { createStyles, cssVar } from 'antd-style';
7
+ import { Loader2, MoreVerticalIcon, SquareArrowOutUpRight, Unplug } from 'lucide-react';
8
+ import { memo, useCallback, useEffect, useRef, useState } from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+
11
+ import IntegrationDetailModal from '@/features/IntegrationDetailModal';
12
+ import { useToolStore } from '@/store/tool';
13
+ import {
14
+ type LobehubSkillServer,
15
+ LobehubSkillStatus,
16
+ } from '@/store/tool/slices/lobehubSkillStore/types';
17
+
18
+ const POLL_INTERVAL_MS = 1000;
19
+ const POLL_TIMEOUT_MS = 15_000;
20
+
21
+ const useStyles = createStyles(({ css, token }) => ({
22
+ connected: css`
23
+ font-size: 14px;
24
+ color: ${token.colorSuccess};
25
+ `,
26
+ container: css`
27
+ padding-block: 12px;
28
+ padding-inline: 0;
29
+ `,
30
+ disconnected: css`
31
+ font-size: 14px;
32
+ color: ${token.colorTextTertiary};
33
+ `,
34
+ disconnectedIcon: css`
35
+ opacity: 0.5;
36
+ `,
37
+ disconnectedTitle: css`
38
+ color: ${token.colorTextTertiary};
39
+ `,
40
+ error: css`
41
+ font-size: 14px;
42
+ color: ${token.colorError};
43
+ `,
44
+ icon: css`
45
+ display: flex;
46
+ flex-shrink: 0;
47
+ align-items: center;
48
+ justify-content: center;
49
+
50
+ width: 48px;
51
+ height: 48px;
52
+ border-radius: 12px;
53
+
54
+ background: ${token.colorFillTertiary};
55
+ `,
56
+ title: css`
57
+ cursor: pointer;
58
+ font-size: 15px;
59
+ font-weight: 500;
60
+ color: ${token.colorText};
61
+
62
+ &:hover {
63
+ color: ${token.colorPrimary};
64
+ }
65
+ `,
66
+ }));
67
+
68
+ interface LobehubSkillItemProps {
69
+ provider: LobehubSkillProviderType;
70
+ server?: LobehubSkillServer;
71
+ }
72
+
73
+ const LobehubSkillItem = memo<LobehubSkillItemProps>(({ provider, server }) => {
74
+ const { t } = useTranslation('setting');
75
+ const { styles } = useStyles();
76
+ const { modal } = App.useApp();
77
+ const [isConnecting, setIsConnecting] = useState(false);
78
+ const [isWaitingAuth, setIsWaitingAuth] = useState(false);
79
+ const [detailOpen, setDetailOpen] = useState(false);
80
+
81
+ const oauthWindowRef = useRef<Window | null>(null);
82
+ const windowCheckIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
83
+ const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
84
+ const pollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
85
+
86
+ const checkStatus = useToolStore((s) => s.checkLobehubSkillStatus);
87
+ const revokeConnect = useToolStore((s) => s.revokeLobehubSkill);
88
+ const getAuthorizeUrl = useToolStore((s) => s.getLobehubSkillAuthorizeUrl);
89
+
90
+ const cleanup = useCallback(() => {
91
+ if (windowCheckIntervalRef.current) {
92
+ clearInterval(windowCheckIntervalRef.current);
93
+ windowCheckIntervalRef.current = null;
94
+ }
95
+ if (pollIntervalRef.current) {
96
+ clearInterval(pollIntervalRef.current);
97
+ pollIntervalRef.current = null;
98
+ }
99
+ if (pollTimeoutRef.current) {
100
+ clearTimeout(pollTimeoutRef.current);
101
+ pollTimeoutRef.current = null;
102
+ }
103
+ oauthWindowRef.current = null;
104
+ setIsWaitingAuth(false);
105
+ }, []);
106
+
107
+ useEffect(() => {
108
+ return () => {
109
+ cleanup();
110
+ };
111
+ }, [cleanup]);
112
+
113
+ useEffect(() => {
114
+ if (server?.status === LobehubSkillStatus.CONNECTED && isWaitingAuth) {
115
+ cleanup();
116
+ }
117
+ }, [server?.status, isWaitingAuth, cleanup]);
118
+
119
+ const startFallbackPolling = useCallback(() => {
120
+ if (pollIntervalRef.current) return;
121
+
122
+ pollIntervalRef.current = setInterval(async () => {
123
+ try {
124
+ await checkStatus(provider.id);
125
+ } catch (error) {
126
+ console.error('[LobehubSkill] Failed to check status:', error);
127
+ }
128
+ }, POLL_INTERVAL_MS);
129
+
130
+ pollTimeoutRef.current = setTimeout(() => {
131
+ if (pollIntervalRef.current) {
132
+ clearInterval(pollIntervalRef.current);
133
+ pollIntervalRef.current = null;
134
+ }
135
+ setIsWaitingAuth(false);
136
+ }, POLL_TIMEOUT_MS);
137
+ }, [checkStatus, provider.id]);
138
+
139
+ const startWindowMonitor = useCallback(
140
+ (oauthWindow: Window) => {
141
+ windowCheckIntervalRef.current = setInterval(async () => {
142
+ try {
143
+ if (oauthWindow.closed) {
144
+ if (windowCheckIntervalRef.current) {
145
+ clearInterval(windowCheckIntervalRef.current);
146
+ windowCheckIntervalRef.current = null;
147
+ }
148
+ oauthWindowRef.current = null;
149
+ await checkStatus(provider.id);
150
+ setIsWaitingAuth(false);
151
+ }
152
+ } catch {
153
+ console.log('[LobehubSkill] COOP blocked window.closed access, falling back to polling');
154
+ if (windowCheckIntervalRef.current) {
155
+ clearInterval(windowCheckIntervalRef.current);
156
+ windowCheckIntervalRef.current = null;
157
+ }
158
+ startFallbackPolling();
159
+ }
160
+ }, 500);
161
+ },
162
+ [checkStatus, provider.id, startFallbackPolling],
163
+ );
164
+
165
+ const openOAuthWindow = useCallback(
166
+ (authorizeUrl: string) => {
167
+ cleanup();
168
+ setIsWaitingAuth(true);
169
+
170
+ const oauthWindow = window.open(authorizeUrl, '_blank', 'width=600,height=700');
171
+ if (oauthWindow) {
172
+ oauthWindowRef.current = oauthWindow;
173
+ startWindowMonitor(oauthWindow);
174
+ } else {
175
+ startFallbackPolling();
176
+ }
177
+ },
178
+ [cleanup, startWindowMonitor, startFallbackPolling],
179
+ );
180
+
181
+ useEffect(() => {
182
+ const handleMessage = async (event: MessageEvent) => {
183
+ if (event.origin !== window.location.origin) return;
184
+
185
+ if (
186
+ event.data?.type === 'LOBEHUB_SKILL_AUTH_SUCCESS' &&
187
+ event.data?.provider === provider.id
188
+ ) {
189
+ cleanup();
190
+ await checkStatus(provider.id);
191
+ }
192
+ };
193
+
194
+ window.addEventListener('message', handleMessage);
195
+ return () => window.removeEventListener('message', handleMessage);
196
+ }, [provider.id, cleanup, checkStatus]);
197
+
198
+ const handleConnect = async () => {
199
+ if (server?.isConnected) return;
200
+
201
+ setIsConnecting(true);
202
+ try {
203
+ const redirectUri = `${window.location.origin}/oauth/callback/success?provider=${encodeURIComponent(provider.id)}`;
204
+ const { authorizeUrl } = await getAuthorizeUrl(provider.id, { redirectUri });
205
+ openOAuthWindow(authorizeUrl);
206
+ } catch (error) {
207
+ console.error('[LobehubSkill] Failed to get authorize URL:', error);
208
+ } finally {
209
+ setIsConnecting(false);
210
+ }
211
+ };
212
+
213
+ const handleDisconnect = () => {
214
+ if (!server) return;
215
+ modal.confirm({
216
+ cancelText: t('cancel', { ns: 'common' }),
217
+ centered: true,
218
+ content: t('tools.lobehubSkill.disconnectConfirm.desc', { name: provider.label }),
219
+ okButtonProps: { danger: true },
220
+ okText: t('tools.lobehubSkill.disconnect'),
221
+ onOk: async () => {
222
+ await revokeConnect(server.identifier);
223
+ },
224
+ title: t('tools.lobehubSkill.disconnectConfirm.title', { name: provider.label }),
225
+ });
226
+ };
227
+
228
+ const renderIcon = () => {
229
+ const { icon, label } = provider;
230
+ if (typeof icon === 'string') {
231
+ return <Avatar alt={label} avatar={icon} size={32} />;
232
+ }
233
+ return <Icon fill={cssVar.colorText} icon={icon} size={32} />;
234
+ };
235
+
236
+ const renderStatus = () => {
237
+ if (!server) {
238
+ return (
239
+ <span className={styles.disconnected}>
240
+ {t('tools.lobehubSkill.disconnected', { defaultValue: 'Disconnected' })}
241
+ </span>
242
+ );
243
+ }
244
+
245
+ switch (server.status) {
246
+ case LobehubSkillStatus.CONNECTED: {
247
+ return (
248
+ <span className={styles.connected}>
249
+ {t('tools.lobehubSkill.connected', { defaultValue: 'Connected' })}
250
+ </span>
251
+ );
252
+ }
253
+ case LobehubSkillStatus.ERROR: {
254
+ return <span className={styles.error}>{t('tools.lobehubSkill.error')}</span>;
255
+ }
256
+ default: {
257
+ return (
258
+ <span className={styles.disconnected}>
259
+ {t('tools.lobehubSkill.disconnected', { defaultValue: 'Disconnected' })}
260
+ </span>
261
+ );
262
+ }
263
+ }
264
+ };
265
+
266
+ const renderAction = () => {
267
+ if (isConnecting || isWaitingAuth) {
268
+ return (
269
+ <Button disabled icon={<Icon icon={Loader2} spin />} type="default">
270
+ {t('tools.lobehubSkill.connect')}
271
+ </Button>
272
+ );
273
+ }
274
+
275
+ if (!server || server.status !== LobehubSkillStatus.CONNECTED) {
276
+ return (
277
+ <Button icon={<Icon icon={SquareArrowOutUpRight} />} onClick={handleConnect} type="default">
278
+ {t('tools.lobehubSkill.connect')}
279
+ </Button>
280
+ );
281
+ }
282
+
283
+ return (
284
+ <DropdownMenu
285
+ items={[
286
+ {
287
+ icon: <Icon icon={Unplug} />,
288
+ key: 'disconnect',
289
+ label: t('tools.lobehubSkill.disconnect', { defaultValue: 'Disconnect' }),
290
+ onClick: handleDisconnect,
291
+ },
292
+ ]}
293
+ placement="bottomRight"
294
+ >
295
+ <ActionIcon icon={MoreVerticalIcon} />
296
+ </DropdownMenu>
297
+ );
298
+ };
299
+
300
+ const isConnected = server?.status === LobehubSkillStatus.CONNECTED;
301
+
302
+ return (
303
+ <>
304
+ <Flexbox
305
+ align="center"
306
+ className={styles.container}
307
+ gap={16}
308
+ horizontal
309
+ justify="space-between"
310
+ >
311
+ <Flexbox align="center" gap={16} horizontal style={{ flex: 1, overflow: 'hidden' }}>
312
+ <div className={`${styles.icon} ${!isConnected ? styles.disconnectedIcon : ''}`}>
313
+ {renderIcon()}
314
+ </div>
315
+ <Flexbox gap={4} style={{ overflow: 'hidden' }}>
316
+ <span
317
+ className={`${styles.title} ${!isConnected ? styles.disconnectedTitle : ''}`}
318
+ onClick={() => setDetailOpen(true)}
319
+ >
320
+ {provider.label}
321
+ </span>
322
+ {!isConnected && renderStatus()}
323
+ </Flexbox>
324
+ </Flexbox>
325
+ <Flexbox align="center" gap={12} horizontal>
326
+ {isConnected && renderStatus()}
327
+ {renderAction()}
328
+ </Flexbox>
329
+ </Flexbox>
330
+ <IntegrationDetailModal
331
+ identifier={provider.id}
332
+ isConnecting={isConnecting || isWaitingAuth}
333
+ onClose={() => setDetailOpen(false)}
334
+ onConnect={handleConnect}
335
+ open={detailOpen}
336
+ type="lobehub"
337
+ />
338
+ </>
339
+ );
340
+ });
341
+
342
+ LobehubSkillItem.displayName = 'LobehubSkillItem';
343
+
344
+ export default LobehubSkillItem;
@@ -0,0 +1,116 @@
1
+ 'use client';
2
+
3
+ import { Flexbox, Modal } from '@lobehub/ui';
4
+ import { createStyles } from 'antd-style';
5
+ import { memo, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import PluginAvatar from '@/components/Plugins/PluginAvatar';
9
+ import PluginTag from '@/components/Plugins/PluginTag';
10
+ import PluginDetailModal from '@/features/PluginDetailModal';
11
+ import Actions from './Actions';
12
+ import McpDetail from '@/features/MCP/MCPDetail';
13
+ import { useToolStore } from '@/store/tool';
14
+ import { pluginSelectors } from '@/store/tool/selectors';
15
+ import { type LobeToolType } from '@/types/tool/tool';
16
+
17
+ const useStyles = createStyles(({ css, token }) => ({
18
+ container: css`
19
+ padding-block: 12px;
20
+ padding-inline: 0;
21
+ `,
22
+ icon: css`
23
+ display: flex;
24
+ flex-shrink: 0;
25
+ align-items: center;
26
+ justify-content: center;
27
+
28
+ width: 48px;
29
+ height: 48px;
30
+ border-radius: 12px;
31
+
32
+ background: ${token.colorFillTertiary};
33
+ `,
34
+ title: css`
35
+ cursor: pointer;
36
+ font-size: 15px;
37
+ font-weight: 500;
38
+ color: ${token.colorText};
39
+
40
+ &:hover {
41
+ color: ${token.colorPrimary};
42
+ }
43
+ `,
44
+ }));
45
+
46
+ interface McpSkillItemProps {
47
+ author?: string;
48
+ avatar?: string;
49
+ identifier: string;
50
+ runtimeType?: string;
51
+ title: string;
52
+ type: LobeToolType;
53
+ }
54
+
55
+ const McpSkillItem = memo<McpSkillItemProps>(
56
+ ({ identifier, title, avatar, type, runtimeType, author }) => {
57
+ const { styles } = useStyles();
58
+ const { t } = useTranslation('plugin');
59
+ const isMCP = runtimeType === 'mcp';
60
+ const isCustomPlugin = type === 'customPlugin';
61
+ const isCommunityMCP = isMCP && !isCustomPlugin;
62
+ const [detailOpen, setDetailOpen] = useState(false);
63
+
64
+ const plugin = useToolStore(pluginSelectors.getToolManifestById(identifier));
65
+
66
+ return (
67
+ <>
68
+ <Flexbox
69
+ align="center"
70
+ className={styles.container}
71
+ gap={16}
72
+ horizontal
73
+ justify="space-between"
74
+ >
75
+ <Flexbox align="center" gap={16} horizontal style={{ flex: 1, overflow: 'hidden' }}>
76
+ <div className={styles.icon}>
77
+ <PluginAvatar avatar={avatar} size={32} />
78
+ </div>
79
+ <Flexbox align="center" gap={8} horizontal style={{ overflow: 'hidden' }}>
80
+ <span className={styles.title} onClick={() => setDetailOpen(true)}>
81
+ {title}
82
+ </span>
83
+ <PluginTag author={author} isMCP={isMCP} type={type} />
84
+ </Flexbox>
85
+ </Flexbox>
86
+ <Actions identifier={identifier} isMCP={isMCP} type={type} />
87
+ </Flexbox>
88
+ {isCommunityMCP && (
89
+ <Modal
90
+ destroyOnHidden
91
+ footer={null}
92
+ onCancel={() => setDetailOpen(false)}
93
+ open={detailOpen}
94
+ title={t('dev.title.skillDetails')}
95
+ width={800}
96
+ >
97
+ <McpDetail identifier={identifier} noSettings />
98
+ </Modal>
99
+ )}
100
+ {isCustomPlugin && (
101
+ <PluginDetailModal
102
+ id={identifier}
103
+ onClose={() => setDetailOpen(false)}
104
+ open={detailOpen}
105
+ schema={plugin?.settings}
106
+ tab="info"
107
+ />
108
+ )}
109
+ </>
110
+ );
111
+ },
112
+ );
113
+
114
+ McpSkillItem.displayName = 'McpSkillItem';
115
+
116
+ export default McpSkillItem;
@@ -0,0 +1,244 @@
1
+ 'use client';
2
+
3
+ import {
4
+ KLAVIS_SERVER_TYPES,
5
+ type KlavisServerType,
6
+ LOBEHUB_SKILL_PROVIDERS,
7
+ type LobehubSkillProviderType,
8
+ RECOMMENDED_SKILLS,
9
+ RecommendedSkillType,
10
+ getKlavisServerByServerIdentifier,
11
+ getLobehubSkillProviderById,
12
+ } from '@lobechat/const';
13
+ import { Divider } from 'antd';
14
+ import { createStyles } from 'antd-style';
15
+ import isEqual from 'fast-deep-equal';
16
+ import { memo, useMemo } from 'react';
17
+ import { useTranslation } from 'react-i18next';
18
+
19
+ import AddSkillButton from '@/features/SkillStore/AddSkillButton';
20
+ import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
21
+ import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
22
+ import { useToolStore } from '@/store/tool';
23
+ import {
24
+ klavisStoreSelectors,
25
+ lobehubSkillStoreSelectors,
26
+ pluginSelectors,
27
+ } from '@/store/tool/selectors';
28
+ import { KlavisServerStatus } from '@/store/tool/slices/klavisStore';
29
+ import { LobehubSkillStatus } from '@/store/tool/slices/lobehubSkillStore/types';
30
+ import { type LobeToolType } from '@/types/tool/tool';
31
+
32
+ import McpSkillItem from './McpSkillItem';
33
+ import KlavisSkillItem from './KlavisSkillItem';
34
+ import LobehubSkillItem from './LobehubSkillItem';
35
+
36
+ const useStyles = createStyles(({ css, token }) => ({
37
+ container: css`
38
+ display: flex;
39
+ flex-direction: column;
40
+ gap: 8px;
41
+ `,
42
+ description: css`
43
+ margin-block-end: 8px;
44
+ color: ${token.colorTextSecondary};
45
+ `,
46
+ empty: css`
47
+ padding: 24px;
48
+ color: ${token.colorTextTertiary};
49
+ text-align: center;
50
+ `,
51
+ }));
52
+
53
+ const SkillList = memo(() => {
54
+ const { t } = useTranslation('setting');
55
+ const { styles } = useStyles();
56
+
57
+ const isLobehubSkillEnabled = useServerConfigStore(serverConfigSelectors.enableLobehubSkill);
58
+ const isKlavisEnabled = useServerConfigStore(serverConfigSelectors.enableKlavis);
59
+ const allLobehubSkillServers = useToolStore(lobehubSkillStoreSelectors.getServers, isEqual);
60
+ const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
61
+ const installedPluginList = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
62
+
63
+ const [useFetchLobehubSkillConnections, useFetchUserKlavisServers] = useToolStore((s) => [
64
+ s.useFetchLobehubSkillConnections,
65
+ s.useFetchUserKlavisServers,
66
+ ]);
67
+
68
+ useFetchInstalledPlugins();
69
+ useFetchLobehubSkillConnections(isLobehubSkillEnabled);
70
+ useFetchUserKlavisServers(isKlavisEnabled);
71
+
72
+ const getLobehubSkillServerByProvider = (providerId: string) => {
73
+ return allLobehubSkillServers.find((server) => server.identifier === providerId);
74
+ };
75
+
76
+ const getKlavisServerByIdentifier = (identifier: string) => {
77
+ return allKlavisServers.find((server) => server.identifier === identifier);
78
+ };
79
+
80
+ // Separate skills into three categories:
81
+ // 1. Integrations (connected LobHub and Klavis)
82
+ // 2. Community MCP Tools (type === 'plugin')
83
+ // 3. Custom MCP Tools (type === 'customPlugin')
84
+ const { integrations, communityMCPs, customMCPs } = useMemo(() => {
85
+ type IntegrationItem =
86
+ | { provider: LobehubSkillProviderType; type: 'lobehub' }
87
+ | { serverType: KlavisServerType; type: 'klavis' };
88
+
89
+ let integrationItems: IntegrationItem[] = [];
90
+
91
+ // If RECOMMENDED_SKILLS is configured, use it to build the list
92
+ if (RECOMMENDED_SKILLS.length > 0) {
93
+ for (const skill of RECOMMENDED_SKILLS) {
94
+ if (skill.type === RecommendedSkillType.Lobehub && isLobehubSkillEnabled) {
95
+ const provider = getLobehubSkillProviderById(skill.id);
96
+ if (provider) {
97
+ integrationItems.push({ provider, type: 'lobehub' });
98
+ }
99
+ } else if (skill.type === RecommendedSkillType.Klavis && isKlavisEnabled) {
100
+ const serverType = getKlavisServerByServerIdentifier(skill.id);
101
+ if (serverType) {
102
+ integrationItems.push({ serverType, type: 'klavis' });
103
+ }
104
+ }
105
+ }
106
+ } else {
107
+ // Default behavior: add all lobehub skills
108
+ if (isLobehubSkillEnabled) {
109
+ for (const provider of LOBEHUB_SKILL_PROVIDERS) {
110
+ integrationItems.push({ provider, type: 'lobehub' });
111
+ }
112
+ }
113
+
114
+ // Add klavis skills
115
+ if (isKlavisEnabled) {
116
+ for (const serverType of KLAVIS_SERVER_TYPES) {
117
+ integrationItems.push({ serverType, type: 'klavis' });
118
+ }
119
+ }
120
+
121
+ // Filter integrations: show all lobehub skills, but only connected klavis
122
+ integrationItems = integrationItems.filter((item) => {
123
+ if (item.type === 'lobehub') {
124
+ return true;
125
+ }
126
+ return (
127
+ getKlavisServerByIdentifier(item.serverType.identifier)?.status ===
128
+ KlavisServerStatus.CONNECTED
129
+ );
130
+ });
131
+ }
132
+
133
+ // Sort integrations: connected ones first
134
+ const sortedIntegrations = integrationItems.sort((a, b) => {
135
+ const isConnectedA =
136
+ a.type === 'lobehub'
137
+ ? getLobehubSkillServerByProvider(a.provider.id)?.status === LobehubSkillStatus.CONNECTED
138
+ : getKlavisServerByIdentifier(a.serverType.identifier)?.status ===
139
+ KlavisServerStatus.CONNECTED;
140
+ const isConnectedB =
141
+ b.type === 'lobehub'
142
+ ? getLobehubSkillServerByProvider(b.provider.id)?.status === LobehubSkillStatus.CONNECTED
143
+ : getKlavisServerByIdentifier(b.serverType.identifier)?.status ===
144
+ KlavisServerStatus.CONNECTED;
145
+
146
+ if (isConnectedA && !isConnectedB) return -1;
147
+ if (!isConnectedA && isConnectedB) return 1;
148
+ return 0;
149
+ });
150
+
151
+ // Separate installed plugins into community and custom
152
+ const communityPlugins = installedPluginList.filter((plugin) => plugin.type === 'plugin');
153
+ const customPlugins = installedPluginList.filter((plugin) => plugin.type === 'customPlugin');
154
+
155
+ return {
156
+ communityMCPs: communityPlugins,
157
+ customMCPs: customPlugins,
158
+ integrations: sortedIntegrations,
159
+ };
160
+ }, [
161
+ installedPluginList,
162
+ isLobehubSkillEnabled,
163
+ isKlavisEnabled,
164
+ allLobehubSkillServers,
165
+ allKlavisServers,
166
+ ]);
167
+
168
+ const hasAnySkills = integrations.length > 0 || communityMCPs.length > 0 || customMCPs.length > 0;
169
+
170
+ if (!hasAnySkills) {
171
+ return (
172
+ <div className={styles.container}>
173
+ <p className={styles.description}>{t('tab.skillDesc')}</p>
174
+ <div className={styles.empty}>{t('tab.skillEmpty')}</div>
175
+ <AddSkillButton />
176
+ </div>
177
+ );
178
+ }
179
+
180
+ const renderIntegrations = () =>
181
+ integrations.map((item) => {
182
+ if (item.type === 'lobehub') {
183
+ return (
184
+ <LobehubSkillItem
185
+ key={item.provider.id}
186
+ provider={item.provider}
187
+ server={getLobehubSkillServerByProvider(item.provider.id)}
188
+ />
189
+ );
190
+ }
191
+ return (
192
+ <KlavisSkillItem
193
+ key={item.serverType.identifier}
194
+ server={getKlavisServerByIdentifier(item.serverType.identifier)}
195
+ serverType={item.serverType}
196
+ />
197
+ );
198
+ });
199
+
200
+ const renderCommunityMCPs = () =>
201
+ communityMCPs.map((plugin) => (
202
+ <McpSkillItem
203
+ author={plugin.author}
204
+ avatar={plugin.avatar}
205
+ identifier={plugin.identifier}
206
+ key={plugin.identifier}
207
+ runtimeType={plugin.runtimeType}
208
+ title={plugin.title || plugin.identifier}
209
+ type={plugin.type as LobeToolType}
210
+ />
211
+ ));
212
+
213
+ const renderCustomMCPs = () =>
214
+ customMCPs.map((plugin) => (
215
+ <McpSkillItem
216
+ author={plugin.author}
217
+ avatar={plugin.avatar}
218
+ identifier={plugin.identifier}
219
+ key={plugin.identifier}
220
+ runtimeType={plugin.runtimeType}
221
+ title={plugin.title || plugin.identifier}
222
+ type={plugin.type as LobeToolType}
223
+ />
224
+ ));
225
+
226
+ return (
227
+ <div className={styles.container}>
228
+ {integrations.length > 0 && renderIntegrations()}
229
+ {integrations.length > 0 && communityMCPs.length > 0 && <Divider style={{ margin: 0 }} />}
230
+ {communityMCPs.length > 0 && renderCommunityMCPs()}
231
+ {(integrations.length > 0 || communityMCPs.length > 0) && customMCPs.length > 0 && (
232
+ <Divider style={{ margin: 0 }} />
233
+ )}
234
+ {customMCPs.length > 0 && renderCustomMCPs()}
235
+ <div style={{ marginTop: 8 }}>
236
+ <AddSkillButton />
237
+ </div>
238
+ </div>
239
+ );
240
+ });
241
+
242
+ SkillList.displayName = 'SkillList';
243
+
244
+ export default SkillList;