@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
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+
3
+ import { Tag } from '@lobehub/ui';
4
+ import { memo, useEffect, useMemo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { marketApiService } from '@/services/marketApi';
8
+ import { useAgentStore } from '@/store/agent';
9
+ import { agentSelectors } from '@/store/agent/selectors';
10
+ import type { AgentStatus } from '@/types/discover';
11
+
12
+ /**
13
+ * Agent Status Tag Component
14
+ * Displays the market status of the agent (published/unpublished/archived/deprecated)
15
+ */
16
+ const AgentStatusTag = memo(() => {
17
+ const { t } = useTranslation('setting');
18
+ const [status, setStatus] = useState<AgentStatus | null>(null);
19
+ const [loading, setLoading] = useState(false);
20
+
21
+ const meta = useAgentStore(agentSelectors.currentAgentMeta);
22
+ const marketIdentifier = meta?.marketIdentifier;
23
+
24
+ useEffect(() => {
25
+ if (!marketIdentifier) {
26
+ setStatus(null);
27
+ return;
28
+ }
29
+
30
+ const fetchAgentStatus = async () => {
31
+ try {
32
+ setLoading(true);
33
+ const agentDetail = await marketApiService.getAgentDetail(marketIdentifier);
34
+ setStatus(agentDetail.status as AgentStatus | null);
35
+ } catch (error) {
36
+ console.error('Failed to fetch agent status:', error);
37
+ setStatus(null);
38
+ } finally {
39
+ setLoading(false);
40
+ }
41
+ };
42
+
43
+ fetchAgentStatus();
44
+ }, [marketIdentifier]);
45
+
46
+ const statusConfig = useMemo(() => {
47
+ if (!status) return null;
48
+
49
+ const configs = {
50
+ archived: {
51
+ color: 'orange',
52
+ label: t('marketPublish.status.archived', { defaultValue: 'Archived' }),
53
+ },
54
+ deprecated: {
55
+ color: 'red',
56
+ label: t('marketPublish.status.deprecated', { defaultValue: 'Deprecated' }),
57
+ },
58
+ published: {
59
+ color: 'green',
60
+ label: t('marketPublish.status.published', { defaultValue: 'Published' }),
61
+ },
62
+ unpublished: {
63
+ color: 'default',
64
+ label: t('marketPublish.status.unpublished', { defaultValue: 'Unpublished' }),
65
+ },
66
+ };
67
+
68
+ return configs[status];
69
+ }, [status, t]);
70
+
71
+ if (loading || !statusConfig) return null;
72
+
73
+ return (
74
+ <Tag bordered={false} color={statusConfig.color} style={{ marginRight: 8 }}>
75
+ {statusConfig.label}
76
+ </Tag>
77
+ );
78
+ });
79
+
80
+ AgentStatusTag.displayName = 'AgentStatusTag';
81
+
82
+ export default AgentStatusTag;
@@ -17,6 +17,9 @@ import { useFileStore } from '@/store/file';
17
17
  import { useGlobalStore } from '@/store/global';
18
18
  import { globalGeneralSelectors } from '@/store/global/selectors';
19
19
 
20
+ import AgentForkTag from '../Header/AgentForkTag';
21
+ import AgentStatusTag from '../Header/AgentStatusTag';
22
+
20
23
  const MAX_AVATAR_SIZE = 1024 * 1024; // 1MB limit for server actions
21
24
 
22
25
  const AgentHeader = memo(() => {
@@ -88,6 +91,7 @@ const AgentHeader = memo(() => {
88
91
  return (
89
92
  <Flexbox
90
93
  gap={16}
94
+ horizontal
91
95
  onClick={(e) => {
92
96
  e.stopPropagation();
93
97
  e.preventDefault();
@@ -97,6 +101,7 @@ const AgentHeader = memo(() => {
97
101
  cursor: 'default',
98
102
  }}
99
103
  >
104
+ {/* Avatar Section */}
100
105
  <EmojiPicker
101
106
  allowDelete={!!meta.avatar}
102
107
  allowUpload
@@ -147,21 +152,28 @@ const AgentHeader = memo(() => {
147
152
  size={72}
148
153
  value={meta.avatar}
149
154
  />
150
- <Input
151
- onChange={(e) => {
152
- setLocalTitle(e.target.value);
153
- debouncedSaveTitle(e.target.value);
154
- }}
155
- placeholder={t('settingAgent.name.placeholder', { ns: 'setting' })}
156
- style={{
157
- fontSize: 36,
158
- fontWeight: 600,
159
- padding: 0,
160
- width: '100%',
161
- }}
162
- value={localTitle}
163
- variant={'borderless'}
164
- />
155
+ {/* Title and Tags Section */}
156
+ <Flexbox flex={1} gap={8} style={{ minWidth: 0 }}>
157
+ <Input
158
+ onChange={(e) => {
159
+ setLocalTitle(e.target.value);
160
+ debouncedSaveTitle(e.target.value);
161
+ }}
162
+ placeholder={t('settingAgent.name.placeholder', { ns: 'setting' })}
163
+ style={{
164
+ fontSize: 36,
165
+ fontWeight: 600,
166
+ padding: 0,
167
+ width: '100%',
168
+ }}
169
+ value={localTitle}
170
+ variant={'borderless'}
171
+ />
172
+ <Flexbox gap={8} horizontal>
173
+ <AgentStatusTag />
174
+ <AgentForkTag />
175
+ </Flexbox>
176
+ </Flexbox>
165
177
  </Flexbox>
166
178
  );
167
179
  });
@@ -132,6 +132,7 @@ const ForkGroupAndChat = memo<{ mobile?: boolean }>(() => {
132
132
  // Group content is the supervisor's systemRole (for backward compatibility)
133
133
  content: config.systemRole || supervisorConfig?.systemRole,
134
134
  ...meta,
135
+ marketIdentifier: forkResult.group.identifier, // Store the new market identifier
135
136
  };
136
137
 
137
138
  // Step 5: Prepare member agents from market data
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+
3
+ import { Icon, Tag } from '@lobehub/ui';
4
+ import { GitFork } from 'lucide-react';
5
+ import { memo, useEffect, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { useNavigate } from 'react-router-dom';
8
+
9
+ import { marketApiService } from '@/services/marketApi';
10
+ import { useAgentGroupStore } from '@/store/agentGroup';
11
+ import { agentGroupSelectors } from '@/store/agentGroup/selectors';
12
+ import type { AgentGroupForkSourceResponse } from '@/types/discover';
13
+
14
+ /**
15
+ * Group Fork Tag Component
16
+ * Displays fork source information if the group is forked from another group
17
+ */
18
+ const GroupForkTag = memo(() => {
19
+ const { t } = useTranslation('setting');
20
+ const navigate = useNavigate();
21
+ const [forkSource, setForkSource] = useState<AgentGroupForkSourceResponse['source']>(null);
22
+ const [loading, setLoading] = useState(false);
23
+
24
+ const groupMeta = useAgentGroupStore(agentGroupSelectors.currentGroupMeta);
25
+ const marketIdentifier = groupMeta?.marketIdentifier;
26
+
27
+ useEffect(() => {
28
+ if (!marketIdentifier) {
29
+ setForkSource(null);
30
+ return;
31
+ }
32
+
33
+ const fetchGroupForkInfo = async () => {
34
+ try {
35
+ setLoading(true);
36
+
37
+ // Get fork source info from market using the marketIdentifier
38
+ const forkSourceResponse =
39
+ await marketApiService.getAgentGroupForkSource(marketIdentifier);
40
+
41
+ setForkSource(forkSourceResponse.source);
42
+ } catch (error) {
43
+ console.error('Failed to fetch group fork info:', error);
44
+ setForkSource(null);
45
+ } finally {
46
+ setLoading(false);
47
+ }
48
+ };
49
+
50
+ fetchGroupForkInfo();
51
+ }, [marketIdentifier]);
52
+
53
+ if (loading || !forkSource) return null;
54
+
55
+ const handleClick = () => {
56
+ if (forkSource?.identifier) {
57
+ navigate(`/community/group_agent/${forkSource.identifier}`);
58
+ }
59
+ };
60
+
61
+ return (
62
+ <Tag
63
+ bordered={false}
64
+ color="default"
65
+ icon={<Icon icon={GitFork} />}
66
+ onClick={handleClick}
67
+ style={{ cursor: 'pointer' }}
68
+ title={t('marketPublish.forkFrom.tooltip', {
69
+ agent: forkSource.name,
70
+ defaultValue: `Forked from ${forkSource.name}`,
71
+ })}
72
+ >
73
+ {t('marketPublish.forkFrom.label', { defaultValue: 'Forked from' })} {forkSource.name}
74
+ </Tag>
75
+ );
76
+ });
77
+
78
+ GroupForkTag.displayName = 'GroupForkTag';
79
+
80
+ export default GroupForkTag;
@@ -18,6 +18,9 @@ import { useFileStore } from '@/store/file';
18
18
  import { useGlobalStore } from '@/store/global';
19
19
  import { globalGeneralSelectors } from '@/store/global/selectors';
20
20
 
21
+ import GroupForkTag from './GroupForkTag';
22
+ import GroupStatusTag from './GroupStatusTag';
23
+
21
24
  const MAX_AVATAR_SIZE = 1024 * 1024; // 1MB limit for server actions
22
25
 
23
26
  const GroupHeader = memo(() => {
@@ -89,6 +92,7 @@ const GroupHeader = memo(() => {
89
92
  return (
90
93
  <Flexbox
91
94
  gap={16}
95
+ horizontal
92
96
  onClick={(e) => {
93
97
  e.stopPropagation();
94
98
  e.preventDefault();
@@ -98,6 +102,7 @@ const GroupHeader = memo(() => {
98
102
  cursor: 'default',
99
103
  }}
100
104
  >
105
+ {/* Avatar Section */}
101
106
  <EmojiPicker
102
107
  allowDelete={!!groupMeta.avatar}
103
108
  allowUpload
@@ -159,21 +164,28 @@ const GroupHeader = memo(() => {
159
164
  size={72}
160
165
  value={groupMeta.avatar}
161
166
  />
162
- <Input
163
- onChange={(e) => {
164
- setLocalTitle(e.target.value);
165
- debouncedSaveTitle(e.target.value);
166
- }}
167
- placeholder={t('name.placeholder')}
168
- style={{
169
- fontSize: 36,
170
- fontWeight: 600,
171
- padding: 0,
172
- width: '100%',
173
- }}
174
- value={localTitle}
175
- variant={'borderless'}
176
- />
167
+ {/* Title and Tags Section */}
168
+ <Flexbox flex={1} gap={8} style={{ minWidth: 0 }}>
169
+ <Input
170
+ onChange={(e) => {
171
+ setLocalTitle(e.target.value);
172
+ debouncedSaveTitle(e.target.value);
173
+ }}
174
+ placeholder={t('name.placeholder')}
175
+ style={{
176
+ fontSize: 36,
177
+ fontWeight: 600,
178
+ padding: 0,
179
+ width: '100%',
180
+ }}
181
+ value={localTitle}
182
+ variant={'borderless'}
183
+ />
184
+ <Flexbox gap={8} horizontal>
185
+ <GroupStatusTag />
186
+ <GroupForkTag />
187
+ </Flexbox>
188
+ </Flexbox>
177
189
  </Flexbox>
178
190
  );
179
191
  });
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+
3
+ import { Tag } from '@lobehub/ui';
4
+ import { memo, useEffect, useMemo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { useAgentGroupStore } from '@/store/agentGroup';
8
+ import { agentGroupSelectors } from '@/store/agentGroup/selectors';
9
+ import type { AgentStatus } from '@/types/discover';
10
+
11
+ /**
12
+ * Group Status Tag Component
13
+ * Displays the market status of the agent group
14
+ */
15
+ const GroupStatusTag = memo(() => {
16
+ const { t } = useTranslation('setting');
17
+ const [status, setStatus] = useState<AgentStatus | null>(null);
18
+ const [loading, setLoading] = useState(false);
19
+
20
+ const meta = useAgentGroupStore(agentGroupSelectors.currentGroupMeta);
21
+ const marketIdentifier = meta?.marketIdentifier;
22
+
23
+ useEffect(() => {
24
+ if (!marketIdentifier) {
25
+ setStatus(null);
26
+ return;
27
+ }
28
+
29
+ const fetchGroupStatus = async () => {
30
+ try {
31
+ setLoading(true);
32
+ // TODO: Use getAgentGroupDetail when available
33
+ // For now, groups might not have a separate detail endpoint
34
+ // This is a placeholder - adjust based on actual API
35
+ setStatus('published'); // Temporary: assume published if has identifier
36
+ } catch (error) {
37
+ console.error('Failed to fetch group status:', error);
38
+ setStatus(null);
39
+ } finally {
40
+ setLoading(false);
41
+ }
42
+ };
43
+
44
+ fetchGroupStatus();
45
+ }, [marketIdentifier]);
46
+
47
+ const statusConfig = useMemo(() => {
48
+ if (!status) return null;
49
+
50
+ const configs = {
51
+ archived: {
52
+ color: 'orange',
53
+ label: t('marketPublish.status.archived', { defaultValue: 'Archived' }),
54
+ },
55
+ deprecated: {
56
+ color: 'red',
57
+ label: t('marketPublish.status.deprecated', { defaultValue: 'Deprecated' }),
58
+ },
59
+ published: {
60
+ color: 'green',
61
+ label: t('marketPublish.status.published', { defaultValue: 'Published' }),
62
+ },
63
+ unpublished: {
64
+ color: 'default',
65
+ label: t('marketPublish.status.unpublished', { defaultValue: 'Unpublished' }),
66
+ },
67
+ };
68
+
69
+ return configs[status];
70
+ }, [status, t]);
71
+
72
+ if (loading || !statusConfig) return null;
73
+
74
+ return (
75
+ <Tag bordered={false} color={statusConfig.color}>
76
+ {statusConfig.label}
77
+ </Tag>
78
+ );
79
+ });
80
+
81
+ GroupStatusTag.displayName = 'GroupStatusTag';
82
+
83
+ export default GroupStatusTag;
@@ -47,9 +47,6 @@ const Actions = memo<ActionsProps>(({ identifier, type, isMCP }) => {
47
47
  const [showModal, setModal] = useState(false);
48
48
  const [mcpSettingsOpen, setMcpSettingsOpen] = useState(false);
49
49
 
50
- // 自定义插件(包括自定义 MCP)使用 EditCustomPlugin
51
- // 社区 MCP 使用 McpSettingsModal
52
- // 传统插件使用 PluginDetailModal
53
50
  const isCommunityMCP = !isCustomPlugin && isMCP;
54
51
  const showConfigureButton = isCustomPlugin || isMCP || hasSettings;
55
52
 
@@ -1,13 +1,14 @@
1
1
  'use client';
2
2
 
3
- import { Block, Flexbox, Modal } from '@lobehub/ui';
3
+ import { Flexbox, Modal, Block } from '@lobehub/ui';
4
4
  import { createStaticStyles } from 'antd-style';
5
- import { memo, useState } from 'react';
5
+ import { Suspense, memo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import PluginAvatar from '@/components/Plugins/PluginAvatar';
9
9
  import PluginTag from '@/components/Plugins/PluginTag';
10
10
  import McpDetail from '@/features/MCP/MCPDetail';
11
+ import McpDetailLoading from '@/features/MCP/MCPDetail/Loading';
11
12
  import PluginDetailModal from '@/features/PluginDetailModal';
12
13
  import { useToolStore } from '@/store/tool';
13
14
  import { pluginSelectors } from '@/store/tool/selectors';
@@ -28,6 +29,9 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
28
29
 
29
30
  width: 40px;
30
31
  height: 40px;
32
+ border-radius: 12px;
33
+
34
+ background: ${cssVar.colorFillTertiary};
31
35
  `,
32
36
  title: css`
33
37
  cursor: pointer;
@@ -91,7 +95,9 @@ const McpSkillItem = memo<McpSkillItemProps>(
91
95
  title={t('dev.title.skillDetails')}
92
96
  width={800}
93
97
  >
94
- <McpDetail identifier={identifier} noSettings />
98
+ <Suspense fallback={<McpDetailLoading />}>
99
+ <McpDetail identifier={identifier} noSettings />
100
+ </Suspense>
95
101
  </Modal>
96
102
  )}
97
103
  {isCustomPlugin && (
@@ -29,9 +29,9 @@ import { KlavisServerStatus } from '@/store/tool/slices/klavisStore';
29
29
  import { LobehubSkillStatus } from '@/store/tool/slices/lobehubSkillStore/types';
30
30
  import { type LobeToolType } from '@/types/tool/tool';
31
31
 
32
- import McpSkillItem from './McpSkillItem';
33
32
  import KlavisSkillItem from './KlavisSkillItem';
34
33
  import LobehubSkillItem from './LobehubSkillItem';
34
+ import McpSkillItem from './McpSkillItem';
35
35
 
36
36
  const styles = createStaticStyles(({ css, cssVar }) => ({
37
37
  container: css`
@@ -89,6 +89,9 @@ const SkillList = memo(() => {
89
89
 
90
90
  // If RECOMMENDED_SKILLS is configured, use it to build the list
91
91
  if (RECOMMENDED_SKILLS.length > 0) {
92
+ const addedLobehubIds = new Set<string>();
93
+ const addedKlavisIds = new Set<string>();
94
+
92
95
  for (const skill of RECOMMENDED_SKILLS) {
93
96
  if (skill.type === RecommendedSkillType.Lobehub && isLobehubSkillEnabled) {
94
97
  const provider = getLobehubSkillProviderById(skill.id);
@@ -99,6 +102,37 @@ const SkillList = memo(() => {
99
102
  const serverType = getKlavisServerByServerIdentifier(skill.id);
100
103
  if (serverType) {
101
104
  integrationItems.push({ serverType, type: 'klavis' });
105
+ addedKlavisIds.add(skill.id);
106
+ }
107
+ }
108
+ }
109
+
110
+ // Also add connected Lobehub skills that are not in RECOMMENDED_SKILLS
111
+ if (isLobehubSkillEnabled) {
112
+ for (const server of allLobehubSkillServers) {
113
+ if (
114
+ server.status === LobehubSkillStatus.CONNECTED &&
115
+ !addedLobehubIds.has(server.identifier)
116
+ ) {
117
+ const provider = getLobehubSkillProviderById(server.identifier);
118
+ if (provider) {
119
+ integrationItems.push({ provider, type: 'lobehub' });
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ // Also add connected Klavis skills that are not in RECOMMENDED_SKILLS
126
+ if (isKlavisEnabled) {
127
+ for (const server of allKlavisServers) {
128
+ if (
129
+ server.status === KlavisServerStatus.CONNECTED &&
130
+ !addedKlavisIds.has(server.identifier)
131
+ ) {
132
+ const serverType = getKlavisServerByServerIdentifier(server.identifier);
133
+ if (serverType) {
134
+ integrationItems.push({ serverType, type: 'klavis' });
135
+ }
102
136
  }
103
137
  }
104
138
  }
@@ -3,6 +3,8 @@ import { BadgeCheck, CircleUser, Package } from 'lucide-react';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
+ import MCPTag from './MCPTag';
7
+
6
8
  interface PluginTagProps {
7
9
  author?: string;
8
10
  isMCP?: boolean;
@@ -11,28 +13,39 @@ interface PluginTagProps {
11
13
  type: 'builtin' | 'customPlugin' | 'plugin';
12
14
  }
13
15
 
14
- const PluginTag = memo<PluginTagProps>(({ showIcon = true, author, type, showText = true }) => {
15
- const { t } = useTranslation('plugin');
16
- const isCustom = type === 'customPlugin';
17
- const isOfficial = author === 'LobeHub';
18
-
19
- const customTag = (
20
- <Tag color={'warning'} icon={showIcon && <Icon icon={Package} />} size={'small'}>
21
- {t('store.customPlugin')}
22
- </Tag>
23
- );
24
-
25
- if (isCustom) return customTag;
26
-
27
- return (
28
- <Tag
29
- color={isOfficial ? 'success' : undefined}
30
- icon={showIcon && <Icon icon={isOfficial ? BadgeCheck : CircleUser} />}
31
- size={'small'}
32
- >
33
- {showText && (author || t('store.communityPlugin'))}
34
- </Tag>
35
- );
36
- });
16
+ const PluginTag = memo<PluginTagProps>(
17
+ ({ showIcon = true, author, type, showText = true, isMCP }) => {
18
+ const { t } = useTranslation('plugin');
19
+ const isCustom = type === 'customPlugin';
20
+ const isOfficial = author === 'LobeHub';
21
+
22
+ const customTag = (
23
+ <Tag color={'warning'} icon={showIcon && <Icon icon={Package} />} size={'small'}>
24
+ {t('store.customPlugin')}
25
+ </Tag>
26
+ );
27
+
28
+ if (isMCP) {
29
+ return (
30
+ <>
31
+ <MCPTag showIcon={showIcon} showText={false} />
32
+ {isCustom && customTag}
33
+ </>
34
+ );
35
+ }
36
+
37
+ if (isCustom) return customTag;
38
+
39
+ return (
40
+ <Tag
41
+ color={isOfficial ? 'success' : undefined}
42
+ icon={showIcon && <Icon icon={isOfficial ? BadgeCheck : CircleUser} />}
43
+ size={'small'}
44
+ >
45
+ {showText && (author || t('store.communityPlugin'))}
46
+ </Tag>
47
+ );
48
+ },
49
+ );
37
50
 
38
51
  export default PluginTag;
@@ -1,6 +1,6 @@
1
1
  import { Flexbox, Icon, type ItemType, usePopoverContext } from '@lobehub/ui';
2
2
  import { createStaticStyles, cssVar } from 'antd-style';
3
- import { ChevronRight, Settings, Store } from 'lucide-react';
3
+ import { ChevronRight, ExternalLink, Settings, Store } from 'lucide-react';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { useNavigate } from 'react-router-dom';
@@ -53,7 +53,7 @@ const PopoverContent = memo<PopoverContentProps>(({ items, enableKlavis, onOpenS
53
53
  <div className={toolsListStyles.itemIcon}>
54
54
  <Icon icon={Store} size={20} />
55
55
  </div>
56
- <div className={toolsListStyles.itemContent}>{t('tools.plugins.store')}</div>
56
+ <div className={toolsListStyles.itemContent}>{t('skillStore.title')}</div>
57
57
  <Icon className={styles.trailingIcon} icon={ChevronRight} size={16} />
58
58
  </div>
59
59
  <div
@@ -69,7 +69,7 @@ const PopoverContent = memo<PopoverContentProps>(({ items, enableKlavis, onOpenS
69
69
  <Icon icon={Settings} size={20} />
70
70
  </div>
71
71
  <div className={toolsListStyles.itemContent}>{t('tools.plugins.management')}</div>
72
- <Icon className={styles.trailingIcon} icon={ChevronRight} size={16} />
72
+ <Icon className={styles.trailingIcon} icon={ExternalLink} size={16} />
73
73
  </div>
74
74
  </div>
75
75
  </Flexbox>