@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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/auth.json +2 -1
- package/locales/ar/models.json +6 -14
- package/locales/ar/providers.json +1 -0
- package/locales/ar/setting.json +1 -0
- package/locales/bg-BG/auth.json +2 -1
- package/locales/bg-BG/models.json +33 -7
- package/locales/bg-BG/providers.json +1 -0
- package/locales/bg-BG/setting.json +1 -0
- package/locales/de-DE/auth.json +2 -1
- package/locales/de-DE/models.json +32 -14
- package/locales/de-DE/providers.json +1 -0
- package/locales/de-DE/setting.json +1 -0
- package/locales/en-US/auth.json +1 -1
- package/locales/en-US/models.json +16 -15
- package/locales/en-US/providers.json +1 -0
- package/locales/en-US/setting.json +1 -0
- package/locales/es-ES/auth.json +2 -1
- package/locales/es-ES/models.json +5 -13
- package/locales/es-ES/providers.json +1 -0
- package/locales/es-ES/setting.json +1 -0
- package/locales/fa-IR/auth.json +2 -1
- package/locales/fa-IR/models.json +38 -12
- package/locales/fa-IR/providers.json +1 -0
- package/locales/fa-IR/setting.json +1 -0
- package/locales/fr-FR/auth.json +2 -1
- package/locales/fr-FR/models.json +43 -15
- package/locales/fr-FR/providers.json +1 -0
- package/locales/fr-FR/setting.json +1 -0
- package/locales/it-IT/auth.json +2 -1
- package/locales/it-IT/models.json +51 -10
- package/locales/it-IT/providers.json +1 -0
- package/locales/it-IT/setting.json +1 -0
- package/locales/ja-JP/auth.json +2 -1
- package/locales/ja-JP/models.json +33 -15
- package/locales/ja-JP/providers.json +1 -0
- package/locales/ja-JP/setting.json +1 -0
- package/locales/ko-KR/auth.json +2 -1
- package/locales/ko-KR/models.json +47 -8
- package/locales/ko-KR/providers.json +1 -0
- package/locales/ko-KR/setting.json +1 -0
- package/locales/nl-NL/auth.json +2 -1
- package/locales/nl-NL/models.json +5 -13
- package/locales/nl-NL/providers.json +1 -0
- package/locales/nl-NL/setting.json +1 -0
- package/locales/pl-PL/auth.json +2 -1
- package/locales/pl-PL/models.json +35 -13
- package/locales/pl-PL/providers.json +1 -0
- package/locales/pl-PL/setting.json +1 -0
- package/locales/pt-BR/auth.json +2 -1
- package/locales/pt-BR/models.json +50 -13
- package/locales/pt-BR/providers.json +1 -0
- package/locales/pt-BR/setting.json +1 -0
- package/locales/ru-RU/auth.json +2 -1
- package/locales/ru-RU/models.json +6 -9
- package/locales/ru-RU/providers.json +1 -0
- package/locales/ru-RU/setting.json +1 -0
- package/locales/tr-TR/auth.json +2 -1
- package/locales/tr-TR/models.json +49 -15
- package/locales/tr-TR/providers.json +1 -0
- package/locales/tr-TR/setting.json +1 -0
- package/locales/vi-VN/auth.json +2 -1
- package/locales/vi-VN/models.json +34 -7
- package/locales/vi-VN/providers.json +1 -0
- package/locales/vi-VN/setting.json +1 -0
- package/locales/zh-CN/auth.json +1 -1
- package/locales/zh-CN/models.json +48 -12
- package/locales/zh-CN/providers.json +1 -0
- package/locales/zh-CN/setting.json +1 -0
- package/locales/zh-TW/auth.json +2 -1
- package/locales/zh-TW/models.json +31 -14
- package/locales/zh-TW/providers.json +1 -0
- package/locales/zh-TW/setting.json +1 -0
- package/package.json +1 -1
- package/packages/types/src/agentGroup/index.ts +4 -0
- package/src/app/[variants]/(main)/agent/profile/features/Header/AgentForkTag.tsx +86 -0
- package/src/app/[variants]/(main)/agent/profile/features/Header/AgentStatusTag.tsx +82 -0
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/AgentHeader.tsx +27 -15
- package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/ForkGroupAndChat.tsx +1 -0
- package/src/app/[variants]/(main)/group/profile/features/GroupProfile/GroupForkTag.tsx +80 -0
- package/src/app/[variants]/(main)/group/profile/features/GroupProfile/GroupHeader.tsx +27 -15
- package/src/app/[variants]/(main)/group/profile/features/GroupProfile/GroupStatusTag.tsx +83 -0
- package/src/app/[variants]/(main)/settings/skill/features/Actions.tsx +0 -3
- package/src/app/[variants]/(main)/settings/skill/features/McpSkillItem.tsx +9 -3
- package/src/app/[variants]/(main)/settings/skill/features/SkillList.tsx +35 -1
- package/src/components/Plugins/PluginTag.tsx +36 -23
- package/src/features/ChatInput/ActionBar/Tools/PopoverContent.tsx +3 -3
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +96 -78
- package/src/features/PluginDetailModal/index.tsx +1 -0
- package/src/features/ProfileEditor/AgentTool.tsx +141 -126
- package/src/features/ProfileEditor/Empty.tsx +22 -0
- package/src/features/ProfileEditor/PluginTag.tsx +33 -7
- package/src/features/ProfileEditor/PopoverContent.tsx +142 -0
- package/src/features/SkillStore/CommunityList/Item.tsx +5 -2
- package/src/features/SkillStore/CustomList/Item.tsx +149 -0
- package/src/features/SkillStore/CustomList/index.tsx +71 -0
- package/src/features/SkillStore/Empty.tsx +3 -0
- package/src/features/SkillStore/Search/index.tsx +5 -0
- package/src/features/SkillStore/SkillStoreContent.tsx +10 -2
- package/src/features/SkillStore/index.tsx +8 -1
- package/src/libs/trpc/lambda/middleware/marketUserInfo.ts +13 -1
- package/src/locales/default/setting.ts +1 -0
- package/src/services/marketApi.ts +1 -1
- package/src/store/agentGroup/selectors/byId.ts +1 -0
- package/src/store/tool/slices/customPlugin/initialState.ts +2 -0
- package/src/store/tool/slices/mcpStore/action.ts +1 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { Avatar, Icon, Tag } from '@lobehub/ui';
|
|
10
10
|
import { createStaticStyles, cssVar } from 'antd-style';
|
|
11
11
|
import isEqual from 'fast-deep-equal';
|
|
12
|
-
import { AlertCircle, X } from 'lucide-react';
|
|
12
|
+
import { AlertCircle, Loader2, X } from 'lucide-react';
|
|
13
13
|
import React, { memo, useMemo } from 'react';
|
|
14
14
|
import { useTranslation } from 'react-i18next';
|
|
15
15
|
|
|
@@ -51,6 +51,21 @@ const LobehubSkillIcon = memo<Pick<LobehubSkillProviderType, 'icon' | 'label'>>(
|
|
|
51
51
|
);
|
|
52
52
|
|
|
53
53
|
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
54
|
+
loadingIcon: css`
|
|
55
|
+
flex-shrink: 0;
|
|
56
|
+
color: ${cssVar.colorTextSecondary};
|
|
57
|
+
animation: spin 1s linear infinite;
|
|
58
|
+
|
|
59
|
+
@keyframes spin {
|
|
60
|
+
from {
|
|
61
|
+
transform: rotate(0deg);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
to {
|
|
65
|
+
transform: rotate(360deg);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`,
|
|
54
69
|
notInstalledTag: css`
|
|
55
70
|
border-color: ${cssVar.colorWarningBorder};
|
|
56
71
|
background: ${cssVar.colorWarningBg};
|
|
@@ -196,11 +211,18 @@ const PluginTag = memo<PluginTagProps>(
|
|
|
196
211
|
type: 'plugin' as const,
|
|
197
212
|
};
|
|
198
213
|
|
|
199
|
-
|
|
214
|
+
// Use identifier as title when loading, otherwise use meta.title
|
|
215
|
+
const displayTitle = meta.title;
|
|
200
216
|
const isDesktopOnly = showDesktopOnlyLabel && !meta.availableInWeb;
|
|
201
217
|
|
|
202
218
|
// Render icon based on type
|
|
203
219
|
const renderIcon = () => {
|
|
220
|
+
// Show loading spinner when loading
|
|
221
|
+
if (isLoading) {
|
|
222
|
+
return <Loader2 className={styles.loadingIcon} size={14} />;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Show warning icon when not installed
|
|
204
226
|
if (!meta.isInstalled) {
|
|
205
227
|
return <AlertCircle className={styles.warningIcon} size={14} />;
|
|
206
228
|
}
|
|
@@ -234,24 +256,28 @@ const PluginTag = memo<PluginTagProps>(
|
|
|
234
256
|
if (isDesktopOnly) {
|
|
235
257
|
text += ` (${t('tools.desktopOnly', { defaultValue: 'Desktop Only' })})`;
|
|
236
258
|
}
|
|
237
|
-
|
|
259
|
+
// Don't show "Not Installed" when loading
|
|
260
|
+
if (!meta.isInstalled && !isLoading) {
|
|
238
261
|
text += ` (${t('tools.notInstalled', { defaultValue: 'Not Installed' })})`;
|
|
239
262
|
}
|
|
240
263
|
return text;
|
|
241
264
|
};
|
|
242
265
|
|
|
266
|
+
// Only show error state when not installed and not loading
|
|
267
|
+
const showErrorState = !meta.isInstalled && !isLoading;
|
|
268
|
+
|
|
243
269
|
return (
|
|
244
270
|
<Tag
|
|
245
271
|
className={styles.tag}
|
|
246
272
|
closable
|
|
247
273
|
closeIcon={<X size={12} />}
|
|
248
|
-
color={
|
|
274
|
+
color={showErrorState ? 'error' : undefined}
|
|
249
275
|
icon={renderIcon()}
|
|
250
276
|
onClose={onRemove}
|
|
251
277
|
title={
|
|
252
|
-
|
|
253
|
-
?
|
|
254
|
-
:
|
|
278
|
+
showErrorState
|
|
279
|
+
? t('tools.notInstalledWarning', { defaultValue: 'This tool is not installed' })
|
|
280
|
+
: undefined
|
|
255
281
|
}
|
|
256
282
|
variant={isDarkMode ? 'filled' : 'outlined'}
|
|
257
283
|
>
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Flexbox, Icon, type ItemType, Segmented } from '@lobehub/ui';
|
|
2
|
+
import { createStaticStyles, cssVar } from 'antd-style';
|
|
3
|
+
import { ArrowRight, ExternalLink, Settings, Store } from 'lucide-react';
|
|
4
|
+
import { type ReactNode, memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { useNavigate } from 'react-router-dom';
|
|
7
|
+
|
|
8
|
+
import Empty from './Empty';
|
|
9
|
+
|
|
10
|
+
type TabType = 'all' | 'installed';
|
|
11
|
+
|
|
12
|
+
const prefixCls = 'ant';
|
|
13
|
+
|
|
14
|
+
const styles = createStaticStyles(({ css }) => ({
|
|
15
|
+
dropdown: css`
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
width: 100%;
|
|
18
|
+
|
|
19
|
+
.${prefixCls}-dropdown-menu {
|
|
20
|
+
border-radius: 0 !important;
|
|
21
|
+
background: transparent !important;
|
|
22
|
+
box-shadow: none !important;
|
|
23
|
+
}
|
|
24
|
+
`,
|
|
25
|
+
footerItem: css`
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
|
|
28
|
+
display: flex;
|
|
29
|
+
gap: 12px;
|
|
30
|
+
align-items: center;
|
|
31
|
+
|
|
32
|
+
padding-block: 8px;
|
|
33
|
+
padding-inline: 12px;
|
|
34
|
+
border-radius: 6px;
|
|
35
|
+
|
|
36
|
+
transition: background-color 0.2s;
|
|
37
|
+
|
|
38
|
+
&:hover {
|
|
39
|
+
background: ${cssVar.colorFillTertiary};
|
|
40
|
+
}
|
|
41
|
+
`,
|
|
42
|
+
footerItemContent: css`
|
|
43
|
+
flex: 1;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
`,
|
|
46
|
+
footerItemIcon: css`
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-shrink: 0;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
|
|
52
|
+
width: 24px;
|
|
53
|
+
height: 24px;
|
|
54
|
+
`,
|
|
55
|
+
header: css`
|
|
56
|
+
padding: ${cssVar.paddingXS};
|
|
57
|
+
border-block-end: 1px solid ${cssVar.colorBorderSecondary};
|
|
58
|
+
background: transparent;
|
|
59
|
+
`,
|
|
60
|
+
scroller: css`
|
|
61
|
+
overflow: hidden auto;
|
|
62
|
+
`,
|
|
63
|
+
trailingIcon: css`
|
|
64
|
+
opacity: 0.5;
|
|
65
|
+
`,
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
interface PopoverContentProps {
|
|
69
|
+
activeTab: TabType;
|
|
70
|
+
installedTabItems: ItemType[];
|
|
71
|
+
menu: ReactNode;
|
|
72
|
+
onClose?: () => void;
|
|
73
|
+
onOpenStore: () => void;
|
|
74
|
+
onTabChange: (tab: TabType) => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const PopoverContent = memo<PopoverContentProps>(
|
|
78
|
+
({ menu, activeTab, onTabChange, installedTabItems, onOpenStore, onClose }) => {
|
|
79
|
+
const { t } = useTranslation('setting');
|
|
80
|
+
const navigate = useNavigate();
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Flexbox className={styles.dropdown} style={{ maxHeight: 500 }}>
|
|
84
|
+
{/* stopPropagation prevents dropdown's onClick from calling preventDefault on Segmented */}
|
|
85
|
+
<div className={styles.header} onClick={(e) => e.stopPropagation()}>
|
|
86
|
+
<Segmented
|
|
87
|
+
block
|
|
88
|
+
onChange={(v) => onTabChange(v as TabType)}
|
|
89
|
+
options={[
|
|
90
|
+
{
|
|
91
|
+
label: t('tools.tabs.all', { defaultValue: 'All' }),
|
|
92
|
+
value: 'all',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
label: t('tools.tabs.installed', { defaultValue: 'Installed' }),
|
|
96
|
+
value: 'installed',
|
|
97
|
+
},
|
|
98
|
+
]}
|
|
99
|
+
size="small"
|
|
100
|
+
value={activeTab}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
<div className={styles.scroller} style={{ flex: 1 }}>
|
|
104
|
+
{activeTab === 'installed' && installedTabItems.length === 0 ? <Empty /> : menu}
|
|
105
|
+
</div>
|
|
106
|
+
<div
|
|
107
|
+
style={{
|
|
108
|
+
borderBlockStart: `1px solid ${cssVar.colorBorderSecondary}`,
|
|
109
|
+
padding: 4,
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<div className={styles.footerItem} onClick={onOpenStore} role="button" tabIndex={0}>
|
|
113
|
+
<div className={styles.footerItemIcon}>
|
|
114
|
+
<Icon icon={Store} size={20} />
|
|
115
|
+
</div>
|
|
116
|
+
<div className={styles.footerItemContent}>{t('skillStore.title')}</div>
|
|
117
|
+
<Icon className={styles.trailingIcon} icon={ArrowRight} size={16} />
|
|
118
|
+
</div>
|
|
119
|
+
<div
|
|
120
|
+
className={styles.footerItem}
|
|
121
|
+
onClick={() => {
|
|
122
|
+
onClose?.();
|
|
123
|
+
navigate('/settings/skill');
|
|
124
|
+
}}
|
|
125
|
+
role="button"
|
|
126
|
+
tabIndex={0}
|
|
127
|
+
>
|
|
128
|
+
<div className={styles.footerItemIcon}>
|
|
129
|
+
<Icon icon={Settings} size={20} />
|
|
130
|
+
</div>
|
|
131
|
+
<div className={styles.footerItemContent}>{t('tools.plugins.management')}</div>
|
|
132
|
+
<Icon className={styles.trailingIcon} icon={ExternalLink} size={16} />
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</Flexbox>
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
PopoverContent.displayName = 'PopoverContent';
|
|
141
|
+
|
|
142
|
+
export default PopoverContent;
|
|
@@ -4,12 +4,13 @@ import { ActionIcon, Block, DropdownMenu, Flexbox, Icon, Modal } from '@lobehub/
|
|
|
4
4
|
import { App, Button } from 'antd';
|
|
5
5
|
import isEqual from 'fast-deep-equal';
|
|
6
6
|
import { MoreVerticalIcon, Plus, Trash2 } from 'lucide-react';
|
|
7
|
-
import React, { memo, useState } from 'react';
|
|
7
|
+
import React, { Suspense, memo, useState } from 'react';
|
|
8
8
|
import { useTranslation } from 'react-i18next';
|
|
9
9
|
|
|
10
10
|
import PluginAvatar from '@/components/Plugins/PluginAvatar';
|
|
11
11
|
import McpDetail from '@/features/MCP/MCPDetail';
|
|
12
12
|
import MCPInstallProgress from '@/features/MCP/MCPInstallProgress';
|
|
13
|
+
import McpDetailLoading from '@/features/MCP/MCPDetail/Loading';
|
|
13
14
|
import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
|
|
14
15
|
import { useAgentStore } from '@/store/agent';
|
|
15
16
|
import { agentSelectors } from '@/store/agent/selectors';
|
|
@@ -148,7 +149,9 @@ const Item = memo<DiscoverMcpItem>(({ name, description, icon, identifier }) =>
|
|
|
148
149
|
title={t('dev.title.skillDetails')}
|
|
149
150
|
width={800}
|
|
150
151
|
>
|
|
151
|
-
<
|
|
152
|
+
<Suspense fallback={<McpDetailLoading />}>
|
|
153
|
+
<McpDetail identifier={identifier} noSettings />
|
|
154
|
+
</Suspense>
|
|
152
155
|
</Modal>
|
|
153
156
|
</>
|
|
154
157
|
);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ActionIcon, Block, DropdownMenu, Flexbox, Icon } from '@lobehub/ui';
|
|
4
|
+
import { App } from 'antd';
|
|
5
|
+
import { createStaticStyles, cssVar } from 'antd-style';
|
|
6
|
+
import { MoreVerticalIcon, PackageSearch, Trash2 } from 'lucide-react';
|
|
7
|
+
import { memo, useState } from 'react';
|
|
8
|
+
import { useTranslation } from 'react-i18next';
|
|
9
|
+
|
|
10
|
+
import PluginAvatar from '@/components/Plugins/PluginAvatar';
|
|
11
|
+
import PluginDetailModal from '@/features/PluginDetailModal';
|
|
12
|
+
import DevModal from '@/features/PluginDevModal';
|
|
13
|
+
import { useAgentStore } from '@/store/agent';
|
|
14
|
+
import { agentSelectors } from '@/store/agent/selectors';
|
|
15
|
+
import { useToolStore } from '@/store/tool';
|
|
16
|
+
import { pluginSelectors } from '@/store/tool/selectors';
|
|
17
|
+
|
|
18
|
+
import { itemStyles } from '../style';
|
|
19
|
+
|
|
20
|
+
const styles = createStaticStyles(({ css }) => ({
|
|
21
|
+
title: css`
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
font-weight: 500;
|
|
28
|
+
color: ${cssVar.colorText};
|
|
29
|
+
text-overflow: ellipsis;
|
|
30
|
+
white-space: nowrap;
|
|
31
|
+
|
|
32
|
+
&:hover {
|
|
33
|
+
color: ${cssVar.colorPrimary};
|
|
34
|
+
}
|
|
35
|
+
`,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
interface ItemProps {
|
|
39
|
+
avatar?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
identifier: string;
|
|
42
|
+
title?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const Item = memo<ItemProps>(({ identifier, title, description, avatar }) => {
|
|
46
|
+
const { t } = useTranslation('plugin');
|
|
47
|
+
const { modal } = App.useApp();
|
|
48
|
+
const [configOpen, setConfigOpen] = useState(false);
|
|
49
|
+
const [detailOpen, setDetailOpen] = useState(false);
|
|
50
|
+
|
|
51
|
+
const [customPlugin, uninstallPlugin, updateCustomPlugin, pluginManifest] = useToolStore((s) => [
|
|
52
|
+
pluginSelectors.getCustomPluginById(identifier)(s),
|
|
53
|
+
s.uninstallPlugin,
|
|
54
|
+
s.updateCustomPlugin,
|
|
55
|
+
pluginSelectors.getToolManifestById(identifier)(s),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
const [togglePlugin, isPluginEnabledInAgent] = useAgentStore((s) => [
|
|
59
|
+
s.togglePlugin,
|
|
60
|
+
agentSelectors.currentAgentPlugins(s).includes(identifier),
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const handleDelete = () => {
|
|
64
|
+
modal.confirm({
|
|
65
|
+
centered: true,
|
|
66
|
+
okButtonProps: { danger: true },
|
|
67
|
+
onOk: async () => {
|
|
68
|
+
if (isPluginEnabledInAgent) {
|
|
69
|
+
await togglePlugin(identifier, false);
|
|
70
|
+
}
|
|
71
|
+
await uninstallPlugin(identifier);
|
|
72
|
+
},
|
|
73
|
+
title: t('store.actions.confirmUninstall'),
|
|
74
|
+
type: 'error',
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
<Flexbox className={itemStyles.container} gap={0}>
|
|
81
|
+
<Block
|
|
82
|
+
align={'center'}
|
|
83
|
+
gap={12}
|
|
84
|
+
horizontal
|
|
85
|
+
paddingBlock={12}
|
|
86
|
+
paddingInline={12}
|
|
87
|
+
variant={'filled'}
|
|
88
|
+
>
|
|
89
|
+
<PluginAvatar avatar={avatar} size={40} />
|
|
90
|
+
<Flexbox flex={1} gap={4} style={{ minWidth: 0, overflow: 'hidden' }}>
|
|
91
|
+
<span className={styles.title} onClick={() => setDetailOpen(true)}>
|
|
92
|
+
{title || identifier}
|
|
93
|
+
</span>
|
|
94
|
+
{description && <span className={itemStyles.description}>{description}</span>}
|
|
95
|
+
</Flexbox>
|
|
96
|
+
<Flexbox horizontal>
|
|
97
|
+
<ActionIcon
|
|
98
|
+
icon={PackageSearch}
|
|
99
|
+
onClick={() => setConfigOpen(true)}
|
|
100
|
+
title={t('store.actions.manifest')}
|
|
101
|
+
/>
|
|
102
|
+
<DropdownMenu
|
|
103
|
+
items={[
|
|
104
|
+
{
|
|
105
|
+
danger: true,
|
|
106
|
+
icon: <Icon icon={Trash2} />,
|
|
107
|
+
key: 'uninstall',
|
|
108
|
+
label: t('store.actions.uninstall'),
|
|
109
|
+
onClick: handleDelete,
|
|
110
|
+
},
|
|
111
|
+
]}
|
|
112
|
+
placement="bottomRight"
|
|
113
|
+
>
|
|
114
|
+
<ActionIcon icon={MoreVerticalIcon} />
|
|
115
|
+
</DropdownMenu>
|
|
116
|
+
</Flexbox>
|
|
117
|
+
</Block>
|
|
118
|
+
</Flexbox>
|
|
119
|
+
{customPlugin && (
|
|
120
|
+
<DevModal
|
|
121
|
+
mode="edit"
|
|
122
|
+
onDelete={async () => {
|
|
123
|
+
if (isPluginEnabledInAgent) {
|
|
124
|
+
await togglePlugin(identifier, false);
|
|
125
|
+
}
|
|
126
|
+
await uninstallPlugin(identifier);
|
|
127
|
+
}}
|
|
128
|
+
onOpenChange={setConfigOpen}
|
|
129
|
+
onSave={async (value) => {
|
|
130
|
+
await updateCustomPlugin(identifier, value);
|
|
131
|
+
}}
|
|
132
|
+
open={configOpen}
|
|
133
|
+
value={customPlugin}
|
|
134
|
+
/>
|
|
135
|
+
)}
|
|
136
|
+
<PluginDetailModal
|
|
137
|
+
id={identifier}
|
|
138
|
+
onClose={() => setDetailOpen(false)}
|
|
139
|
+
open={detailOpen}
|
|
140
|
+
schema={pluginManifest?.settings}
|
|
141
|
+
tab="info"
|
|
142
|
+
/>
|
|
143
|
+
</>
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
Item.displayName = 'CustomListItem';
|
|
148
|
+
|
|
149
|
+
export default Item;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createStaticStyles, responsive } from 'antd-style';
|
|
4
|
+
import isEqual from 'fast-deep-equal';
|
|
5
|
+
import { memo, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useToolStore } from '@/store/tool';
|
|
8
|
+
import { pluginSelectors } from '@/store/tool/selectors';
|
|
9
|
+
|
|
10
|
+
import Empty from '../Empty';
|
|
11
|
+
import Item from './Item';
|
|
12
|
+
|
|
13
|
+
const styles = createStaticStyles(({ css }) => ({
|
|
14
|
+
container: css`
|
|
15
|
+
min-height: 50vh;
|
|
16
|
+
`,
|
|
17
|
+
grid: css`
|
|
18
|
+
display: grid;
|
|
19
|
+
grid-template-columns: repeat(2, 1fr);
|
|
20
|
+
gap: 12px;
|
|
21
|
+
|
|
22
|
+
padding-block-end: 16px;
|
|
23
|
+
padding-inline: 16px;
|
|
24
|
+
|
|
25
|
+
${responsive.sm} {
|
|
26
|
+
grid-template-columns: 1fr;
|
|
27
|
+
}
|
|
28
|
+
`,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
export const CustomList = memo(() => {
|
|
32
|
+
const customPlugins = useToolStore(pluginSelectors.installedCustomPluginMetaList, isEqual);
|
|
33
|
+
const searchKeywords = useToolStore((s) => s.customPluginSearchKeywords || '');
|
|
34
|
+
|
|
35
|
+
const filteredItems = useMemo(() => {
|
|
36
|
+
const lowerKeywords = searchKeywords.toLowerCase().trim();
|
|
37
|
+
if (!lowerKeywords) return customPlugins;
|
|
38
|
+
|
|
39
|
+
return customPlugins.filter((plugin) => {
|
|
40
|
+
const title = plugin.title?.toLowerCase() || '';
|
|
41
|
+
const identifier = plugin.identifier?.toLowerCase() || '';
|
|
42
|
+
return title.includes(lowerKeywords) || identifier.includes(lowerKeywords);
|
|
43
|
+
});
|
|
44
|
+
}, [customPlugins, searchKeywords]);
|
|
45
|
+
|
|
46
|
+
const hasSearchKeywords = Boolean(searchKeywords && searchKeywords.trim());
|
|
47
|
+
|
|
48
|
+
if (filteredItems.length === 0) {
|
|
49
|
+
return <Empty search={hasSearchKeywords} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className={styles.container}>
|
|
54
|
+
<div className={styles.grid}>
|
|
55
|
+
{filteredItems.map((plugin) => (
|
|
56
|
+
<Item
|
|
57
|
+
avatar={plugin.avatar}
|
|
58
|
+
description={plugin.description}
|
|
59
|
+
identifier={plugin.identifier}
|
|
60
|
+
key={plugin.identifier}
|
|
61
|
+
title={plugin.title}
|
|
62
|
+
/>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
CustomList.displayName = 'CustomList';
|
|
70
|
+
|
|
71
|
+
export default CustomList;
|
|
@@ -3,6 +3,8 @@ import { Plug2 } from 'lucide-react';
|
|
|
3
3
|
import { memo } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
|
6
|
+
import AddSkillButton from './AddSkillButton';
|
|
7
|
+
|
|
6
8
|
interface SkillEmptyProps extends Omit<EmptyProps, 'icon'> {
|
|
7
9
|
search?: boolean;
|
|
8
10
|
}
|
|
@@ -13,6 +15,7 @@ const Empty = memo<SkillEmptyProps>(({ search, ...rest }) => {
|
|
|
13
15
|
return (
|
|
14
16
|
<Center height="100%" style={{ minHeight: '50vh' }} width="100%">
|
|
15
17
|
<EmptyComponent
|
|
18
|
+
action={!search && <AddSkillButton />}
|
|
16
19
|
description={search ? t('skillStore.emptySearch') : t('skillStore.empty')}
|
|
17
20
|
descriptionProps={{
|
|
18
21
|
fontSize: 14,
|
|
@@ -17,6 +17,8 @@ export const Search = memo<SearchProps>(({ activeTab, onLobeHubSearch }) => {
|
|
|
17
17
|
const { t } = useTranslation('setting');
|
|
18
18
|
const mcpKeywords = useToolStore((s) => s.mcpSearchKeywords);
|
|
19
19
|
|
|
20
|
+
const isCustomTab = activeTab === SkillStoreTab.Custom;
|
|
21
|
+
|
|
20
22
|
const keywords = activeTab === SkillStoreTab.Community ? mcpKeywords : '';
|
|
21
23
|
|
|
22
24
|
return (
|
|
@@ -28,11 +30,14 @@ export const Search = memo<SearchProps>(({ activeTab, onLobeHubSearch }) => {
|
|
|
28
30
|
onSearch={(keywords: string) => {
|
|
29
31
|
if (activeTab === SkillStoreTab.Community) {
|
|
30
32
|
useToolStore.setState({ mcpSearchKeywords: keywords, searchLoading: true });
|
|
33
|
+
} else if (isCustomTab) {
|
|
34
|
+
useToolStore.setState({ customPluginSearchKeywords: keywords });
|
|
31
35
|
} else {
|
|
32
36
|
onLobeHubSearch(keywords);
|
|
33
37
|
}
|
|
34
38
|
}}
|
|
35
39
|
placeholder={t('skillStore.search')}
|
|
40
|
+
variant="filled"
|
|
36
41
|
/>
|
|
37
42
|
</Flexbox>
|
|
38
43
|
</Flexbox>
|
|
@@ -7,11 +7,13 @@ import { useTranslation } from 'react-i18next';
|
|
|
7
7
|
|
|
8
8
|
import AddSkillButton from './AddSkillButton';
|
|
9
9
|
import CommunityList from './CommunityList';
|
|
10
|
+
import CustomList from './CustomList';
|
|
10
11
|
import LobeHubList from './LobeHubList';
|
|
11
12
|
import Search from './Search';
|
|
12
13
|
|
|
13
14
|
export enum SkillStoreTab {
|
|
14
15
|
Community = 'community',
|
|
16
|
+
Custom = 'custom',
|
|
15
17
|
LobeHub = 'lobehub',
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -23,14 +25,17 @@ export const SkillStoreContent = () => {
|
|
|
23
25
|
const options: SegmentedOptions = [
|
|
24
26
|
{ label: t('skillStore.tabs.lobehub'), value: SkillStoreTab.LobeHub },
|
|
25
27
|
{ label: t('skillStore.tabs.community'), value: SkillStoreTab.Community },
|
|
28
|
+
{ label: t('skillStore.tabs.custom'), value: SkillStoreTab.Custom },
|
|
26
29
|
];
|
|
27
30
|
|
|
28
31
|
const isLobeHub = activeTab === SkillStoreTab.LobeHub;
|
|
32
|
+
const isCommunity = activeTab === SkillStoreTab.Community;
|
|
33
|
+
const isCustom = activeTab === SkillStoreTab.Custom;
|
|
29
34
|
|
|
30
35
|
return (
|
|
31
36
|
<Flexbox gap={8} style={{ maxHeight: '75vh' }} width={'100%'}>
|
|
32
37
|
<Flexbox gap={8} paddingInline={16}>
|
|
33
|
-
<Flexbox align=
|
|
38
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
34
39
|
<Segmented
|
|
35
40
|
block
|
|
36
41
|
onChange={(v) => setActiveTab(v as SkillStoreTab)}
|
|
@@ -46,9 +51,12 @@ export const SkillStoreContent = () => {
|
|
|
46
51
|
<Flexbox flex={1} style={{ display: isLobeHub ? 'flex' : 'none', overflow: 'auto' }}>
|
|
47
52
|
<LobeHubList keywords={lobehubKeywords} />
|
|
48
53
|
</Flexbox>
|
|
49
|
-
<Flexbox flex={1} style={{ display:
|
|
54
|
+
<Flexbox flex={1} style={{ display: isCommunity ? 'flex' : 'none', overflow: 'auto' }}>
|
|
50
55
|
<CommunityList />
|
|
51
56
|
</Flexbox>
|
|
57
|
+
<Flexbox flex={1} style={{ display: isCustom ? 'flex' : 'none', overflow: 'auto' }}>
|
|
58
|
+
<CustomList />
|
|
59
|
+
</Flexbox>
|
|
52
60
|
</Flexbox>
|
|
53
61
|
);
|
|
54
62
|
};
|
|
@@ -3,12 +3,19 @@
|
|
|
3
3
|
import { createModal } from '@lobehub/ui';
|
|
4
4
|
import { t } from 'i18next';
|
|
5
5
|
|
|
6
|
+
import { isDesktop } from '@/const/version';
|
|
7
|
+
import { MarketAuthProvider } from '@/layout/AuthProvider/MarketAuth';
|
|
8
|
+
|
|
6
9
|
import { SkillStoreContent } from './SkillStoreContent';
|
|
7
10
|
|
|
8
11
|
export const createSkillStoreModal = () =>
|
|
9
12
|
createModal({
|
|
10
13
|
allowFullscreen: true,
|
|
11
|
-
children:
|
|
14
|
+
children: (
|
|
15
|
+
<MarketAuthProvider isDesktop={isDesktop}>
|
|
16
|
+
<SkillStoreContent />
|
|
17
|
+
</MarketAuthProvider>
|
|
18
|
+
),
|
|
12
19
|
destroyOnHidden: false,
|
|
13
20
|
footer: null,
|
|
14
21
|
styles: {
|
|
@@ -6,6 +6,7 @@ import type { TrustedClientUserInfo } from '@/libs/trusted-client';
|
|
|
6
6
|
import { trpc } from '../init';
|
|
7
7
|
|
|
8
8
|
interface ContextWithServerDB {
|
|
9
|
+
marketAccessToken?: string;
|
|
9
10
|
serverDB?: LobeChatDatabase;
|
|
10
11
|
userId?: string | null;
|
|
11
12
|
}
|
|
@@ -39,8 +40,19 @@ export const marketUserInfo = trpc.middleware(async (opts) => {
|
|
|
39
40
|
userId: ctx.userId,
|
|
40
41
|
};
|
|
41
42
|
|
|
43
|
+
// Fetch market access token from user_settings.market
|
|
44
|
+
const userModel = new UserModel(ctx.serverDB, ctx.userId);
|
|
45
|
+
const userSettings = await userModel.getUserSettings();
|
|
46
|
+
const marketTokenFromDB = (userSettings?.market as any)?.accessToken;
|
|
47
|
+
|
|
48
|
+
// Prioritize database token over cookie token
|
|
49
|
+
const marketAccessToken = marketTokenFromDB || ctx.marketAccessToken;
|
|
50
|
+
|
|
42
51
|
return opts.next({
|
|
43
|
-
ctx: {
|
|
52
|
+
ctx: {
|
|
53
|
+
marketAccessToken,
|
|
54
|
+
marketUserInfo,
|
|
55
|
+
},
|
|
44
56
|
});
|
|
45
57
|
} catch {
|
|
46
58
|
// If fetching user info fails, continue without it
|
|
@@ -563,6 +563,7 @@ export default {
|
|
|
563
563
|
'skillStore.networkError': 'Network error, please try again',
|
|
564
564
|
'skillStore.search': 'Search skills by name or keyword, press Enter to search…',
|
|
565
565
|
'skillStore.tabs.community': 'Community',
|
|
566
|
+
'skillStore.tabs.custom': 'Custom',
|
|
566
567
|
'skillStore.tabs.lobehub': 'LobeHub',
|
|
567
568
|
'skillStore.title': 'Skill Store',
|
|
568
569
|
'startConversation': 'Start Conversation',
|
|
@@ -45,7 +45,7 @@ export class MarketApiService {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Get agent detail by identifier
|
|
48
|
-
async getAgentDetail(identifier: string): Promise<AgentItemDetail> {
|
|
48
|
+
async getAgentDetail(identifier: string): Promise<AgentItemDetail & { forkedFromAgentId?: string }> {
|
|
49
49
|
return lambdaClient.market.agent.getAgentDetail.query({
|
|
50
50
|
identifier,
|
|
51
51
|
}) as Promise<AgentItemDetail>;
|
|
@@ -22,6 +22,7 @@ const groupMeta = (groupId: string) => (s: ChatGroupStore) => {
|
|
|
22
22
|
avatar: group?.avatar || undefined,
|
|
23
23
|
backgroundColor: group?.backgroundColor || undefined,
|
|
24
24
|
description: group?.description || '',
|
|
25
|
+
marketIdentifier: group?.marketIdentifier || undefined,
|
|
25
26
|
title: group?.title || '',
|
|
26
27
|
});
|
|
27
28
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type LobeToolCustomPlugin } from '@/types/tool/plugin';
|
|
2
2
|
|
|
3
3
|
export interface CustomPluginState {
|
|
4
|
+
customPluginSearchKeywords?: string;
|
|
4
5
|
newCustomPlugin: Partial<LobeToolCustomPlugin>;
|
|
5
6
|
}
|
|
6
7
|
export const defaultCustomPlugin: Partial<LobeToolCustomPlugin> = {
|
|
@@ -13,5 +14,6 @@ export const defaultCustomPlugin: Partial<LobeToolCustomPlugin> = {
|
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export const initialCustomPluginState: CustomPluginState = {
|
|
17
|
+
customPluginSearchKeywords: '',
|
|
16
18
|
newCustomPlugin: defaultCustomPlugin,
|
|
17
19
|
};
|