@lobehub/lobehub 2.0.0-next.367 → 2.0.0-next.369

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 (107) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/auth.json +2 -1
  4. package/locales/ar/models.json +6 -14
  5. package/locales/ar/providers.json +1 -0
  6. package/locales/ar/setting.json +1 -0
  7. package/locales/bg-BG/auth.json +2 -1
  8. package/locales/bg-BG/models.json +33 -7
  9. package/locales/bg-BG/providers.json +1 -0
  10. package/locales/bg-BG/setting.json +1 -0
  11. package/locales/de-DE/auth.json +2 -1
  12. package/locales/de-DE/models.json +32 -14
  13. package/locales/de-DE/providers.json +1 -0
  14. package/locales/de-DE/setting.json +1 -0
  15. package/locales/en-US/auth.json +1 -1
  16. package/locales/en-US/models.json +16 -15
  17. package/locales/en-US/providers.json +1 -0
  18. package/locales/en-US/setting.json +1 -0
  19. package/locales/es-ES/auth.json +2 -1
  20. package/locales/es-ES/models.json +5 -13
  21. package/locales/es-ES/providers.json +1 -0
  22. package/locales/es-ES/setting.json +1 -0
  23. package/locales/fa-IR/auth.json +2 -1
  24. package/locales/fa-IR/models.json +38 -12
  25. package/locales/fa-IR/providers.json +1 -0
  26. package/locales/fa-IR/setting.json +1 -0
  27. package/locales/fr-FR/auth.json +2 -1
  28. package/locales/fr-FR/models.json +43 -15
  29. package/locales/fr-FR/providers.json +1 -0
  30. package/locales/fr-FR/setting.json +1 -0
  31. package/locales/it-IT/auth.json +2 -1
  32. package/locales/it-IT/models.json +51 -10
  33. package/locales/it-IT/providers.json +1 -0
  34. package/locales/it-IT/setting.json +1 -0
  35. package/locales/ja-JP/auth.json +2 -1
  36. package/locales/ja-JP/models.json +33 -15
  37. package/locales/ja-JP/providers.json +1 -0
  38. package/locales/ja-JP/setting.json +1 -0
  39. package/locales/ko-KR/auth.json +2 -1
  40. package/locales/ko-KR/models.json +47 -8
  41. package/locales/ko-KR/providers.json +1 -0
  42. package/locales/ko-KR/setting.json +1 -0
  43. package/locales/nl-NL/auth.json +2 -1
  44. package/locales/nl-NL/models.json +5 -13
  45. package/locales/nl-NL/providers.json +1 -0
  46. package/locales/nl-NL/setting.json +1 -0
  47. package/locales/pl-PL/auth.json +2 -1
  48. package/locales/pl-PL/models.json +35 -13
  49. package/locales/pl-PL/providers.json +1 -0
  50. package/locales/pl-PL/setting.json +1 -0
  51. package/locales/pt-BR/auth.json +2 -1
  52. package/locales/pt-BR/models.json +50 -13
  53. package/locales/pt-BR/providers.json +1 -0
  54. package/locales/pt-BR/setting.json +1 -0
  55. package/locales/ru-RU/auth.json +2 -1
  56. package/locales/ru-RU/models.json +6 -9
  57. package/locales/ru-RU/providers.json +1 -0
  58. package/locales/ru-RU/setting.json +1 -0
  59. package/locales/tr-TR/auth.json +2 -1
  60. package/locales/tr-TR/models.json +49 -15
  61. package/locales/tr-TR/providers.json +1 -0
  62. package/locales/tr-TR/setting.json +1 -0
  63. package/locales/vi-VN/auth.json +2 -1
  64. package/locales/vi-VN/models.json +34 -7
  65. package/locales/vi-VN/providers.json +1 -0
  66. package/locales/vi-VN/setting.json +1 -0
  67. package/locales/zh-CN/auth.json +1 -1
  68. package/locales/zh-CN/models.json +48 -12
  69. package/locales/zh-CN/providers.json +1 -0
  70. package/locales/zh-CN/setting.json +1 -0
  71. package/locales/zh-TW/auth.json +2 -1
  72. package/locales/zh-TW/models.json +31 -14
  73. package/locales/zh-TW/providers.json +1 -0
  74. package/locales/zh-TW/setting.json +1 -0
  75. package/package.json +1 -1
  76. package/packages/types/src/agentGroup/index.ts +4 -0
  77. package/src/app/[variants]/(main)/agent/profile/features/Header/AgentForkTag.tsx +86 -0
  78. package/src/app/[variants]/(main)/agent/profile/features/Header/AgentStatusTag.tsx +82 -0
  79. package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/AgentHeader.tsx +27 -15
  80. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/ForkGroupAndChat.tsx +1 -0
  81. package/src/app/[variants]/(main)/group/profile/features/GroupProfile/GroupForkTag.tsx +80 -0
  82. package/src/app/[variants]/(main)/group/profile/features/GroupProfile/GroupHeader.tsx +27 -15
  83. package/src/app/[variants]/(main)/group/profile/features/GroupProfile/GroupStatusTag.tsx +83 -0
  84. package/src/app/[variants]/(main)/settings/skill/features/Actions.tsx +0 -3
  85. package/src/app/[variants]/(main)/settings/skill/features/McpSkillItem.tsx +9 -3
  86. package/src/app/[variants]/(main)/settings/skill/features/SkillList.tsx +35 -1
  87. package/src/components/Plugins/PluginTag.tsx +36 -23
  88. package/src/features/ChatInput/ActionBar/Tools/PopoverContent.tsx +3 -3
  89. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +96 -78
  90. package/src/features/PluginDetailModal/index.tsx +1 -0
  91. package/src/features/ProfileEditor/AgentTool.tsx +141 -126
  92. package/src/features/ProfileEditor/Empty.tsx +22 -0
  93. package/src/features/ProfileEditor/PluginTag.tsx +33 -7
  94. package/src/features/ProfileEditor/PopoverContent.tsx +142 -0
  95. package/src/features/SkillStore/CommunityList/Item.tsx +5 -2
  96. package/src/features/SkillStore/CustomList/Item.tsx +149 -0
  97. package/src/features/SkillStore/CustomList/index.tsx +71 -0
  98. package/src/features/SkillStore/Empty.tsx +3 -0
  99. package/src/features/SkillStore/Search/index.tsx +5 -0
  100. package/src/features/SkillStore/SkillStoreContent.tsx +10 -2
  101. package/src/features/SkillStore/index.tsx +8 -1
  102. package/src/libs/trpc/lambda/middleware/marketUserInfo.ts +13 -1
  103. package/src/locales/default/setting.ts +1 -0
  104. package/src/services/marketApi.ts +1 -1
  105. package/src/store/agentGroup/selectors/byId.ts +1 -0
  106. package/src/store/tool/slices/customPlugin/initialState.ts +2 -0
  107. package/src/store/tool/slices/mcpStore/action.ts +1 -0
@@ -6,7 +6,7 @@ import {
6
6
  RECOMMENDED_SKILLS,
7
7
  RecommendedSkillType,
8
8
  } from '@lobechat/const';
9
- import { Avatar, Flexbox, Icon, Image, type ItemType } from '@lobehub/ui';
9
+ import { Avatar, Icon, Image, type ItemType } from '@lobehub/ui';
10
10
  import { cssVar } from 'antd-style';
11
11
  import isEqual from 'fast-deep-equal';
12
12
  import { ToyBrick } from 'lucide-react';
@@ -72,12 +72,6 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
72
72
  s.togglePlugin,
73
73
  ]);
74
74
  const builtinList = useToolStore(builtinToolSelectors.metaList, isEqual);
75
- const enablePluginCount = useAgentStore(
76
- (s) =>
77
- agentByIdSelectors
78
- .getAgentPluginsById(agentId)(s)
79
- .filter((i) => !builtinList.some((b) => b.identifier === i)).length,
80
- );
81
75
  const plugins = useAgentStore((s) => agentByIdSelectors.getAgentPluginsById(agentId)(s));
82
76
 
83
77
  // Klavis 相关状态
@@ -219,10 +213,21 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
219
213
  );
220
214
 
221
215
  // Skills 列表项(包含 LobeHub Skill 和 Klavis)
222
- const skillItems = useMemo(
223
- () => [...lobehubSkillItems, ...klavisServerItems],
224
- [lobehubSkillItems, klavisServerItems],
225
- );
216
+ // 已连接的排在前面
217
+ const skillItems = useMemo(() => {
218
+ const allItems = [...lobehubSkillItems, ...klavisServerItems];
219
+
220
+ return allItems.sort((a, b) => {
221
+ const isConnectedA =
222
+ installedLobehubIds.has(a.key as string) || installedKlavisIds.has(a.key as string);
223
+ const isConnectedB =
224
+ installedLobehubIds.has(b.key as string) || installedKlavisIds.has(b.key as string);
225
+
226
+ if (isConnectedA && !isConnectedB) return -1;
227
+ if (!isConnectedA && isConnectedB) return 1;
228
+ return 0;
229
+ });
230
+ }, [lobehubSkillItems, klavisServerItems, installedLobehubIds, installedKlavisIds]);
226
231
 
227
232
  // 区分社区插件和自定义插件
228
233
  const communityPlugins = list.filter((item) => item.type !== 'customPlugin');
@@ -250,47 +255,55 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
250
255
  ),
251
256
  });
252
257
 
253
- // 构建 Skills 分组的 children
254
- const skillGroupChildren: ItemType[] = [
255
- // 1. LobeHub Skill 和 Klavis
258
+ // 构建 LobeHub 分组的 children(包含内置工具和 LobeHub Skill/Klavis)
259
+ const lobehubGroupChildren: ItemType[] = [
260
+ // 1. 内置工具
261
+ ...builtinItems,
262
+ // 2. LobeHub Skill 和 Klavis(作为内置技能)
256
263
  ...skillItems,
257
- // 2. divider (如果有 skillItems 且有社区插件)
258
- ...(skillItems.length > 0 && communityPlugins.length > 0
259
- ? [{ key: 'divider-skill-community', type: 'divider' as const }]
260
- : []),
261
- // 3. 社区插件
262
- ...communityPlugins.map(mapPluginToItem),
263
- // 4. divider (如果有自定义插件)
264
- ...(customPlugins.length > 0
265
- ? [{ key: 'divider-community-custom', type: 'divider' as const }]
266
- : []),
267
- // 5. 自定义插件
268
- ...customPlugins.map(mapPluginToItem),
269
264
  ];
270
265
 
266
+ // 构建 Community 分组的 children(只包含社区插件)
267
+ const communityGroupChildren: ItemType[] = communityPlugins.map(mapPluginToItem);
268
+
269
+ // 构建 Custom 分组的 children(只包含自定义插件)
270
+ const customGroupChildren: ItemType[] = customPlugins.map(mapPluginToItem);
271
+
271
272
  // 市场 tab 的 items
272
273
  const marketItems: ItemType[] = [
273
- {
274
- children: builtinItems,
275
- key: 'builtins',
276
- label: t('tools.builtins.groupName'),
277
- type: 'group',
278
- },
279
- {
280
- children: skillGroupChildren,
281
- key: 'plugins',
282
- label: (
283
- <Flexbox align={'center'} gap={40} horizontal justify={'space-between'}>
284
- {t('tools.plugins.groupName')}
285
- {enablePluginCount === 0 ? null : (
286
- <div style={{ fontSize: 12, marginInlineEnd: 4 }}>
287
- {t('tools.plugins.enabled', { num: enablePluginCount })}
288
- </div>
289
- )}
290
- </Flexbox>
291
- ),
292
- type: 'group',
293
- },
274
+ // LobeHub 分组
275
+ ...(lobehubGroupChildren.length > 0
276
+ ? [
277
+ {
278
+ children: lobehubGroupChildren,
279
+ key: 'lobehub',
280
+ label: t('skillStore.tabs.lobehub'),
281
+ type: 'group' as const,
282
+ },
283
+ ]
284
+ : []),
285
+ // Community 分组
286
+ ...(communityGroupChildren.length > 0
287
+ ? [
288
+ {
289
+ children: communityGroupChildren,
290
+ key: 'community',
291
+ label: t('skillStore.tabs.community'),
292
+ type: 'group' as const,
293
+ },
294
+ ]
295
+ : []),
296
+ // Custom 分组(只有在有自定义插件时才显示)
297
+ ...(customGroupChildren.length > 0
298
+ ? [
299
+ {
300
+ children: customGroupChildren,
301
+ key: 'custom',
302
+ label: t('skillStore.tabs.custom'),
303
+ type: 'group' as const,
304
+ },
305
+ ]
306
+ : []),
294
307
  ];
295
308
 
296
309
  // 已安装 tab 的 items - 只显示已安装的插件
@@ -319,15 +332,6 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
319
332
  ),
320
333
  }));
321
334
 
322
- if (enabledBuiltinItems.length > 0) {
323
- installedItems.push({
324
- children: enabledBuiltinItems,
325
- key: 'installed-builtins',
326
- label: t('tools.builtins.groupName'),
327
- type: 'group',
328
- });
329
- }
330
-
331
335
  // 已连接的 Klavis 服务器
332
336
  const connectedKlavisItems = klavisServerItems.filter((item) =>
333
337
  checked.includes(item.key as string),
@@ -338,9 +342,30 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
338
342
  checked.includes(item.key as string),
339
343
  );
340
344
 
341
- // 合并已启用的 LobeHub Skill 和 Klavis
345
+ // 合并已启用的 LobeHub Skill 和 Klavis(作为内置技能)
342
346
  const enabledSkillItems = [...connectedLobehubSkillItems, ...connectedKlavisItems];
343
347
 
348
+ // 构建内置工具分组的 children(包含内置工具和 LobeHub Skill/Klavis)
349
+ const allBuiltinItems: ItemType[] = [
350
+ // 1. 内置工具
351
+ ...enabledBuiltinItems,
352
+ // 2. divider (如果有内置工具且有 skill items)
353
+ ...(enabledBuiltinItems.length > 0 && enabledSkillItems.length > 0
354
+ ? [{ key: 'installed-divider-builtin-skill', type: 'divider' as const }]
355
+ : []),
356
+ // 3. LobeHub Skill 和 Klavis
357
+ ...enabledSkillItems,
358
+ ];
359
+
360
+ if (allBuiltinItems.length > 0) {
361
+ installedItems.push({
362
+ children: allBuiltinItems,
363
+ key: 'installed-lobehub',
364
+ label: t('skillStore.tabs.lobehub'),
365
+ type: 'group',
366
+ });
367
+ }
368
+
344
369
  // 已启用的社区插件
345
370
  const enabledCommunityPlugins = communityPlugins
346
371
  .filter((item) => checked.includes(item.identifier))
@@ -389,29 +414,22 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
389
414
  ),
390
415
  }));
391
416
 
392
- // 构建 Skills 分组的 children(带 divider)
393
- const allSkillItems: ItemType[] = [
394
- // 1. LobeHub Skill 和 Klavis
395
- ...enabledSkillItems,
396
- // 2. divider (如果有 skillItems 且有社区插件)
397
- ...(enabledSkillItems.length > 0 && enabledCommunityPlugins.length > 0
398
- ? [{ key: 'installed-divider-skill-community', type: 'divider' as const }]
399
- : []),
400
- // 3. 社区插件
401
- ...enabledCommunityPlugins,
402
- // 4. divider (如果有自定义插件)
403
- ...(enabledCustomPlugins.length > 0
404
- ? [{ key: 'installed-divider-community-custom', type: 'divider' as const }]
405
- : []),
406
- // 5. 自定义插件
407
- ...enabledCustomPlugins,
408
- ];
417
+ // Community 分组(只包含社区插件)
418
+ if (enabledCommunityPlugins.length > 0) {
419
+ installedItems.push({
420
+ children: enabledCommunityPlugins,
421
+ key: 'installed-community',
422
+ label: t('skillStore.tabs.community'),
423
+ type: 'group',
424
+ });
425
+ }
409
426
 
410
- if (allSkillItems.length > 0) {
427
+ // Custom 分组(只包含自定义插件)
428
+ if (enabledCustomPlugins.length > 0) {
411
429
  installedItems.push({
412
- children: allSkillItems,
413
- key: 'installed-plugins',
414
- label: t('tools.plugins.groupName'),
430
+ children: enabledCustomPlugins,
431
+ key: 'installed-custom',
432
+ label: t('skillStore.tabs.custom'),
415
433
  type: 'group',
416
434
  });
417
435
  }
@@ -36,6 +36,7 @@ const PluginDetailModal = memo<PluginDetailModalProps>(
36
36
  return (
37
37
  <Modal
38
38
  allowFullscreen
39
+ destroyOnHidden
39
40
  footer={null}
40
41
  onCancel={onClose}
41
42
  onOk={() => {
@@ -6,10 +6,10 @@ import {
6
6
  LOBEHUB_SKILL_PROVIDERS,
7
7
  type LobehubSkillProviderType,
8
8
  } from '@lobechat/const';
9
- import { Avatar, Button, Flexbox, Icon, type ItemType, Segmented } from '@lobehub/ui';
9
+ import { Avatar, Button, Flexbox, Icon, type ItemType } from '@lobehub/ui';
10
10
  import { createStaticStyles, cssVar } from 'antd-style';
11
11
  import isEqual from 'fast-deep-equal';
12
- import { ArrowRight, PlusIcon, Store, ToyBrick } from 'lucide-react';
12
+ import { PlusIcon, ToyBrick } from 'lucide-react';
13
13
  import React, { Suspense, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
14
14
  import { useTranslation } from 'react-i18next';
15
15
 
@@ -34,29 +34,13 @@ import {
34
34
  import { type LobeToolMetaWithAvailability } from '@/store/tool/slices/builtin/selectors';
35
35
 
36
36
  import PluginTag from './PluginTag';
37
+ import PopoverContent from './PopoverContent';
37
38
 
38
39
  const WEB_BROWSING_IDENTIFIER = 'lobe-web-browsing';
39
40
 
40
41
  type TabType = 'all' | 'installed';
41
42
 
42
- const prefixCls = 'ant';
43
-
44
43
  const styles = createStaticStyles(({ css }) => ({
45
- dropdown: css`
46
- overflow: hidden;
47
- width: 100%;
48
-
49
- .${prefixCls}-dropdown-menu {
50
- border-radius: 0 !important;
51
- background: transparent !important;
52
- box-shadow: none !important;
53
- }
54
- `,
55
- header: css`
56
- padding: ${cssVar.paddingXS};
57
- border-block-end: 1px solid ${cssVar.colorBorderSecondary};
58
- background: transparent;
59
- `,
60
44
  icon: css`
61
45
  flex: none;
62
46
  width: 18px;
@@ -152,6 +136,7 @@ const AgentTool = memo<AgentToolProps>(
152
136
  const isLobehubSkillEnabled = useServerConfigStore(serverConfigSelectors.enableLobehubSkill);
153
137
 
154
138
  const [updating, setUpdating] = useState(false);
139
+ const [dropdownOpen, setDropdownOpen] = useState(false);
155
140
 
156
141
  // Tab state for dual-column layout
157
142
  const [activeTab, setActiveTab] = useState<TabType | null>(null);
@@ -227,10 +212,11 @@ const AgentTool = memo<AgentToolProps>(
227
212
  );
228
213
 
229
214
  // Set default tab based on installed plugins (only on first load)
215
+ // Only show 'installed' tab by default if more than 5 plugins are enabled
230
216
  useEffect(() => {
231
217
  if (!isInitializedRef.current && plugins.length >= 0) {
232
218
  isInitializedRef.current = true;
233
- setActiveTab(plugins.length > 0 ? 'installed' : 'all');
219
+ setActiveTab(plugins.length > 5 ? 'installed' : 'all');
234
220
  }
235
221
  }, [plugins.length]);
236
222
 
@@ -323,11 +309,6 @@ const AgentTool = memo<AgentToolProps>(
323
309
  }
324
310
  };
325
311
 
326
- // Build dropdown menu items (adapted from useControls)
327
- const enablePluginCount = plugins.filter(
328
- (id) => !builtinList.some((b) => b.identifier === id),
329
- ).length;
330
-
331
312
  // 合并 builtin 工具、LobeHub Skill Providers 和 Klavis 服务器
332
313
  const builtinItems = useMemo(
333
314
  () => [
@@ -358,58 +339,85 @@ const AgentTool = memo<AgentToolProps>(
358
339
  [filteredBuiltinList, klavisServerItems, lobehubSkillItems, isToolEnabled, handleToggleTool],
359
340
  );
360
341
 
361
- // Plugin items for dropdown
362
- const pluginItems = useMemo(
363
- () =>
364
- installedPluginList.map((item) => ({
365
- icon: item?.avatar ? (
366
- <PluginAvatar avatar={item.avatar} size={20} />
367
- ) : (
368
- <Icon icon={ToyBrick} size={20} />
369
- ),
370
- key: item.identifier,
371
- label: (
372
- <ToolItem
373
- checked={plugins.includes(item.identifier)}
374
- id={item.identifier}
375
- label={item.title}
376
- onUpdate={async () => {
377
- setUpdating(true);
378
- await togglePlugin(item.identifier);
379
- setUpdating(false);
380
- }}
381
- />
382
- ),
383
- })),
384
- [installedPluginList, plugins, togglePlugin],
342
+ // 区分社区插件和自定义插件
343
+ const communityPlugins = installedPluginList.filter((item) => item.type !== 'customPlugin');
344
+ const customPlugins = installedPluginList.filter((item) => item.type === 'customPlugin');
345
+
346
+ // 生成插件列表项的函数
347
+ const mapPluginToItem = useCallback(
348
+ (item: (typeof installedPluginList)[0]) => ({
349
+ icon: item?.avatar ? (
350
+ <PluginAvatar avatar={item.avatar} size={20} />
351
+ ) : (
352
+ <Icon icon={ToyBrick} size={20} />
353
+ ),
354
+ key: item.identifier,
355
+ label: (
356
+ <ToolItem
357
+ checked={plugins.includes(item.identifier)}
358
+ id={item.identifier}
359
+ label={item.title}
360
+ onUpdate={async () => {
361
+ setUpdating(true);
362
+ await togglePlugin(item.identifier);
363
+ setUpdating(false);
364
+ }}
365
+ />
366
+ ),
367
+ }),
368
+ [plugins, togglePlugin],
369
+ );
370
+
371
+ // Community 插件列表项
372
+ const communityPluginItems = useMemo(
373
+ () => communityPlugins.map(mapPluginToItem),
374
+ [communityPlugins, mapPluginToItem],
375
+ );
376
+
377
+ // Custom 插件列表项
378
+ const customPluginItems = useMemo(
379
+ () => customPlugins.map(mapPluginToItem),
380
+ [customPlugins, mapPluginToItem],
385
381
  );
386
382
 
387
383
  // All tab items (市场 tab)
388
384
  const allTabItems: ItemType[] = useMemo(
389
385
  () => [
390
- {
391
- children: builtinItems,
392
- key: 'builtins',
393
- label: t('tools.builtins.groupName'),
394
- type: 'group',
395
- },
396
- {
397
- children: pluginItems,
398
- key: 'plugins',
399
- label: (
400
- <Flexbox align={'center'} gap={40} horizontal justify={'space-between'}>
401
- {t('tools.plugins.groupName')}
402
- {enablePluginCount === 0 ? null : (
403
- <div style={{ fontSize: 12, marginInlineEnd: 4 }}>
404
- {t('tools.plugins.enabled', { num: enablePluginCount })}
405
- </div>
406
- )}
407
- </Flexbox>
408
- ),
409
- type: 'group',
410
- },
386
+ // LobeHub 分组
387
+ ...(builtinItems.length > 0
388
+ ? [
389
+ {
390
+ children: builtinItems,
391
+ key: 'lobehub',
392
+ label: t('skillStore.tabs.lobehub'),
393
+ type: 'group' as const,
394
+ },
395
+ ]
396
+ : []),
397
+ // Community 分组
398
+ ...(communityPluginItems.length > 0
399
+ ? [
400
+ {
401
+ children: communityPluginItems,
402
+ key: 'community',
403
+ label: t('skillStore.tabs.community'),
404
+ type: 'group' as const,
405
+ },
406
+ ]
407
+ : []),
408
+ // Custom 分组
409
+ ...(customPluginItems.length > 0
410
+ ? [
411
+ {
412
+ children: customPluginItems,
413
+ key: 'custom',
414
+ label: t('skillStore.tabs.custom'),
415
+ type: 'group' as const,
416
+ },
417
+ ]
418
+ : []),
411
419
  ],
412
- [builtinItems, pluginItems, enablePluginCount, t],
420
+ [builtinItems, communityPluginItems, customPluginItems, t],
413
421
  );
414
422
 
415
423
  // Installed tab items - 只显示已启用的
@@ -446,24 +454,57 @@ const AgentTool = memo<AgentToolProps>(
446
454
  plugins.includes(item.key as string),
447
455
  );
448
456
 
449
- // 合并 builtinLobeHub Skill Klavis
450
- const allBuiltinItems = [
457
+ // LobeHub 分组(builtin + LobeHub Skill + Klavis
458
+ const lobehubGroupItems = [
451
459
  ...enabledBuiltinItems,
452
- ...connectedKlavisItems,
453
460
  ...connectedLobehubSkillItems,
461
+ ...connectedKlavisItems,
454
462
  ];
455
463
 
456
- if (allBuiltinItems.length > 0) {
464
+ if (lobehubGroupItems.length > 0) {
465
+ items.push({
466
+ children: lobehubGroupItems,
467
+ key: 'installed-lobehub',
468
+ label: t('skillStore.tabs.lobehub'),
469
+ type: 'group',
470
+ });
471
+ }
472
+
473
+ // 已启用的社区插件
474
+ const enabledCommunityPlugins = communityPlugins
475
+ .filter((item) => plugins.includes(item.identifier))
476
+ .map((item) => ({
477
+ icon: item?.avatar ? (
478
+ <PluginAvatar avatar={item.avatar} size={20} />
479
+ ) : (
480
+ <Icon icon={ToyBrick} size={20} />
481
+ ),
482
+ key: item.identifier,
483
+ label: (
484
+ <ToolItem
485
+ checked={true}
486
+ id={item.identifier}
487
+ label={item.title}
488
+ onUpdate={async () => {
489
+ setUpdating(true);
490
+ await togglePlugin(item.identifier);
491
+ setUpdating(false);
492
+ }}
493
+ />
494
+ ),
495
+ }));
496
+
497
+ if (enabledCommunityPlugins.length > 0) {
457
498
  items.push({
458
- children: allBuiltinItems,
459
- key: 'installed-builtins',
460
- label: t('tools.builtins.groupName'),
499
+ children: enabledCommunityPlugins,
500
+ key: 'installed-community',
501
+ label: t('skillStore.tabs.community'),
461
502
  type: 'group',
462
503
  });
463
504
  }
464
505
 
465
- // 已启用的插件
466
- const installedPlugins = installedPluginList
506
+ // 已启用的自定义插件
507
+ const enabledCustomPlugins = customPlugins
467
508
  .filter((item) => plugins.includes(item.identifier))
468
509
  .map((item) => ({
469
510
  icon: item?.avatar ? (
@@ -486,11 +527,11 @@ const AgentTool = memo<AgentToolProps>(
486
527
  ),
487
528
  }));
488
529
 
489
- if (installedPlugins.length > 0) {
530
+ if (enabledCustomPlugins.length > 0) {
490
531
  items.push({
491
- children: installedPlugins,
492
- key: 'installed-plugins',
493
- label: t('tools.plugins.groupName'),
532
+ children: enabledCustomPlugins,
533
+ key: 'installed-custom',
534
+ label: t('skillStore.tabs.custom'),
494
535
  type: 'group',
495
536
  });
496
537
  }
@@ -500,7 +541,8 @@ const AgentTool = memo<AgentToolProps>(
500
541
  filteredBuiltinList,
501
542
  klavisServerItems,
502
543
  lobehubSkillItems,
503
- installedPluginList,
544
+ communityPlugins,
545
+ customPlugins,
504
546
  plugins,
505
547
  isToolEnabled,
506
548
  handleToggleTool,
@@ -538,8 +580,8 @@ const AgentTool = memo<AgentToolProps>(
538
580
  <>
539
581
  {/* Plugin Selector and Tags */}
540
582
  <Flexbox align="center" gap={8} horizontal wrap={'wrap'}>
541
- {/* Plugin Selector Dropdown - Using Action component pattern */}
542
583
  <Suspense fallback={button}>
584
+ {/* Plugin Selector Dropdown - Using Action component pattern */}
543
585
  <ActionDropdown
544
586
  maxWidth={400}
545
587
  menu={{
@@ -551,48 +593,21 @@ const AgentTool = memo<AgentToolProps>(
551
593
  },
552
594
  }}
553
595
  minWidth={400}
596
+ onOpenChange={setDropdownOpen}
597
+ open={dropdownOpen}
554
598
  placement={'bottomLeft'}
555
599
  popupRender={(menu) => (
556
- <Flexbox className={styles.dropdown} style={{ maxHeight: 500 }}>
557
- {/* stopPropagation prevents dropdown's onClick from calling preventDefault on Segmented */}
558
- <div className={styles.header} onClick={(e) => e.stopPropagation()}>
559
- <Segmented
560
- block
561
- onChange={(v) => setActiveTab(v as TabType)}
562
- options={[
563
- {
564
- label: t('tools.tabs.all', { defaultValue: 'All' }),
565
- value: 'all',
566
- },
567
- {
568
- label: t('tools.tabs.installed', { defaultValue: 'Installed' }),
569
- value: 'installed',
570
- },
571
- ]}
572
- size="small"
573
- value={effectiveTab}
574
- />
575
- </div>
576
- <div className={styles.scroller} style={{ flex: 1 }}>
577
- {menu}
578
- </div>
579
- <Flexbox
580
- align="center"
581
- gap={8}
582
- horizontal
583
- onClick={() => createSkillStoreModal()}
584
- style={{
585
- borderBlockStart: `1px solid ${cssVar.colorBorderSecondary}`,
586
- cursor: 'pointer',
587
- flex: 'none',
588
- padding: cssVar.paddingSM,
589
- }}
590
- >
591
- <Icon icon={Store} />
592
- <span style={{ flex: 1 }}>{t('tools.plugins.store')}</span>
593
- <Icon icon={ArrowRight} />
594
- </Flexbox>
595
- </Flexbox>
600
+ <PopoverContent
601
+ activeTab={effectiveTab}
602
+ installedTabItems={installedTabItems}
603
+ menu={menu}
604
+ onClose={() => setDropdownOpen(false)}
605
+ onOpenStore={() => {
606
+ setDropdownOpen(false);
607
+ createSkillStoreModal()
608
+ }}
609
+ onTabChange={setActiveTab}
610
+ />
596
611
  )}
597
612
  positionerProps={{
598
613
  collisionAvoidance: { align: 'flip', fallbackAxisSide: 'end', side: 'flip' },
@@ -0,0 +1,22 @@
1
+ import { Empty as EmptyComponent } from '@lobehub/ui';
2
+ import { Plug2 } from 'lucide-react';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ const Empty = memo(() => {
7
+ const { t } = useTranslation('setting');
8
+
9
+ return (
10
+ <EmptyComponent
11
+ description={t('tools.installed.empty', {
12
+ defaultValue: 'No skills enabled',
13
+ })}
14
+ icon={Plug2}
15
+ style={{ paddingBlock: 40 }}
16
+ />
17
+ );
18
+ });
19
+
20
+ Empty.displayName = 'ToolEmpty';
21
+
22
+ export default Empty;