@lobehub/lobehub 2.0.0-next.270 → 2.0.0-next.272
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/e2e/src/features/home/starter.feature +34 -0
- package/e2e/src/steps/community/interactions.steps.ts +37 -14
- package/e2e/src/steps/home/starter.steps.ts +216 -0
- package/package.json +1 -1
- package/src/app/[variants]/(main)/agent/features/Conversation/Header/HeaderActions/useMenu.tsx +7 -17
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/AgentTool.tsx +8 -487
- package/src/app/[variants]/(main)/group/profile/features/ProfileEditor/AgentTool.tsx +6 -388
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +1 -2
- package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +0 -1
- package/src/features/ProfileEditor/AgentTool.tsx +549 -0
- package/src/features/ProfileEditor/PluginTag.tsx +213 -0
- package/src/features/ProfileEditor/index.ts +2 -0
- package/src/libs/better-auth/define-config.ts +7 -1
- package/src/store/home/slices/homeInput/action.ts +6 -3
- package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/PluginTag.tsx +0 -195
- package/src/app/[variants]/(main)/group/profile/features/ProfileEditor/PluginTag.tsx +0 -180
|
@@ -1,395 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { Avatar, Button, Flexbox, Icon, type ItemType, Segmented } from '@lobehub/ui';
|
|
5
|
-
import { cssVar } from 'antd-style';
|
|
6
|
-
import isEqual from 'fast-deep-equal';
|
|
7
|
-
import { ArrowRight, PlusIcon, Store, ToyBrick } from 'lucide-react';
|
|
8
|
-
import Image from 'next/image';
|
|
9
|
-
import React, { Suspense, memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
10
|
-
import { useTranslation } from 'react-i18next';
|
|
11
|
-
|
|
12
|
-
import PluginAvatar from '@/components/Plugins/PluginAvatar';
|
|
13
|
-
import KlavisServerItem from '@/features/ChatInput/ActionBar/Tools/KlavisServerItem';
|
|
14
|
-
import ToolItem from '@/features/ChatInput/ActionBar/Tools/ToolItem';
|
|
15
|
-
import ActionDropdown from '@/features/ChatInput/ActionBar/components/ActionDropdown';
|
|
16
|
-
import PluginStore from '@/features/PluginStore';
|
|
17
|
-
import { useCheckPluginsIsInstalled } from '@/hooks/useCheckPluginsIsInstalled';
|
|
18
|
-
import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
|
|
19
|
-
import { useAgentStore } from '@/store/agent';
|
|
20
|
-
import { agentSelectors } from '@/store/agent/selectors';
|
|
21
|
-
import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
22
|
-
import { useToolStore } from '@/store/tool';
|
|
23
|
-
import {
|
|
24
|
-
builtinToolSelectors,
|
|
25
|
-
klavisStoreSelectors,
|
|
26
|
-
pluginSelectors,
|
|
27
|
-
} from '@/store/tool/selectors';
|
|
28
|
-
|
|
29
|
-
import PluginTag from './PluginTag';
|
|
30
|
-
|
|
31
|
-
type TabType = 'all' | 'installed';
|
|
3
|
+
import { AgentTool as SharedAgentTool } from '@/features/ProfileEditor';
|
|
32
4
|
|
|
33
5
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* 对于 IconType 类型的 icon,使用 Icon 组件渲染,并根据主题设置填充色
|
|
6
|
+
* AgentTool for group profile editor
|
|
7
|
+
* - Uses default settings (no web browsing, no filterAvailableInWeb, uses metaList)
|
|
37
8
|
*/
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<Image alt={label} height={18} src={icon} style={{ flex: 'none' }} unoptimized width={18} />
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 使用主题色填充,在深色模式下自动适应
|
|
46
|
-
return <Icon fill={cssVar.colorText} icon={icon} size={18} />;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const AgentTool = memo(() => {
|
|
50
|
-
const { t } = useTranslation('setting');
|
|
51
|
-
const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
|
|
52
|
-
|
|
53
|
-
// Plugin state management
|
|
54
|
-
const plugins = config?.plugins || [];
|
|
55
|
-
|
|
56
|
-
const toggleAgentPlugin = useAgentStore((s) => s.toggleAgentPlugin);
|
|
57
|
-
const installedPluginList = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
|
|
58
|
-
const builtinList = useToolStore(builtinToolSelectors.metaList, isEqual);
|
|
59
|
-
|
|
60
|
-
// Klavis 相关状态
|
|
61
|
-
const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
|
|
62
|
-
const isKlavisEnabledInEnv = useServerConfigStore(serverConfigSelectors.enableKlavis);
|
|
63
|
-
|
|
64
|
-
// Plugin store modal state
|
|
65
|
-
const [modalOpen, setModalOpen] = useState(false);
|
|
66
|
-
const [updating, setUpdating] = useState(false);
|
|
67
|
-
|
|
68
|
-
// Tab state for dual-column layout
|
|
69
|
-
const [activeTab, setActiveTab] = useState<TabType | null>(null);
|
|
70
|
-
const isInitializedRef = useRef(false);
|
|
71
|
-
|
|
72
|
-
// Fetch plugins
|
|
73
|
-
const [useFetchPluginStore, useFetchUserKlavisServers] = useToolStore((s) => [
|
|
74
|
-
s.useFetchPluginStore,
|
|
75
|
-
s.useFetchUserKlavisServers,
|
|
76
|
-
]);
|
|
77
|
-
useFetchPluginStore();
|
|
78
|
-
useFetchInstalledPlugins();
|
|
79
|
-
useCheckPluginsIsInstalled(plugins);
|
|
80
|
-
|
|
81
|
-
// 使用 SWR 加载用户的 Klavis 集成(从数据库)
|
|
82
|
-
useFetchUserKlavisServers(isKlavisEnabledInEnv);
|
|
83
|
-
|
|
84
|
-
// Set default tab based on installed plugins (only on first load)
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
if (!isInitializedRef.current && plugins.length >= 0) {
|
|
87
|
-
isInitializedRef.current = true;
|
|
88
|
-
setActiveTab(plugins.length > 0 ? 'installed' : 'all');
|
|
89
|
-
}
|
|
90
|
-
}, [plugins.length]);
|
|
91
|
-
|
|
92
|
-
// 根据 identifier 获取已连接的服务器
|
|
93
|
-
const getServerByName = (identifier: string) => {
|
|
94
|
-
return allKlavisServers.find((server) => server.identifier === identifier);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// 获取所有 Klavis 服务器类型的 identifier 集合(用于过滤 builtinList)
|
|
98
|
-
const allKlavisTypeIdentifiers = useMemo(
|
|
99
|
-
() => new Set(KLAVIS_SERVER_TYPES.map((type) => type.identifier)),
|
|
100
|
-
[],
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
// 过滤掉 builtinList 中的 klavis 工具(它们会单独显示在 Klavis 区域)
|
|
104
|
-
const filteredBuiltinList = useMemo(
|
|
105
|
-
() =>
|
|
106
|
-
isKlavisEnabledInEnv
|
|
107
|
-
? builtinList.filter((item) => !allKlavisTypeIdentifiers.has(item.identifier))
|
|
108
|
-
: builtinList,
|
|
109
|
-
[builtinList, allKlavisTypeIdentifiers, isKlavisEnabledInEnv],
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
// Klavis 服务器列表项
|
|
113
|
-
const klavisServerItems = useMemo(
|
|
114
|
-
() =>
|
|
115
|
-
isKlavisEnabledInEnv
|
|
116
|
-
? KLAVIS_SERVER_TYPES.map((type) => ({
|
|
117
|
-
icon: <KlavisIcon icon={type.icon} label={type.label} />,
|
|
118
|
-
key: type.identifier,
|
|
119
|
-
label: (
|
|
120
|
-
<KlavisServerItem
|
|
121
|
-
identifier={type.identifier}
|
|
122
|
-
label={type.label}
|
|
123
|
-
server={getServerByName(type.identifier)}
|
|
124
|
-
serverName={type.serverName}
|
|
125
|
-
/>
|
|
126
|
-
),
|
|
127
|
-
}))
|
|
128
|
-
: [],
|
|
129
|
-
[isKlavisEnabledInEnv, allKlavisServers],
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
// Handle plugin remove via Tag close
|
|
133
|
-
const handleRemovePlugin =
|
|
134
|
-
(pluginId: string | { enabled: boolean; identifier: string; settings: Record<string, any> }) =>
|
|
135
|
-
(e: React.MouseEvent) => {
|
|
136
|
-
e.preventDefault();
|
|
137
|
-
e.stopPropagation();
|
|
138
|
-
const identifier = typeof pluginId === 'string' ? pluginId : pluginId?.identifier;
|
|
139
|
-
toggleAgentPlugin(identifier, false);
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// Build dropdown menu items (adapted from useControls)
|
|
143
|
-
const enablePluginCount = plugins.filter(
|
|
144
|
-
(id) => !builtinList.some((b) => b.identifier === id),
|
|
145
|
-
).length;
|
|
146
|
-
|
|
147
|
-
// 合并 builtin 工具和 Klavis 服务器
|
|
148
|
-
const builtinItems = useMemo(
|
|
149
|
-
() => [
|
|
150
|
-
// 原有的 builtin 工具
|
|
151
|
-
...filteredBuiltinList.map((item) => ({
|
|
152
|
-
icon: <Avatar avatar={item.meta.avatar} size={20} style={{ flex: 'none' }} />,
|
|
153
|
-
key: item.identifier,
|
|
154
|
-
label: (
|
|
155
|
-
<ToolItem
|
|
156
|
-
checked={plugins.includes(item.identifier)}
|
|
157
|
-
id={item.identifier}
|
|
158
|
-
label={item.meta?.title}
|
|
159
|
-
onUpdate={async () => {
|
|
160
|
-
setUpdating(true);
|
|
161
|
-
await toggleAgentPlugin(item.identifier);
|
|
162
|
-
setUpdating(false);
|
|
163
|
-
}}
|
|
164
|
-
/>
|
|
165
|
-
),
|
|
166
|
-
})),
|
|
167
|
-
// Klavis 服务器
|
|
168
|
-
...klavisServerItems,
|
|
169
|
-
],
|
|
170
|
-
[filteredBuiltinList, klavisServerItems, plugins, toggleAgentPlugin],
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
// Plugin items for dropdown
|
|
174
|
-
const pluginItems = useMemo(
|
|
175
|
-
() =>
|
|
176
|
-
installedPluginList.map((item) => ({
|
|
177
|
-
icon: item?.avatar ? (
|
|
178
|
-
<PluginAvatar avatar={item.avatar} size={20} />
|
|
179
|
-
) : (
|
|
180
|
-
<Icon icon={ToyBrick} size={20} />
|
|
181
|
-
),
|
|
182
|
-
key: item.identifier,
|
|
183
|
-
label: (
|
|
184
|
-
<ToolItem
|
|
185
|
-
checked={plugins.includes(item.identifier)}
|
|
186
|
-
id={item.identifier}
|
|
187
|
-
label={item.title}
|
|
188
|
-
onUpdate={async () => {
|
|
189
|
-
setUpdating(true);
|
|
190
|
-
await toggleAgentPlugin(item.identifier);
|
|
191
|
-
setUpdating(false);
|
|
192
|
-
}}
|
|
193
|
-
/>
|
|
194
|
-
),
|
|
195
|
-
})),
|
|
196
|
-
[installedPluginList, plugins, toggleAgentPlugin],
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
// All tab items (市场 tab)
|
|
200
|
-
const allTabItems: ItemType[] = useMemo(
|
|
201
|
-
() => [
|
|
202
|
-
{
|
|
203
|
-
children: builtinItems,
|
|
204
|
-
key: 'builtins',
|
|
205
|
-
label: t('tools.builtins.groupName'),
|
|
206
|
-
type: 'group',
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
children: pluginItems,
|
|
210
|
-
key: 'plugins',
|
|
211
|
-
label: (
|
|
212
|
-
<Flexbox align={'center'} gap={40} horizontal justify={'space-between'}>
|
|
213
|
-
{t('tools.plugins.groupName')}
|
|
214
|
-
{enablePluginCount === 0 ? null : (
|
|
215
|
-
<div style={{ fontSize: 12, marginInlineEnd: 4 }}>
|
|
216
|
-
{t('tools.plugins.enabled', { num: enablePluginCount })}
|
|
217
|
-
</div>
|
|
218
|
-
)}
|
|
219
|
-
</Flexbox>
|
|
220
|
-
),
|
|
221
|
-
type: 'group',
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
type: 'divider',
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
extra: <Icon icon={ArrowRight} />,
|
|
228
|
-
icon: Store,
|
|
229
|
-
key: 'plugin-store',
|
|
230
|
-
label: t('tools.plugins.store'),
|
|
231
|
-
onClick: () => {
|
|
232
|
-
setModalOpen(true);
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
[builtinItems, pluginItems, enablePluginCount, t],
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
// Installed tab items - 只显示已启用的
|
|
240
|
-
const installedTabItems: ItemType[] = useMemo(() => {
|
|
241
|
-
const items: ItemType[] = [];
|
|
242
|
-
|
|
243
|
-
// 已启用的 builtin 工具
|
|
244
|
-
const enabledBuiltinItems = filteredBuiltinList
|
|
245
|
-
.filter((item) => plugins.includes(item.identifier))
|
|
246
|
-
.map((item) => ({
|
|
247
|
-
icon: <Avatar avatar={item.meta.avatar} size={20} style={{ flex: 'none' }} />,
|
|
248
|
-
key: item.identifier,
|
|
249
|
-
label: (
|
|
250
|
-
<ToolItem
|
|
251
|
-
checked={true}
|
|
252
|
-
id={item.identifier}
|
|
253
|
-
label={item.meta?.title}
|
|
254
|
-
onUpdate={async () => {
|
|
255
|
-
setUpdating(true);
|
|
256
|
-
await toggleAgentPlugin(item.identifier);
|
|
257
|
-
setUpdating(false);
|
|
258
|
-
}}
|
|
259
|
-
/>
|
|
260
|
-
),
|
|
261
|
-
}));
|
|
262
|
-
|
|
263
|
-
// 已连接且已启用的 Klavis 服务器
|
|
264
|
-
const connectedKlavisItems = klavisServerItems.filter((item) =>
|
|
265
|
-
plugins.includes(item.key as string),
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
// 合并 builtin 和 Klavis
|
|
269
|
-
const allBuiltinItems = [...enabledBuiltinItems, ...connectedKlavisItems];
|
|
270
|
-
|
|
271
|
-
if (allBuiltinItems.length > 0) {
|
|
272
|
-
items.push({
|
|
273
|
-
children: allBuiltinItems,
|
|
274
|
-
key: 'installed-builtins',
|
|
275
|
-
label: t('tools.builtins.groupName'),
|
|
276
|
-
type: 'group',
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// 已启用的插件
|
|
281
|
-
const installedPlugins = installedPluginList
|
|
282
|
-
.filter((item) => plugins.includes(item.identifier))
|
|
283
|
-
.map((item) => ({
|
|
284
|
-
icon: item?.avatar ? (
|
|
285
|
-
<PluginAvatar avatar={item.avatar} size={20} />
|
|
286
|
-
) : (
|
|
287
|
-
<Icon icon={ToyBrick} size={20} />
|
|
288
|
-
),
|
|
289
|
-
key: item.identifier,
|
|
290
|
-
label: (
|
|
291
|
-
<ToolItem
|
|
292
|
-
checked={true}
|
|
293
|
-
id={item.identifier}
|
|
294
|
-
label={item.title}
|
|
295
|
-
onUpdate={async () => {
|
|
296
|
-
setUpdating(true);
|
|
297
|
-
await toggleAgentPlugin(item.identifier);
|
|
298
|
-
setUpdating(false);
|
|
299
|
-
}}
|
|
300
|
-
/>
|
|
301
|
-
),
|
|
302
|
-
}));
|
|
303
|
-
|
|
304
|
-
if (installedPlugins.length > 0) {
|
|
305
|
-
items.push({
|
|
306
|
-
children: installedPlugins,
|
|
307
|
-
key: 'installed-plugins',
|
|
308
|
-
label: t('tools.plugins.groupName'),
|
|
309
|
-
type: 'group',
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return items;
|
|
314
|
-
}, [filteredBuiltinList, klavisServerItems, installedPluginList, plugins, toggleAgentPlugin, t]);
|
|
315
|
-
|
|
316
|
-
// Use effective tab for display (default to all while initializing)
|
|
317
|
-
const effectiveTab = activeTab ?? 'all';
|
|
318
|
-
const currentItems = effectiveTab === 'all' ? allTabItems : installedTabItems;
|
|
319
|
-
|
|
320
|
-
// Final menu items with tab segmented control
|
|
321
|
-
const menuItems: ItemType[] = useMemo(
|
|
322
|
-
() => [
|
|
323
|
-
{
|
|
324
|
-
key: 'tabs',
|
|
325
|
-
label: (
|
|
326
|
-
<Segmented
|
|
327
|
-
block
|
|
328
|
-
onChange={(v) => setActiveTab(v as TabType)}
|
|
329
|
-
options={[
|
|
330
|
-
{
|
|
331
|
-
label: t('tools.tabs.all', { defaultValue: 'All' }),
|
|
332
|
-
value: 'all',
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
label: t('tools.tabs.installed', { defaultValue: 'Installed' }),
|
|
336
|
-
value: 'installed',
|
|
337
|
-
},
|
|
338
|
-
]}
|
|
339
|
-
size="small"
|
|
340
|
-
value={effectiveTab}
|
|
341
|
-
/>
|
|
342
|
-
),
|
|
343
|
-
type: 'group',
|
|
344
|
-
},
|
|
345
|
-
...currentItems,
|
|
346
|
-
],
|
|
347
|
-
[currentItems, effectiveTab, t],
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
const button = (
|
|
351
|
-
<Button
|
|
352
|
-
icon={PlusIcon}
|
|
353
|
-
loading={updating}
|
|
354
|
-
size={'small'}
|
|
355
|
-
style={{ color: cssVar.colorTextSecondary }}
|
|
356
|
-
type={'text'}
|
|
357
|
-
>
|
|
358
|
-
{t('tools.add', { defaultValue: 'Add' })}
|
|
359
|
-
</Button>
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
return (
|
|
363
|
-
<>
|
|
364
|
-
{/* Plugin Selector and Tags */}
|
|
365
|
-
<Flexbox align="center" gap={8} horizontal wrap={'wrap'}>
|
|
366
|
-
{/* Second Row: Selected Plugins as Tags */}
|
|
367
|
-
{plugins?.map((pluginId) => {
|
|
368
|
-
return (
|
|
369
|
-
<PluginTag key={pluginId} onRemove={handleRemovePlugin(pluginId)} pluginId={pluginId} />
|
|
370
|
-
);
|
|
371
|
-
})}
|
|
372
|
-
{/* Plugin Selector Dropdown - Using Action component pattern */}
|
|
373
|
-
|
|
374
|
-
<Suspense fallback={button}>
|
|
375
|
-
<ActionDropdown
|
|
376
|
-
maxHeight={500}
|
|
377
|
-
maxWidth={480}
|
|
378
|
-
menu={{ items: menuItems }}
|
|
379
|
-
minHeight={isKlavisEnabledInEnv ? 500 : undefined}
|
|
380
|
-
minWidth={320}
|
|
381
|
-
placement={'bottomLeft'}
|
|
382
|
-
trigger={['click']}
|
|
383
|
-
>
|
|
384
|
-
{button}
|
|
385
|
-
</ActionDropdown>
|
|
386
|
-
</Suspense>
|
|
387
|
-
</Flexbox>
|
|
388
|
-
|
|
389
|
-
{/* PluginStore Modal */}
|
|
390
|
-
<PluginStore open={modalOpen} setOpen={setModalOpen} />
|
|
391
|
-
</>
|
|
392
|
-
);
|
|
393
|
-
});
|
|
9
|
+
const AgentTool = () => {
|
|
10
|
+
return <SharedAgentTool />;
|
|
11
|
+
};
|
|
394
12
|
|
|
395
13
|
export default AgentTool;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Flexbox, Icon
|
|
1
|
+
import { Checkbox, Flexbox, Icon } from '@lobehub/ui';
|
|
2
2
|
import { Loader2, SquareArrowOutUpRight, Unplug } from 'lucide-react';
|
|
3
3
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
@@ -336,7 +336,6 @@ const KlavisServerItem = memo<KlavisServerItemProps>(
|
|
|
336
336
|
handleToggle();
|
|
337
337
|
}
|
|
338
338
|
}}
|
|
339
|
-
style={{ paddingLeft: 8 }}
|
|
340
339
|
>
|
|
341
340
|
<Flexbox align={'center'} gap={8} horizontal>
|
|
342
341
|
{label}
|