@lobehub/lobehub 2.0.0-next.338 → 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 (257) hide show
  1. package/.gitattributes +35 -0
  2. package/CHANGELOG.md +44 -0
  3. package/changelog/v1.json +15 -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 +78 -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/plugin.json +12 -2
  57. package/locales/zh-CN/providers.json +1 -0
  58. package/locales/zh-CN/setting.json +78 -2
  59. package/locales/zh-TW/plugin.json +12 -2
  60. package/locales/zh-TW/providers.json +1 -0
  61. package/locales/zh-TW/setting.json +78 -2
  62. package/package.json +1 -1
  63. package/packages/agent-runtime/src/groupOrchestration/GroupOrchestrationSupervisor.ts +2 -0
  64. package/packages/agent-runtime/src/groupOrchestration/__tests__/GroupOrchestrationSupervisor.test.ts +3 -1
  65. package/packages/agent-runtime/src/groupOrchestration/types.ts +5 -0
  66. package/packages/const/src/index.ts +1 -0
  67. package/packages/const/src/klavis.ts +144 -0
  68. package/packages/const/src/lobehubSkill.ts +34 -0
  69. package/packages/const/src/recommendedSkill.ts +17 -0
  70. package/packages/model-runtime/src/core/contextBuilders/anthropic.test.ts +38 -0
  71. package/packages/model-runtime/src/core/contextBuilders/anthropic.ts +20 -1
  72. package/packages/model-runtime/src/core/contextBuilders/google.test.ts +42 -0
  73. package/packages/model-runtime/src/core/contextBuilders/google.ts +17 -0
  74. package/scripts/electronWorkflow/modifiers/dynamicToStatic.mts +273 -0
  75. package/scripts/electronWorkflow/modifiers/index.mts +10 -0
  76. package/scripts/electronWorkflow/modifiers/nextConfig.mts +1 -0
  77. package/scripts/electronWorkflow/modifiers/nextDynamicToStatic.mts +233 -0
  78. package/scripts/electronWorkflow/modifiers/removeSuspense.mts +124 -0
  79. package/scripts/electronWorkflow/modifiers/routes.mts +14 -2
  80. package/scripts/electronWorkflow/modifiers/settingsContentToStatic.mts +148 -0
  81. package/scripts/electronWorkflow/modifiers/wrapChildrenWithClientOnly.mts +73 -0
  82. package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +131 -0
  83. package/src/app/[variants]/(main)/home/features/InputArea/index.tsx +34 -27
  84. package/src/app/[variants]/(main)/settings/features/SettingHeader.tsx +8 -4
  85. package/src/app/[variants]/(main)/settings/features/SettingsContent.tsx +3 -0
  86. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +6 -0
  87. package/src/{features/PluginStore/InstalledList/List/Item/Action.tsx → app/[variants]/(main)/settings/skill/features/Actions.tsx} +45 -40
  88. package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +353 -0
  89. package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +344 -0
  90. package/src/app/[variants]/(main)/settings/skill/features/McpSkillItem.tsx +116 -0
  91. package/src/app/[variants]/(main)/settings/skill/features/SkillList.tsx +244 -0
  92. package/src/app/[variants]/(main)/settings/skill/index.tsx +35 -0
  93. package/src/components/Plugins/PluginTag.tsx +23 -35
  94. package/src/components/client/ClientOnly.tsx +6 -2
  95. package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
  96. package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +8 -32
  97. package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +8 -30
  98. package/src/features/ChatInput/ActionBar/Tools/PopoverContent.tsx +48 -59
  99. package/src/features/ChatInput/ActionBar/Tools/index.tsx +5 -23
  100. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +158 -56
  101. package/src/features/IntegrationDetailModal/index.tsx +293 -0
  102. package/src/features/{PluginStore/McpList/Detail → MCP/MCPDetail}/index.tsx +15 -6
  103. package/src/features/MCP/MCPSettings/McpSettingsModal.tsx +58 -0
  104. package/src/features/{PluginStore/McpList/Detail/Settings → MCP/MCPSettings}/index.tsx +39 -27
  105. package/src/features/PluginDetailModal/index.tsx +2 -2
  106. package/src/features/PluginDevModal/index.tsx +16 -40
  107. package/src/features/ProfileEditor/AgentTool.tsx +2 -2
  108. package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/index.tsx +1 -1
  109. package/src/features/{PluginStore/AddPluginButton.tsx → SkillStore/AddSkillButton.tsx} +3 -3
  110. package/src/features/SkillStore/CommunityList/Item.tsx +158 -0
  111. package/src/features/SkillStore/CommunityList/index.tsx +101 -0
  112. package/src/features/SkillStore/Content.tsx +59 -0
  113. package/src/features/{PluginStore/PluginEmpty.tsx → SkillStore/Empty.tsx} +8 -8
  114. package/src/features/SkillStore/LobeHubList/Item.tsx +118 -0
  115. package/src/features/SkillStore/LobeHubList/index.tsx +187 -0
  116. package/src/features/SkillStore/LobeHubList/useSkillConnect.ts +239 -0
  117. package/src/features/SkillStore/Search/index.tsx +43 -0
  118. package/src/features/{PluginStore → SkillStore}/index.tsx +14 -10
  119. package/src/features/SkillStore/style.ts +27 -0
  120. package/src/locales/default/plugin.ts +15 -4
  121. package/src/locales/default/setting.ts +185 -2
  122. package/src/services/chat/mecha/agentConfigResolver.test.ts +197 -0
  123. package/src/services/chat/mecha/agentConfigResolver.ts +44 -17
  124. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +40 -37
  125. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +78 -0
  126. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +50 -16
  127. package/src/store/global/initialState.ts +1 -0
  128. package/src/store/tool/slices/lobehubSkillStore/action.test.ts +914 -0
  129. package/src/store/tool/slices/lobehubSkillStore/selectors.test.ts +548 -0
  130. package/.cursor/skills/vercel-react-best-practices/AGENTS.md +0 -2410
  131. package/.cursor/skills/vercel-react-best-practices/SKILL.md +0 -125
  132. package/.cursor/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
  133. package/.cursor/skills/vercel-react-best-practices/rules/advanced-use-latest.md +0 -49
  134. package/.cursor/skills/vercel-react-best-practices/rules/async-api-routes.md +0 -38
  135. package/.cursor/skills/vercel-react-best-practices/rules/async-defer-await.md +0 -80
  136. package/.cursor/skills/vercel-react-best-practices/rules/async-dependencies.md +0 -36
  137. package/.cursor/skills/vercel-react-best-practices/rules/async-parallel.md +0 -28
  138. package/.cursor/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
  139. package/.cursor/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -59
  140. package/.cursor/skills/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
  141. package/.cursor/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
  142. package/.cursor/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
  143. package/.cursor/skills/vercel-react-best-practices/rules/bundle-preload.md +0 -50
  144. package/.cursor/skills/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
  145. package/.cursor/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
  146. package/.cursor/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
  147. package/.cursor/skills/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
  148. package/.cursor/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -57
  149. package/.cursor/skills/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
  150. package/.cursor/skills/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
  151. package/.cursor/skills/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
  152. package/.cursor/skills/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
  153. package/.cursor/skills/vercel-react-best-practices/rules/js-early-exit.md +0 -50
  154. package/.cursor/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
  155. package/.cursor/skills/vercel-react-best-practices/rules/js-index-maps.md +0 -37
  156. package/.cursor/skills/vercel-react-best-practices/rules/js-length-check-first.md +0 -49
  157. package/.cursor/skills/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
  158. package/.cursor/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
  159. package/.cursor/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
  160. package/.cursor/skills/vercel-react-best-practices/rules/rendering-activity.md +0 -26
  161. package/.cursor/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
  162. package/.cursor/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
  163. package/.cursor/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
  164. package/.cursor/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
  165. package/.cursor/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
  166. package/.cursor/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
  167. package/.cursor/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
  168. package/.cursor/skills/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
  169. package/.cursor/skills/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
  170. package/.cursor/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
  171. package/.cursor/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
  172. package/.cursor/skills/vercel-react-best-practices/rules/rerender-memo.md +0 -44
  173. package/.cursor/skills/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
  174. package/.cursor/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
  175. package/.cursor/skills/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
  176. package/.cursor/skills/vercel-react-best-practices/rules/server-cache-react.md +0 -76
  177. package/.cursor/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
  178. package/.cursor/skills/vercel-react-best-practices/rules/server-serialization.md +0 -38
  179. package/src/features/PluginStore/Content.tsx +0 -54
  180. package/src/features/PluginStore/InstalledList/Detail/CustomPluginEmptyState.tsx +0 -79
  181. package/src/features/PluginStore/InstalledList/Detail/index.tsx +0 -21
  182. package/src/features/PluginStore/InstalledList/List/Item/index.tsx +0 -61
  183. package/src/features/PluginStore/InstalledList/List/index.tsx +0 -72
  184. package/src/features/PluginStore/InstalledList/index.tsx +0 -90
  185. package/src/features/PluginStore/McpList/List/Action.tsx +0 -119
  186. package/src/features/PluginStore/McpList/List/Item.tsx +0 -83
  187. package/src/features/PluginStore/McpList/List/index.tsx +0 -93
  188. package/src/features/PluginStore/McpList/index.tsx +0 -58
  189. package/src/features/PluginStore/PluginList/Detail/DetailProvider.tsx +0 -19
  190. package/src/features/PluginStore/PluginList/Detail/EmptyState.tsx +0 -56
  191. package/src/features/PluginStore/PluginList/Detail/Header.tsx +0 -130
  192. package/src/features/PluginStore/PluginList/Detail/InstallDetail/Nav.tsx +0 -73
  193. package/src/features/PluginStore/PluginList/Detail/InstallDetail/Settings.tsx +0 -19
  194. package/src/features/PluginStore/PluginList/Detail/InstallDetail/Tools.tsx +0 -111
  195. package/src/features/PluginStore/PluginList/Detail/InstallDetail/index.tsx +0 -24
  196. package/src/features/PluginStore/PluginList/Detail/Loading.tsx +0 -42
  197. package/src/features/PluginStore/PluginList/Detail/TagList.tsx +0 -35
  198. package/src/features/PluginStore/PluginList/Detail/index.tsx +0 -39
  199. package/src/features/PluginStore/PluginList/Detail/useCategory.tsx +0 -76
  200. package/src/features/PluginStore/PluginList/List/Action.tsx +0 -78
  201. package/src/features/PluginStore/PluginList/List/Item.tsx +0 -92
  202. package/src/features/PluginStore/PluginList/List/index.tsx +0 -94
  203. package/src/features/PluginStore/PluginList/index.tsx +0 -46
  204. package/src/features/PluginStore/Search/index.tsx +0 -40
  205. /package/{.codex/skills → .agents}/vercel-react-best-practices/AGENTS.md +0 -0
  206. /package/{.codex/skills → .agents}/vercel-react-best-practices/SKILL.md +0 -0
  207. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -0
  208. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/advanced-use-latest.md +0 -0
  209. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-api-routes.md +0 -0
  210. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-defer-await.md +0 -0
  211. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-dependencies.md +0 -0
  212. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-parallel.md +0 -0
  213. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -0
  214. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -0
  215. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-conditional.md +0 -0
  216. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -0
  217. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -0
  218. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-preload.md +0 -0
  219. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-event-listeners.md +0 -0
  220. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -0
  221. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -0
  222. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-swr-dedup.md +0 -0
  223. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -0
  224. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-function-results.md +0 -0
  225. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-property-access.md +0 -0
  226. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-storage.md +0 -0
  227. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-combine-iterations.md +0 -0
  228. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-early-exit.md +0 -0
  229. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -0
  230. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-index-maps.md +0 -0
  231. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-length-check-first.md +0 -0
  232. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-min-max-loop.md +0 -0
  233. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -0
  234. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -0
  235. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-activity.md +0 -0
  236. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -0
  237. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -0
  238. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -0
  239. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -0
  240. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -0
  241. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -0
  242. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -0
  243. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-dependencies.md +0 -0
  244. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-derived-state.md +0 -0
  245. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -0
  246. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -0
  247. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-memo.md +0 -0
  248. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-transitions.md +0 -0
  249. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -0
  250. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-cache-lru.md +0 -0
  251. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-cache-react.md +0 -0
  252. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -0
  253. /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-serialization.md +0 -0
  254. /package/src/{features/PluginStore/InstalledList → app/[variants]/(main)/settings/skill/features}/EditCustomPlugin.tsx +0 -0
  255. /package/src/features/{PluginStore/McpList/Detail → MCP/MCPDetail}/Loading.tsx +0 -0
  256. /package/src/features/{PluginStore → SkillStore}/Loading.tsx +0 -0
  257. /package/src/features/{PluginStore → SkillStore}/VirtuosoLoading.tsx +0 -0
@@ -0,0 +1,124 @@
1
+ import { Lang, parse } from '@ast-grep/napi';
2
+ import path from 'node:path';
3
+
4
+ import { invariant, isDirectRun, runStandalone, updateFile } from './utils.mjs';
5
+
6
+ const removeSuspenseWrapper = (code: string): string => {
7
+ const ast = parse(Lang.Tsx, code);
8
+ const root = ast.root();
9
+
10
+ const suspenseElements = root.findAll({
11
+ rule: {
12
+ has: {
13
+ has: {
14
+ kind: 'identifier',
15
+ regex: '^Suspense$',
16
+ },
17
+ kind: 'jsx_opening_element',
18
+ },
19
+ kind: 'jsx_element',
20
+ },
21
+ });
22
+
23
+ if (suspenseElements.length === 0) {
24
+ return code;
25
+ }
26
+
27
+ const edits: Array<{ end: number; start: number; text: string }> = [];
28
+
29
+ for (const suspense of suspenseElements) {
30
+ const range = suspense.range();
31
+
32
+ const children = suspense.children();
33
+ let childrenText = '';
34
+
35
+ for (const child of children) {
36
+ const kind = child.kind();
37
+ if (kind === 'jsx_element' || kind === 'jsx_self_closing_element' || kind === 'jsx_fragment') {
38
+ childrenText = child.text();
39
+ break;
40
+ }
41
+ }
42
+
43
+ if (childrenText) {
44
+ edits.push({
45
+ end: range.end.index,
46
+ start: range.start.index,
47
+ text: childrenText,
48
+ });
49
+ }
50
+ }
51
+
52
+ edits.sort((a, b) => b.start - a.start);
53
+
54
+ let result = code;
55
+ for (const edit of edits) {
56
+ result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
57
+ }
58
+
59
+ return result;
60
+ };
61
+
62
+ const removeUnusedImports = (code: string): string => {
63
+ let result = code;
64
+
65
+ if (!result.includes('<Suspense')) {
66
+ result = result.replaceAll(/,?\s*Suspense\s*,?/g, (match) => {
67
+ if (match.startsWith(',') && match.endsWith(',')) {
68
+ return ',';
69
+ }
70
+ return '';
71
+ });
72
+
73
+ result = result.replaceAll(/{\s*,/g, '{');
74
+ result = result.replaceAll(/,\s*}/g, '}');
75
+ result = result.replaceAll(/{\s*}/g, '');
76
+ result = result.replaceAll(/import\s+{\s*}\s+from\s+["'][^"']+["'];\n?/g, '');
77
+ }
78
+
79
+ if (!result.includes('<Loading') && !result.includes('Loading />')) {
80
+ result = result.replaceAll(
81
+ /import Loading from ["']@\/components\/Loading\/BrandTextLoading["'];\n?/g,
82
+ '',
83
+ );
84
+ }
85
+
86
+ result = result.replaceAll(/\n{3,}/g, '\n\n');
87
+
88
+ return result;
89
+ };
90
+
91
+ export const removeSuspenseFromConversation = async (TEMP_DIR: string) => {
92
+ const filePath = path.join(
93
+ TEMP_DIR,
94
+ 'src/app/[variants]/(main)/agent/features/Conversation/index.tsx',
95
+ );
96
+
97
+ console.log(' Removing Suspense from Conversation/index.tsx...');
98
+
99
+ await updateFile({
100
+ assertAfter: (code) => {
101
+ const noSuspenseElement = !/<Suspense/.test(code);
102
+ return noSuspenseElement;
103
+ },
104
+ filePath,
105
+ name: 'removeSuspenseFromConversation',
106
+ transformer: (code) => {
107
+ invariant(
108
+ /<Suspense/.test(code),
109
+ '[removeSuspenseFromConversation] No Suspense element found in Conversation/index.tsx',
110
+ );
111
+
112
+ let result = removeSuspenseWrapper(code);
113
+ result = removeUnusedImports(result);
114
+
115
+ return result;
116
+ },
117
+ });
118
+ };
119
+
120
+ if (isDirectRun(import.meta.url)) {
121
+ await runStandalone('removeSuspenseFromConversation', removeSuspenseFromConversation, [
122
+ { lang: Lang.Tsx, path: 'src/app/[variants]/(main)/agent/features/Conversation/index.tsx' },
123
+ ]);
124
+ }
@@ -17,6 +17,7 @@ export const modifyRoutes = async (TEMP_DIR: string) => {
17
17
 
18
18
  // Auth & User routes
19
19
  'src/app/[variants]/(auth)',
20
+ 'src/app/[variants]/(mobile)',
20
21
  'src/app/[variants]/(main)/(mobile)/me',
21
22
  'src/app/[variants]/(main)/changelog',
22
23
  'src/app/[variants]/oauth',
@@ -45,13 +46,25 @@ export const modifyRoutes = async (TEMP_DIR: string) => {
45
46
  });
46
47
  }
47
48
 
48
- // 2. Modify desktopRouter.config.tsx
49
+ // 2. Delete root loading.tsx files(not needed in Electron SPA)
50
+ const loadingFiles = ['src/app/loading.tsx', 'src/app/[variants]/loading.tsx'];
51
+ console.log(` Removing ${loadingFiles.length} root loading.tsx files...`);
52
+ for (const file of loadingFiles) {
53
+ const fullPath = path.join(TEMP_DIR, file);
54
+ await removePathEnsuring({
55
+ name: `modifyRoutes:delete:loading:${file}`,
56
+ path: fullPath,
57
+ });
58
+ }
59
+
60
+ // 3. Modify desktopRouter.config.tsx
49
61
  const routerConfigPath = path.join(
50
62
  TEMP_DIR,
51
63
  'src/app/[variants]/router/desktopRouter.config.tsx',
52
64
  );
53
65
  console.log(' Processing src/app/[variants]/router/desktopRouter.config.tsx...');
54
66
  await updateFile({
67
+ assertAfter: (code) => !/\bchangelog\b/.test(code),
55
68
  filePath: routerConfigPath,
56
69
  name: 'modifyRoutes:desktopRouterConfig',
57
70
  transformer: (code) => {
@@ -86,7 +99,6 @@ export const modifyRoutes = async (TEMP_DIR: string) => {
86
99
 
87
100
  return root.text();
88
101
  },
89
- assertAfter: (code) => !/\bchangelog\b/.test(code),
90
102
  });
91
103
  };
92
104
 
@@ -0,0 +1,148 @@
1
+ /* eslint-disable no-undef */
2
+ import { Lang, parse } from '@ast-grep/napi';
3
+ import path from 'node:path';
4
+
5
+ import { invariant, isDirectRun, runStandalone, updateFile } from './utils.mjs';
6
+
7
+ interface DynamicImportInfo {
8
+ componentName: string;
9
+ end: number;
10
+ importPath: string;
11
+ key: string;
12
+ start: number;
13
+ }
14
+
15
+ const isBusinessFeaturesEnabled = () => {
16
+ const raw = process.env.ENABLE_BUSINESS_FEATURES;
17
+ if (!raw) return false;
18
+ const normalized = raw.trim().toLowerCase();
19
+ return normalized === 'true' || normalized === '1';
20
+ };
21
+
22
+ const extractDynamicImportsFromMap = (code: string): DynamicImportInfo[] => {
23
+ const results: DynamicImportInfo[] = [];
24
+
25
+ const regex = /\[SettingsTabs\.(\w+)]:\s*dynamic\(\s*\(\)\s*=>\s*import\(\s*["']([^"']+)["']\s*\)/g;
26
+
27
+ let match;
28
+ while ((match = regex.exec(code)) !== null) {
29
+ const key = match[1];
30
+ const importPath = match[2];
31
+
32
+ const componentName = key.charAt(0).toUpperCase() + key.slice(1) + 'Tab';
33
+
34
+ results.push({
35
+ componentName,
36
+ end: 0,
37
+ importPath,
38
+ key,
39
+ start: 0,
40
+ });
41
+ }
42
+
43
+ return results;
44
+ };
45
+
46
+ const generateStaticImports = (imports: DynamicImportInfo[], keepBusinessTabs: boolean): string => {
47
+ return imports
48
+ .filter((imp) => keepBusinessTabs || !imp.importPath.includes('@/business/'))
49
+ .map((imp) => `import ${imp.componentName} from '${imp.importPath}';`)
50
+ .join('\n');
51
+ };
52
+
53
+ const generateStaticComponentMap = (
54
+ imports: DynamicImportInfo[],
55
+ keepBusinessTabs: boolean,
56
+ ): string => {
57
+ const entries = imports
58
+ .filter((imp) => keepBusinessTabs || !imp.importPath.includes('@/business/'))
59
+ .map((imp) => ` [SettingsTabs.${imp.key}]: ${imp.componentName},`);
60
+
61
+ return `const componentMap: Record<string, React.ComponentType<{ mobile?: boolean }>> = {\n${entries.join('\n')}\n}`;
62
+ };
63
+
64
+ export const convertSettingsContentToStatic = async (TEMP_DIR: string) => {
65
+ const filePath = path.join(
66
+ TEMP_DIR,
67
+ 'src/app/[variants]/(main)/settings/features/SettingsContent.tsx',
68
+ );
69
+
70
+ console.log(' Processing SettingsContent.tsx dynamic imports...');
71
+
72
+ await updateFile({
73
+ assertAfter: (code) => {
74
+ const noDynamic = !/dynamic\(\s*\(\)\s*=>\s*import/.test(code);
75
+ const hasStaticMap = /componentMap:\s*Record<string,/.test(code);
76
+ return noDynamic && hasStaticMap;
77
+ },
78
+ filePath,
79
+ name: 'convertSettingsContentToStatic',
80
+ transformer: (code) => {
81
+ const keepBusinessTabs = isBusinessFeaturesEnabled();
82
+ if (keepBusinessTabs) {
83
+ console.log(
84
+ ' ENABLE_BUSINESS_FEATURES is enabled, preserving business Settings tabs in componentMap',
85
+ );
86
+ }
87
+
88
+ const imports = extractDynamicImportsFromMap(code);
89
+
90
+ invariant(
91
+ imports.length > 0,
92
+ '[convertSettingsContentToStatic] No dynamic imports found in SettingsContent.tsx',
93
+ );
94
+
95
+ console.log(` Found ${imports.length} dynamic imports in componentMap`);
96
+
97
+ const staticImports = generateStaticImports(imports, keepBusinessTabs);
98
+ const staticComponentMap = generateStaticComponentMap(imports, keepBusinessTabs);
99
+
100
+ let result = code;
101
+
102
+ result = result.replace(
103
+ /import dynamic from ["']@\/libs\/next\/dynamic["'];\n?/,
104
+ '',
105
+ );
106
+
107
+ result = result.replace(
108
+ /import Loading from ["']@\/components\/Loading\/BrandTextLoading["'];\n?/,
109
+ '',
110
+ );
111
+
112
+ const ast = parse(Lang.Tsx, result);
113
+ const root = ast.root();
114
+
115
+ const lastImport = root.findAll({
116
+ rule: {
117
+ kind: 'import_statement',
118
+ },
119
+ }).at(-1);
120
+
121
+ invariant(
122
+ lastImport,
123
+ '[convertSettingsContentToStatic] No import statements found in SettingsContent.tsx',
124
+ );
125
+
126
+ const insertPos = lastImport!.range().end.index;
127
+ result = result.slice(0, insertPos) + '\nimport type React from \'react\';\n' + staticImports + result.slice(insertPos);
128
+
129
+ const componentMapRegex = /const componentMap = {[\S\s]*?\n};/;
130
+ invariant(
131
+ componentMapRegex.test(result),
132
+ '[convertSettingsContentToStatic] componentMap declaration not found in SettingsContent.tsx',
133
+ );
134
+
135
+ result = result.replace(componentMapRegex, staticComponentMap + ';');
136
+
137
+ result = result.replaceAll(/\n{3,}/g, '\n\n');
138
+
139
+ return result;
140
+ },
141
+ });
142
+ };
143
+
144
+ if (isDirectRun(import.meta.url)) {
145
+ await runStandalone('convertSettingsContentToStatic', convertSettingsContentToStatic, [
146
+ { lang: Lang.Tsx, path: 'src/app/[variants]/(main)/settings/features/SettingsContent.tsx' },
147
+ ]);
148
+ }
@@ -0,0 +1,73 @@
1
+ import { Lang, parse } from '@ast-grep/napi';
2
+ import path from 'node:path';
3
+
4
+ import { invariant, isDirectRun, runStandalone, updateFile } from './utils.mjs';
5
+
6
+ export const wrapChildrenWithClientOnly = async (TEMP_DIR: string) => {
7
+ const layoutPath = path.join(TEMP_DIR, 'src/app/[variants]/layout.tsx');
8
+
9
+ console.log(' Wrapping children with ClientOnly in layout.tsx...');
10
+
11
+ await updateFile({
12
+ assertAfter: (code) => {
13
+ const hasClientOnlyImport = /import ClientOnly from ["']@\/components\/client\/ClientOnly["']/.test(code);
14
+ const hasLoadingImport = /import Loading from ["']@\/components\/Loading\/BrandTextLoading["']/.test(code);
15
+ const hasClientOnlyWrapper = /<ClientOnly fallback={<Loading/.test(code);
16
+ return hasClientOnlyImport && hasLoadingImport && hasClientOnlyWrapper;
17
+ },
18
+ filePath: layoutPath,
19
+ name: 'wrapChildrenWithClientOnly',
20
+ transformer: (code) => {
21
+ const ast = parse(Lang.Tsx, code);
22
+ const root = ast.root();
23
+
24
+ let result = code;
25
+
26
+ const hasClientOnlyImport = /import ClientOnly from ["']@\/components\/client\/ClientOnly["']/.test(code);
27
+ const hasLoadingImport = /import Loading from ["']@\/components\/Loading\/BrandTextLoading["']/.test(code);
28
+
29
+ const lastImport = root.findAll({
30
+ rule: {
31
+ kind: 'import_statement',
32
+ },
33
+ }).at(-1);
34
+
35
+ invariant(lastImport, '[wrapChildrenWithClientOnly] No import statements found in layout.tsx');
36
+
37
+ const insertPos = lastImport!.range().end.index;
38
+ let importsToAdd = '';
39
+
40
+ if (!hasClientOnlyImport) {
41
+ importsToAdd += "\nimport ClientOnly from '@/components/client/ClientOnly';";
42
+ }
43
+ if (!hasLoadingImport) {
44
+ importsToAdd += "\nimport Loading from '@/components/Loading/BrandTextLoading';";
45
+ }
46
+
47
+ if (importsToAdd) {
48
+ result = result.slice(0, insertPos) + importsToAdd + result.slice(insertPos);
49
+ }
50
+
51
+ const authProviderPattern = /<AuthProvider>\s*{children}\s*<\/AuthProvider>/;
52
+ invariant(
53
+ authProviderPattern.test(result),
54
+ '[wrapChildrenWithClientOnly] Pattern <AuthProvider>{children}</AuthProvider> not found in layout.tsx',
55
+ );
56
+
57
+ result = result.replace(
58
+ authProviderPattern,
59
+ `<AuthProvider>
60
+ <ClientOnly fallback={<Loading />}>{children}</ClientOnly>
61
+ </AuthProvider>`,
62
+ );
63
+
64
+ return result;
65
+ },
66
+ });
67
+ };
68
+
69
+ if (isDirectRun(import.meta.url)) {
70
+ await runStandalone('wrapChildrenWithClientOnly', wrapChildrenWithClientOnly, [
71
+ { lang: Lang.Tsx, path: 'src/app/[variants]/layout.tsx' },
72
+ ]);
73
+ }
@@ -0,0 +1,131 @@
1
+ 'use client';
2
+
3
+ import { getKlavisServerByServerIdentifier, getLobehubSkillProviderById } from '@lobechat/const';
4
+ import { Avatar, Flexbox, Icon } from '@lobehub/ui';
5
+ import { createStyles } from 'antd-style';
6
+ import { Blocks } from 'lucide-react';
7
+ import { type ReactNode, createElement, memo, useMemo, useState } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+
10
+ import SkillStore from '@/features/SkillStore';
11
+ import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
12
+ import { useToolStore } from '@/store/tool';
13
+
14
+ const useStyles = createStyles(({ css, token }) => ({
15
+ banner: css`
16
+ cursor: pointer;
17
+
18
+ position: absolute;
19
+ z-index: 0;
20
+ inset-block-end: 0;
21
+ inset-inline: 0 0;
22
+
23
+ display: flex;
24
+ gap: 12px;
25
+ align-items: center;
26
+ justify-content: space-between;
27
+
28
+ margin-block-end: 6px;
29
+ padding-block: 42px 10px;
30
+ padding-inline: 16px;
31
+ border: 1px solid ${token.colorBorderSecondary};
32
+ border-radius: 20px;
33
+
34
+ background: ${token.colorFillQuaternary};
35
+ box-shadow: 0 12px 32px rgb(0 0 0 / 4%);
36
+
37
+ transition: background 0.2s ease-in-out;
38
+
39
+ &:hover {
40
+ background: ${token.colorFillQuaternary};
41
+ }
42
+ `,
43
+ icon: css`
44
+ color: ${token.colorTextSecondary};
45
+ `,
46
+ text: css`
47
+ font-size: 13px;
48
+ color: ${token.colorTextSecondary};
49
+ `,
50
+ }));
51
+
52
+ const BANNER_SKILL_IDS = [
53
+ { id: 'gmail', type: 'klavis' },
54
+ { id: 'notion', type: 'klavis' },
55
+ { id: 'google-drive', type: 'klavis' },
56
+ { id: 'google-calendar', type: 'klavis' },
57
+ { id: 'slack', type: 'klavis' },
58
+ { id: 'twitter', type: 'lobehub' },
59
+ { id: 'github', type: 'klavis' },
60
+ ] as const;
61
+
62
+ const SkillInstallBanner = memo(() => {
63
+ const { styles } = useStyles();
64
+ const { t } = useTranslation('plugin');
65
+ const [open, setOpen] = useState(false);
66
+
67
+ const isLobehubSkillEnabled = useServerConfigStore(serverConfigSelectors.enableLobehubSkill);
68
+ const isKlavisEnabled = useServerConfigStore(serverConfigSelectors.enableKlavis);
69
+
70
+ // Prefetch skill connections data so SkillStore opens faster
71
+ const [useFetchLobehubSkillConnections, useFetchUserKlavisServers] = useToolStore((s) => [
72
+ s.useFetchLobehubSkillConnections,
73
+ s.useFetchUserKlavisServers,
74
+ ]);
75
+ useFetchLobehubSkillConnections(isLobehubSkillEnabled);
76
+ useFetchUserKlavisServers(isKlavisEnabled);
77
+
78
+ const avatarItems = useMemo(() => {
79
+ const items: Array<{ avatar: ReactNode; key: string; title: string }> = [];
80
+
81
+ for (const skill of BANNER_SKILL_IDS) {
82
+ if (skill.type === 'lobehub') {
83
+ const provider = getLobehubSkillProviderById(skill.id);
84
+ if (provider) {
85
+ items.push({
86
+ avatar:
87
+ typeof provider.icon === 'string'
88
+ ? provider.icon
89
+ : createElement(provider.icon, { size: 14 }),
90
+ key: provider.id,
91
+ title: provider.label,
92
+ });
93
+ }
94
+ } else {
95
+ const server = getKlavisServerByServerIdentifier(skill.id);
96
+ if (server) {
97
+ items.push({
98
+ avatar:
99
+ typeof server.icon === 'string'
100
+ ? server.icon
101
+ : createElement(server.icon, { size: 14 }),
102
+ key: server.identifier,
103
+ title: server.label,
104
+ });
105
+ }
106
+ }
107
+ }
108
+
109
+ return items;
110
+ }, []);
111
+
112
+ // Don't show banner if no skills are enabled
113
+ if (!isLobehubSkillEnabled && !isKlavisEnabled) return null;
114
+
115
+ return (
116
+ <>
117
+ <div className={styles.banner} onClick={() => setOpen(true)}>
118
+ <Flexbox align="center" gap={8} horizontal>
119
+ <Icon className={styles.icon} icon={Blocks} size={18} />
120
+ <span className={styles.text}>{t('skillInstallBanner.title')}</span>
121
+ </Flexbox>
122
+ {avatarItems.length > 0 && <Avatar.Group items={avatarItems} shape="circle" size={24} />}
123
+ </div>
124
+ <SkillStore open={open} setOpen={setOpen} />
125
+ </>
126
+ );
127
+ });
128
+
129
+ SkillInstallBanner.displayName = 'SkillInstallBanner';
130
+
131
+ export default SkillInstallBanner;
@@ -9,6 +9,7 @@ import { useChatStore } from '@/store/chat';
9
9
  import { useHomeStore } from '@/store/home';
10
10
 
11
11
  import ModeHeader from './ModeHeader';
12
+ import SkillInstallBanner from './SkillInstallBanner';
12
13
  import StarterList from './StarterList';
13
14
  import { useSend } from './useSend';
14
15
 
@@ -36,38 +37,44 @@ const InputArea = () => {
36
37
  boxShadow: '0 12px 32px rgba(0,0,0,.04)',
37
38
  },
38
39
  }),
39
- [inputActiveMode],
40
+ [],
40
41
  );
41
42
 
42
43
  return (
43
44
  <Flexbox gap={16} style={{ marginBottom: 16 }}>
44
- <DragUploadZone onUploadFiles={handleUploadFiles}>
45
- <ChatInputProvider
46
- agentId={inboxAgentId}
47
- allowExpand={false}
48
- chatInputEditorRef={(instance) => {
49
- if (!instance) return;
50
- useChatStore.setState({ mainInputEditor: instance });
51
- }}
52
- leftActions={leftActions}
53
- onMarkdownContentChange={(content) => {
54
- useChatStore.setState({ inputMessage: content });
55
- }}
56
- onSend={send}
57
- sendButtonProps={{
58
- disabled: loading,
59
- generating: loading,
60
- onStop: () => {},
61
- shape: 'round',
62
- }}
45
+ <Flexbox style={{ paddingBottom: 32, position: 'relative' }}>
46
+ <SkillInstallBanner />
47
+ <DragUploadZone
48
+ onUploadFiles={handleUploadFiles}
49
+ style={{ position: 'relative', zIndex: 1 }}
63
50
  >
64
- <DesktopChatInput
65
- dropdownPlacement="bottomLeft"
66
- extenHeaderContent={inputActiveMode ? <ModeHeader /> : undefined}
67
- inputContainerProps={inputContainerProps}
68
- />
69
- </ChatInputProvider>
70
- </DragUploadZone>
51
+ <ChatInputProvider
52
+ agentId={inboxAgentId}
53
+ allowExpand={false}
54
+ chatInputEditorRef={(instance) => {
55
+ if (!instance) return;
56
+ useChatStore.setState({ mainInputEditor: instance });
57
+ }}
58
+ leftActions={leftActions}
59
+ onMarkdownContentChange={(content) => {
60
+ useChatStore.setState({ inputMessage: content });
61
+ }}
62
+ onSend={send}
63
+ sendButtonProps={{
64
+ disabled: loading,
65
+ generating: loading,
66
+ onStop: () => {},
67
+ shape: 'round',
68
+ }}
69
+ >
70
+ <DesktopChatInput
71
+ dropdownPlacement="bottomLeft"
72
+ extenHeaderContent={inputActiveMode ? <ModeHeader /> : undefined}
73
+ inputContainerProps={inputContainerProps}
74
+ />
75
+ </ChatInputProvider>
76
+ </DragUploadZone>
77
+ </Flexbox>
71
78
 
72
79
  <StarterList />
73
80
  </Flexbox>
@@ -3,15 +3,19 @@ import { Divider } from 'antd';
3
3
  import { FC, ReactNode } from 'react';
4
4
 
5
5
  interface SettingHeaderProps {
6
+ extra?: ReactNode;
6
7
  title: ReactNode;
7
8
  }
8
9
 
9
- const SettingHeader: FC<SettingHeaderProps> = ({ title }) => {
10
+ const SettingHeader: FC<SettingHeaderProps> = ({ title, extra }) => {
10
11
  return (
11
12
  <Flexbox gap={24} style={{ paddingTop: 12 }}>
12
- <Text fontSize={24} strong>
13
- {title}
14
- </Text>
13
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
14
+ <Text fontSize={24} strong>
15
+ {title}
16
+ </Text>
17
+ {extra}
18
+ </Flexbox>
15
19
  <Divider style={{ margin: 0 }} />
16
20
  </Flexbox>
17
21
  );
@@ -56,6 +56,9 @@ const componentMap = {
56
56
  [SettingsTabs.Security]: dynamic(() => import('../security'), {
57
57
  loading: () => <Loading debugId="Settings > Security" />,
58
58
  }),
59
+ [SettingsTabs.Skill]: dynamic(() => import('../skill'), {
60
+ loading: () => <Loading debugId="Settings > Skill" />,
61
+ }),
59
62
  ...(ENABLE_BUSINESS_FEATURES
60
63
  ? ({
61
64
  [SettingsTabs.Plans]: dynamic(
@@ -2,6 +2,7 @@ import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
2
2
  import { isDesktop } from '@lobechat/const';
3
3
  import { Avatar } from '@lobehub/ui';
4
4
  import {
5
+ Blocks,
5
6
  Brain,
6
7
  BrainCircuit,
7
8
  ChartColumnBigIcon,
@@ -182,6 +183,11 @@ export const useCategory = () => {
182
183
  key: SettingsTabs.Agent,
183
184
  label: t('tab.agent'),
184
185
  },
186
+ {
187
+ icon: Blocks,
188
+ key: SettingsTabs.Skill,
189
+ label: t('tab.skill'),
190
+ },
185
191
  {
186
192
  icon: BrainCircuit,
187
193
  key: SettingsTabs.Memory,