@lobehub/lobehub 2.0.0-next.376 → 2.0.0-next.378

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 (62) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/changelog/v1.json +21 -0
  3. package/docs/development/database-schema.dbml +51 -0
  4. package/docs/self-hosting/advanced/auth/providers/casdoor.mdx +1 -1
  5. package/docs/self-hosting/advanced/auth/providers/casdoor.zh-CN.mdx +1 -1
  6. package/docs/self-hosting/advanced/auth/providers/logto.mdx +1 -1
  7. package/docs/self-hosting/advanced/auth/providers/logto.zh-CN.mdx +1 -1
  8. package/package.json +1 -2
  9. package/packages/database/migrations/0075_add_user_memory_persona.sql +51 -0
  10. package/packages/database/migrations/meta/0075_snapshot.json +11957 -0
  11. package/packages/database/migrations/meta/_journal.json +8 -1
  12. package/packages/database/src/schemas/userMemories/persona.ts +81 -0
  13. package/scripts/_shared/checkDeprecatedAuth.js +46 -16
  14. package/scripts/_shared/checkDeprecatedAuth.test.ts +180 -0
  15. package/src/app/(backend)/api/webhooks/casdoor/route.ts +1 -2
  16. package/src/app/(backend)/api/webhooks/logto/route.ts +2 -3
  17. package/src/app/(backend)/trpc/async/[trpc]/route.ts +1 -2
  18. package/src/app/(backend)/trpc/mobile/[trpc]/route.ts +1 -2
  19. package/src/app/[variants]/(main)/agent/profile/features/ProfileEditor/index.tsx +1 -0
  20. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/AgentProfilePopup.tsx +9 -0
  21. package/src/app/[variants]/(main)/group/_layout/Sidebar/GroupConfig/GroupMember.tsx +27 -2
  22. package/src/app/[variants]/(main)/home/_layout/Body/Agent/Actions.tsx +1 -1
  23. package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +40 -32
  24. package/src/app/[variants]/(main)/memory/(home)/index.tsx +1 -1
  25. package/src/app/[variants]/(main)/memory/contexts/index.tsx +2 -0
  26. package/src/app/[variants]/(main)/memory/experiences/index.tsx +2 -0
  27. package/src/app/[variants]/(main)/memory/features/MemoryAnalysis/Action.tsx +13 -2
  28. package/src/app/[variants]/(main)/memory/features/MemoryAnalysis/AnalysisTrigger.tsx +26 -13
  29. package/src/app/[variants]/(main)/memory/features/MemoryAnalysis/index.tsx +10 -1
  30. package/src/app/[variants]/(main)/memory/identities/index.tsx +2 -0
  31. package/src/app/[variants]/(main)/memory/preferences/index.tsx +2 -0
  32. package/src/app/[variants]/(main)/resource/library/_layout/Header/LibraryHead.tsx +7 -3
  33. package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +30 -30
  34. package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +31 -31
  35. package/src/app/[variants]/(main)/settings/skill/index.tsx +2 -2
  36. package/src/components/FileParsingStatus/EmbeddingStatus.tsx +3 -16
  37. package/src/components/FileParsingStatus/index.tsx +2 -15
  38. package/src/features/ChatInput/ActionBar/Tools/PopoverContent.tsx +1 -3
  39. package/src/features/ChatInput/ActionBar/Tools/ToolsList.tsx +4 -0
  40. package/src/features/ChatInput/ActionBar/Tools/index.tsx +1 -10
  41. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +41 -16
  42. package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +2 -1
  43. package/src/features/Conversation/ChatItem/components/Title.tsx +6 -2
  44. package/src/features/ModelSelect/index.tsx +10 -3
  45. package/src/features/ProfileEditor/AgentTool.tsx +52 -33
  46. package/src/features/ProfileEditor/PopoverContent.tsx +28 -61
  47. package/src/features/SharePopover/index.tsx +3 -3
  48. package/src/features/SkillStore/CommunityList/Item.tsx +2 -1
  49. package/src/features/SkillStore/CommunityList/index.tsx +16 -22
  50. package/src/features/SkillStore/CustomList/Item.tsx +2 -1
  51. package/src/features/SkillStore/CustomList/index.tsx +11 -31
  52. package/src/features/SkillStore/LobeHubList/Item.tsx +4 -3
  53. package/src/features/SkillStore/LobeHubList/index.tsx +2 -18
  54. package/src/features/SkillStore/Search/index.tsx +1 -1
  55. package/src/features/SkillStore/index.tsx +6 -3
  56. package/src/features/SkillStore/style.ts +34 -1
  57. package/src/libs/next/config/define-config.ts +0 -3
  58. package/src/server/routers/lambda/agent.ts +1 -2
  59. package/src/server/services/user/index.ts +1 -2
  60. package/src/server/services/webhookUser/index.test.ts +290 -0
  61. package/src/server/services/webhookUser/index.ts +29 -12
  62. package/src/libs/logger/index.ts +0 -5
@@ -18,12 +18,11 @@ const styles = createStaticStyles(({ css }) => ({
18
18
  }));
19
19
 
20
20
  interface PopoverContentProps {
21
- enableKlavis: boolean;
22
21
  items: ItemType[];
23
22
  onOpenStore: () => void;
24
23
  }
25
24
 
26
- const PopoverContent = memo<PopoverContentProps>(({ items, enableKlavis, onOpenStore }) => {
25
+ const PopoverContent = memo<PopoverContentProps>(({ items, onOpenStore }) => {
27
26
  const { t } = useTranslation('setting');
28
27
  const navigate = useNavigate();
29
28
 
@@ -34,7 +33,6 @@ const PopoverContent = memo<PopoverContentProps>(({ items, enableKlavis, onOpenS
34
33
  <div
35
34
  style={{
36
35
  maxHeight: 500,
37
- minHeight: enableKlavis ? 500 : undefined,
38
36
  overflowY: 'auto',
39
37
  }}
40
38
  >
@@ -38,6 +38,10 @@ export const toolsListStyles = createStaticStyles(({ css }) => ({
38
38
 
39
39
  width: 24px;
40
40
  height: 24px;
41
+
42
+ .ant-avatar {
43
+ margin-inline-end: 0;
44
+ }
41
45
  `,
42
46
  }));
43
47
 
@@ -6,7 +6,6 @@ import { createSkillStoreModal } from '@/features/SkillStore';
6
6
  import { useModelSupportToolUse } from '@/hooks/useModelSupportToolUse';
7
7
  import { useAgentStore } from '@/store/agent';
8
8
  import { agentByIdSelectors } from '@/store/agent/selectors';
9
- import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfig';
10
9
 
11
10
  import { useAgentId } from '../../hooks/useAgentId';
12
11
  import Action from '../components/Action';
@@ -20,8 +19,6 @@ const Tools = memo(() => {
20
19
  setUpdating,
21
20
  });
22
21
 
23
- const enableKlavis = useServerConfigStore(serverConfigSelectors.enableKlavis);
24
-
25
22
  const agentId = useAgentId();
26
23
  const model = useAgentStore((s) => agentByIdSelectors.getAgentModelById(agentId)(s));
27
24
  const provider = useAgentStore((s) => agentByIdSelectors.getAgentModelProviderById(agentId)(s));
@@ -41,13 +38,7 @@ const Tools = memo(() => {
41
38
  icon={Blocks}
42
39
  loading={updating}
43
40
  popover={{
44
- content: (
45
- <PopoverContent
46
- enableKlavis={enableKlavis}
47
- items={marketItems}
48
- onOpenStore={handleOpenStore}
49
- />
50
- ),
41
+ content: <PopoverContent items={marketItems} onOpenStore={handleOpenStore} />,
51
42
  maxWidth: 320,
52
43
  minWidth: 320,
53
44
  styles: {
@@ -6,7 +6,7 @@ import {
6
6
  RECOMMENDED_SKILLS,
7
7
  RecommendedSkillType,
8
8
  } from '@lobechat/const';
9
- import { Avatar, Icon, Image, type ItemType } from '@lobehub/ui';
9
+ import { Avatar, Icon, 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';
@@ -32,18 +32,25 @@ import KlavisServerItem from './KlavisServerItem';
32
32
  import LobehubSkillServerItem from './LobehubSkillServerItem';
33
33
  import ToolItem from './ToolItem';
34
34
 
35
+ const SKILL_ICON_SIZE = 20;
36
+
35
37
  /**
36
38
  * Klavis 服务器图标组件
37
- * 对于 string 类型的 icon,使用 Image 组件渲染
38
- * 对于 IconType 类型的 icon,使用 Icon 组件渲染,并根据主题设置填充色
39
39
  */
40
40
  const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label }) => {
41
41
  if (typeof icon === 'string') {
42
- return <Image alt={label} height={18} src={icon} style={{ flex: 'none' }} width={18} />;
42
+ return (
43
+ <Avatar
44
+ alt={label}
45
+ avatar={icon}
46
+ shape={'square'}
47
+ size={SKILL_ICON_SIZE}
48
+ style={{ flex: 'none' }}
49
+ />
50
+ );
43
51
  }
44
52
 
45
- // 使用主题色填充,在深色模式下自动适应
46
- return <Icon fill={cssVar.colorText} icon={icon} size={18} />;
53
+ return <Icon fill={cssVar.colorText} icon={icon} size={SKILL_ICON_SIZE} />;
47
54
  });
48
55
 
49
56
  KlavisIcon.displayName = 'KlavisIcon';
@@ -54,10 +61,18 @@ KlavisIcon.displayName = 'KlavisIcon';
54
61
  const LobehubSkillIcon = memo<Pick<LobehubSkillProviderType, 'icon' | 'label'>>(
55
62
  ({ icon, label }) => {
56
63
  if (typeof icon === 'string') {
57
- return <Image alt={label} height={18} src={icon} style={{ flex: 'none' }} width={18} />;
64
+ return (
65
+ <Avatar
66
+ alt={label}
67
+ avatar={icon}
68
+ shape={'square'}
69
+ size={SKILL_ICON_SIZE}
70
+ style={{ flex: 'none' }}
71
+ />
72
+ );
58
73
  }
59
74
 
60
- return <Icon fill={cssVar.colorText} icon={icon} size={18} />;
75
+ return <Icon fill={cssVar.colorText} icon={icon} size={SKILL_ICON_SIZE} />;
61
76
  },
62
77
  );
63
78
 
@@ -193,7 +208,12 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
193
208
  () =>
194
209
  filteredBuiltinList.map((item) => ({
195
210
  icon: (
196
- <Avatar avatar={item.meta.avatar} shape={'square'} size={20} style={{ flex: 'none' }} />
211
+ <Avatar
212
+ avatar={item.meta.avatar}
213
+ shape={'square'}
214
+ size={SKILL_ICON_SIZE}
215
+ style={{ flex: 'none' }}
216
+ />
197
217
  ),
198
218
  key: item.identifier,
199
219
  label: (
@@ -236,9 +256,9 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
236
256
  // 生成插件列表项的函数
237
257
  const mapPluginToItem = (item: (typeof list)[0]) => ({
238
258
  icon: item?.avatar ? (
239
- <PluginAvatar avatar={item.avatar} size={20} />
259
+ <PluginAvatar avatar={item.avatar} size={SKILL_ICON_SIZE} />
240
260
  ) : (
241
- <Icon icon={ToyBrick} size={20} />
261
+ <Icon icon={ToyBrick} size={SKILL_ICON_SIZE} />
242
262
  ),
243
263
  key: item.identifier,
244
264
  label: (
@@ -315,7 +335,12 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
315
335
  .filter((item) => checked.includes(item.identifier))
316
336
  .map((item) => ({
317
337
  icon: (
318
- <Avatar avatar={item.meta.avatar} shape={'square'} size={20} style={{ flex: 'none' }} />
338
+ <Avatar
339
+ avatar={item.meta.avatar}
340
+ shape={'square'}
341
+ size={SKILL_ICON_SIZE}
342
+ style={{ flex: 'none' }}
343
+ />
319
344
  ),
320
345
  key: item.identifier,
321
346
  label: (
@@ -371,9 +396,9 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
371
396
  .filter((item) => checked.includes(item.identifier))
372
397
  .map((item) => ({
373
398
  icon: item?.avatar ? (
374
- <PluginAvatar avatar={item.avatar} size={20} />
399
+ <PluginAvatar avatar={item.avatar} size={SKILL_ICON_SIZE} />
375
400
  ) : (
376
- <Icon icon={ToyBrick} size={20} />
401
+ <Icon icon={ToyBrick} size={SKILL_ICON_SIZE} />
377
402
  ),
378
403
  key: item.identifier,
379
404
  label: (
@@ -395,9 +420,9 @@ export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean)
395
420
  .filter((item) => checked.includes(item.identifier))
396
421
  .map((item) => ({
397
422
  icon: item?.avatar ? (
398
- <PluginAvatar avatar={item.avatar} size={20} />
423
+ <PluginAvatar avatar={item.avatar} size={SKILL_ICON_SIZE} />
399
424
  ) : (
400
- <Icon icon={ToyBrick} size={20} />
425
+ <Icon icon={ToyBrick} size={SKILL_ICON_SIZE} />
401
426
  ),
402
427
  key: item.identifier,
403
428
  label: (
@@ -117,8 +117,9 @@ const ActionDropdown = memo<ActionDropdownProps>(
117
117
  return trigger === 'hover';
118
118
  }, [trigger]);
119
119
  const resolvedTriggerProps = useMemo(() => {
120
- if (openOnHover === undefined) return triggerProps;
120
+ if (openOnHover === undefined) return { nativeButton: false, ...triggerProps };
121
121
  return {
122
+ nativeButton: false,
122
123
  ...triggerProps,
123
124
  openOnHover,
124
125
  };
@@ -1,6 +1,7 @@
1
1
  import { Text } from '@lobehub/ui';
2
2
  import dayjs from 'dayjs';
3
3
  import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
4
5
 
5
6
  import { type ChatItemProps } from '../type';
6
7
 
@@ -12,11 +13,14 @@ export interface TitleProps {
12
13
  }
13
14
 
14
15
  const Title = memo<TitleProps>(({ showTitle, time, avatar, titleAddon }) => {
16
+ const { t } = useTranslation('chat');
17
+ const title = avatar.title || t('untitledAgent');
18
+
15
19
  return (
16
20
  <>
17
- {showTitle && avatar.title && (
21
+ {showTitle && (
18
22
  <Text fontSize={14} weight={500}>
19
- {avatar.title}
23
+ {title}
20
24
  </Text>
21
25
  )}
22
26
  {showTitle ? titleAddon : undefined}
@@ -1,14 +1,18 @@
1
1
  import { LobeSelect, type LobeSelectProps, TooltipGroup } from '@lobehub/ui';
2
- import { createStaticStyles } from 'antd-style';
2
+ import { createStyles } from 'antd-style';
3
3
  import { type ReactNode, memo, useMemo } from 'react';
4
4
 
5
5
  import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
6
6
  import { useEnabledChatModels } from '@/hooks/useEnabledChatModels';
7
7
  import { type EnabledProviderWithModels } from '@/types/aiProvider';
8
8
 
9
- const styles = createStaticStyles(({ css }) => ({
9
+ const useStyles = createStyles(({ css }, { popupWidth }: { popupWidth?: number | string }) => ({
10
10
  popup: css`
11
- width: max(360px, var(--anchor-width));
11
+ width: ${popupWidth
12
+ ? typeof popupWidth === 'number'
13
+ ? `${popupWidth}px`
14
+ : popupWidth
15
+ : 'max(360px, var(--anchor-width))'};
12
16
  `,
13
17
  }));
14
18
 
@@ -27,6 +31,7 @@ interface ModelSelectProps extends Pick<LobeSelectProps, 'loading' | 'size' | 's
27
31
  defaultValue?: { model: string; provider?: string };
28
32
  initialWidth?: boolean;
29
33
  onChange?: (props: { model: string; provider: string }) => void;
34
+ popupWidth?: number | string;
30
35
  requiredAbilities?: (keyof EnabledProviderWithModels['children'][number]['abilities'])[];
31
36
  showAbility?: boolean;
32
37
 
@@ -41,10 +46,12 @@ const ModelSelect = memo<ModelSelectProps>(
41
46
  showAbility = true,
42
47
  requiredAbilities,
43
48
  loading,
49
+ popupWidth,
44
50
  size,
45
51
  style,
46
52
  variant,
47
53
  }) => {
54
+ const { styles } = useStyles({ popupWidth });
48
55
  const enabledList = useEnabledChatModels();
49
56
 
50
57
  const options = useMemo<LobeSelectProps['options']>(() => {
@@ -7,7 +7,7 @@ import {
7
7
  type LobehubSkillProviderType,
8
8
  } from '@lobechat/const';
9
9
  import { Avatar, Button, Flexbox, Icon, type ItemType } from '@lobehub/ui';
10
- import { createStaticStyles, cssVar } from 'antd-style';
10
+ import { cssVar } from 'antd-style';
11
11
  import isEqual from 'fast-deep-equal';
12
12
  import { PlusIcon, ToyBrick } from 'lucide-react';
13
13
  import React, { Suspense, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -40,30 +40,25 @@ const WEB_BROWSING_IDENTIFIER = 'lobe-web-browsing';
40
40
 
41
41
  type TabType = 'all' | 'installed';
42
42
 
43
- const styles = createStaticStyles(({ css }) => ({
44
- icon: css`
45
- flex: none;
46
- width: 18px;
47
- height: 18px;
48
- margin-inline-end: ${cssVar.marginXS};
49
- `,
50
- scroller: css`
51
- overflow: hidden auto;
52
- `,
53
- }));
43
+ const SKILL_ICON_SIZE = 20;
54
44
 
55
45
  /**
56
46
  * Klavis 服务器图标组件
57
- * 对于 string 类型的 icon,使用 Image 组件渲染
58
- * 对于 IconType 类型的 icon,使用 Icon 组件渲染,并根据主题设置填充色
59
47
  */
60
48
  const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label }) => {
61
49
  if (typeof icon === 'string') {
62
- return <img alt={label} className={styles.icon} height={18} src={icon} width={18} />;
50
+ return (
51
+ <Avatar
52
+ alt={label}
53
+ avatar={icon}
54
+ shape={'square'}
55
+ size={SKILL_ICON_SIZE}
56
+ style={{ flex: 'none' }}
57
+ />
58
+ );
63
59
  }
64
60
 
65
- // 使用主题色填充,在深色模式下自动适应
66
- return <Icon className={styles.icon} fill={cssVar.colorText} icon={icon} size={18} />;
61
+ return <Icon fill={cssVar.colorText} icon={icon} size={SKILL_ICON_SIZE} />;
67
62
  });
68
63
 
69
64
  /**
@@ -72,10 +67,18 @@ const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label
72
67
  const LobehubSkillIcon = memo<Pick<LobehubSkillProviderType, 'icon' | 'label'>>(
73
68
  ({ icon, label }) => {
74
69
  if (typeof icon === 'string') {
75
- return <img alt={label} className={styles.icon} height={18} src={icon} width={18} />;
70
+ return (
71
+ <Avatar
72
+ alt={label}
73
+ avatar={icon}
74
+ shape={'square'}
75
+ size={SKILL_ICON_SIZE}
76
+ style={{ flex: 'none' }}
77
+ />
78
+ );
76
79
  }
77
80
 
78
- return <Icon className={styles.icon} fill={cssVar.colorText} icon={icon} size={18} />;
81
+ return <Icon fill={cssVar.colorText} icon={icon} size={SKILL_ICON_SIZE} />;
79
82
  },
80
83
  );
81
84
 
@@ -315,7 +318,12 @@ const AgentTool = memo<AgentToolProps>(
315
318
  // 原有的 builtin 工具
316
319
  ...filteredBuiltinList.map((item) => ({
317
320
  icon: (
318
- <Avatar avatar={item.meta.avatar} size={20} style={{ flex: 'none', marginRight: 0 }} />
321
+ <Avatar
322
+ avatar={item.meta.avatar}
323
+ shape={'square'}
324
+ size={SKILL_ICON_SIZE}
325
+ style={{ flex: 'none' }}
326
+ />
319
327
  ),
320
328
  key: item.identifier,
321
329
  label: (
@@ -347,9 +355,9 @@ const AgentTool = memo<AgentToolProps>(
347
355
  const mapPluginToItem = useCallback(
348
356
  (item: (typeof installedPluginList)[0]) => ({
349
357
  icon: item?.avatar ? (
350
- <PluginAvatar avatar={item.avatar} size={20} />
358
+ <PluginAvatar avatar={item.avatar} size={SKILL_ICON_SIZE} />
351
359
  ) : (
352
- <Icon icon={ToyBrick} size={20} />
360
+ <Icon icon={ToyBrick} size={SKILL_ICON_SIZE} />
353
361
  ),
354
362
  key: item.identifier,
355
363
  label: (
@@ -428,7 +436,14 @@ const AgentTool = memo<AgentToolProps>(
428
436
  const enabledBuiltinItems = filteredBuiltinList
429
437
  .filter((item) => isToolEnabled(item.identifier))
430
438
  .map((item) => ({
431
- icon: <Avatar avatar={item.meta.avatar} size={20} style={{ flex: 'none' }} />,
439
+ icon: (
440
+ <Avatar
441
+ avatar={item.meta.avatar}
442
+ shape={'square'}
443
+ size={SKILL_ICON_SIZE}
444
+ style={{ flex: 'none' }}
445
+ />
446
+ ),
432
447
  key: item.identifier,
433
448
  label: (
434
449
  <ToolItem
@@ -475,9 +490,9 @@ const AgentTool = memo<AgentToolProps>(
475
490
  .filter((item) => plugins.includes(item.identifier))
476
491
  .map((item) => ({
477
492
  icon: item?.avatar ? (
478
- <PluginAvatar avatar={item.avatar} size={20} />
493
+ <PluginAvatar avatar={item.avatar} size={SKILL_ICON_SIZE} />
479
494
  ) : (
480
- <Icon icon={ToyBrick} size={20} />
495
+ <Icon icon={ToyBrick} size={SKILL_ICON_SIZE} />
481
496
  ),
482
497
  key: item.identifier,
483
498
  label: (
@@ -508,9 +523,9 @@ const AgentTool = memo<AgentToolProps>(
508
523
  .filter((item) => plugins.includes(item.identifier))
509
524
  .map((item) => ({
510
525
  icon: item?.avatar ? (
511
- <PluginAvatar avatar={item.avatar} size={20} />
526
+ <PluginAvatar avatar={item.avatar} size={SKILL_ICON_SIZE} />
512
527
  ) : (
513
- <Icon icon={ToyBrick} size={20} />
528
+ <Icon icon={ToyBrick} size={SKILL_ICON_SIZE} />
514
529
  ),
515
530
  key: item.identifier,
516
531
  label: (
@@ -552,7 +567,6 @@ const AgentTool = memo<AgentToolProps>(
552
567
 
553
568
  // Use effective tab for display (default to all while initializing)
554
569
  const effectiveTab = activeTab ?? 'all';
555
- const currentItems = effectiveTab === 'all' ? allTabItems : installedTabItems;
556
570
 
557
571
  const button = (
558
572
  <Button
@@ -581,11 +595,11 @@ const AgentTool = memo<AgentToolProps>(
581
595
  {/* Plugin Selector and Tags */}
582
596
  <Flexbox align="center" gap={8} horizontal wrap={'wrap'}>
583
597
  <Suspense fallback={button}>
584
- {/* Plugin Selector Dropdown - Using Action component pattern */}
598
+ {/* Plugin Selector Dropdown - Using Action component pattern */}
585
599
  <ActionDropdown
586
600
  maxWidth={400}
587
601
  menu={{
588
- items: currentItems,
602
+ items: [],
589
603
  style: {
590
604
  // let only the custom scroller scroll
591
605
  maxHeight: 'unset',
@@ -596,15 +610,20 @@ const AgentTool = memo<AgentToolProps>(
596
610
  onOpenChange={setDropdownOpen}
597
611
  open={dropdownOpen}
598
612
  placement={'bottomLeft'}
599
- popupRender={(menu) => (
613
+ popupProps={{
614
+ style: {
615
+ padding: 0,
616
+ },
617
+ }}
618
+ popupRender={() => (
600
619
  <PopoverContent
601
620
  activeTab={effectiveTab}
621
+ allTabItems={allTabItems}
602
622
  installedTabItems={installedTabItems}
603
- menu={menu}
604
623
  onClose={() => setDropdownOpen(false)}
605
624
  onOpenStore={() => {
606
625
  setDropdownOpen(false);
607
- createSkillStoreModal()
626
+ createSkillStoreModal();
608
627
  }}
609
628
  onTabChange={setActiveTab}
610
629
  />
@@ -1,56 +1,22 @@
1
1
  import { Flexbox, Icon, type ItemType, Segmented } from '@lobehub/ui';
2
2
  import { createStaticStyles, cssVar } from 'antd-style';
3
- import { ArrowRight, ExternalLink, Settings, Store } from 'lucide-react';
4
- import { type ReactNode, memo } from 'react';
3
+ import { ChevronRight, ExternalLink, Settings, Store } from 'lucide-react';
4
+ import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { useNavigate } from 'react-router-dom';
7
7
 
8
+ import ToolsList, { toolsListStyles } from '@/features/ChatInput/ActionBar/Tools/ToolsList';
9
+
8
10
  import Empty from './Empty';
9
11
 
10
12
  type TabType = 'all' | 'installed';
11
13
 
12
- const prefixCls = 'ant';
14
+ const SKILL_ICON_SIZE = 20;
13
15
 
14
16
  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;
17
+ footer: css`
18
+ padding: 4px;
19
+ border-block-start: 1px solid ${cssVar.colorBorderSecondary};
54
20
  `,
55
21
  header: css`
56
22
  padding: ${cssVar.paddingXS};
@@ -67,20 +33,22 @@ const styles = createStaticStyles(({ css }) => ({
67
33
 
68
34
  interface PopoverContentProps {
69
35
  activeTab: TabType;
36
+ allTabItems: ItemType[];
70
37
  installedTabItems: ItemType[];
71
- menu: ReactNode;
72
38
  onClose?: () => void;
73
39
  onOpenStore: () => void;
74
40
  onTabChange: (tab: TabType) => void;
75
41
  }
76
42
 
77
43
  const PopoverContent = memo<PopoverContentProps>(
78
- ({ menu, activeTab, onTabChange, installedTabItems, onOpenStore, onClose }) => {
44
+ ({ activeTab, onTabChange, allTabItems, installedTabItems, onOpenStore, onClose }) => {
79
45
  const { t } = useTranslation('setting');
80
46
  const navigate = useNavigate();
81
47
 
48
+ const currentItems = activeTab === 'all' ? allTabItems : installedTabItems;
49
+
82
50
  return (
83
- <Flexbox className={styles.dropdown} style={{ maxHeight: 500 }}>
51
+ <Flexbox style={{ maxHeight: 500, width: '100%' }}>
84
52
  {/* stopPropagation prevents dropdown's onClick from calling preventDefault on Segmented */}
85
53
  <div className={styles.header} onClick={(e) => e.stopPropagation()}>
86
54
  <Segmented
@@ -101,23 +69,22 @@ const PopoverContent = memo<PopoverContentProps>(
101
69
  />
102
70
  </div>
103
71
  <div className={styles.scroller} style={{ flex: 1 }}>
104
- {activeTab === 'installed' && installedTabItems.length === 0 ? <Empty /> : menu}
72
+ {activeTab === 'installed' && installedTabItems.length === 0 ? (
73
+ <Empty />
74
+ ) : (
75
+ <ToolsList items={currentItems} />
76
+ )}
105
77
  </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} />
78
+ <div className={styles.footer}>
79
+ <div className={toolsListStyles.item} onClick={onOpenStore} role="button" tabIndex={0}>
80
+ <div className={toolsListStyles.itemIcon}>
81
+ <Icon icon={Store} size={SKILL_ICON_SIZE} />
115
82
  </div>
116
- <div className={styles.footerItemContent}>{t('skillStore.title')}</div>
117
- <Icon className={styles.trailingIcon} icon={ArrowRight} size={16} />
83
+ <div className={toolsListStyles.itemContent}>{t('skillStore.title')}</div>
84
+ <Icon className={styles.trailingIcon} icon={ChevronRight} size={16} />
118
85
  </div>
119
86
  <div
120
- className={styles.footerItem}
87
+ className={toolsListStyles.item}
121
88
  onClick={() => {
122
89
  onClose?.();
123
90
  navigate('/settings/skill');
@@ -125,10 +92,10 @@ const PopoverContent = memo<PopoverContentProps>(
125
92
  role="button"
126
93
  tabIndex={0}
127
94
  >
128
- <div className={styles.footerItemIcon}>
129
- <Icon icon={Settings} size={20} />
95
+ <div className={toolsListStyles.itemIcon}>
96
+ <Icon icon={Settings} size={SKILL_ICON_SIZE} />
130
97
  </div>
131
- <div className={styles.footerItemContent}>{t('tools.plugins.management')}</div>
98
+ <div className={toolsListStyles.itemContent}>{t('tools.plugins.management')}</div>
132
99
  <Icon className={styles.trailingIcon} icon={ExternalLink} size={16} />
133
100
  </div>
134
101
  </div>
@@ -3,13 +3,14 @@
3
3
  import {
4
4
  Button,
5
5
  Flexbox,
6
+ LobeSelect,
6
7
  Popover,
7
8
  Skeleton,
8
9
  Text,
9
10
  copyToClipboard,
10
11
  usePopoverContext,
11
12
  } from '@lobehub/ui';
12
- import { App, Divider, Select } from 'antd';
13
+ import { App, Divider } from 'antd';
13
14
  import { ExternalLinkIcon, LinkIcon, LockIcon } from 'lucide-react';
14
15
  import { type ReactNode, memo, useCallback, useEffect, useRef, useState } from 'react';
15
16
  import { useTranslation } from 'react-i18next';
@@ -146,9 +147,8 @@ const SharePopoverContent = memo<SharePopoverContentProps>(({ onOpenModal }) =>
146
147
 
147
148
  <Flexbox gap={4}>
148
149
  <Text type="secondary">{t('shareModal.popover.visibility')}</Text>
149
- <Select
150
+ <LobeSelect
150
151
  disabled={updating}
151
- getPopupContainer={() => containerRef.current || document.body}
152
152
  labelRender={({ value }) => {
153
153
  const option = visibilityOptions.find((o) => o.value === value);
154
154
  return (
@@ -9,8 +9,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
- import MCPInstallProgress from '@/features/MCP/MCPInstallProgress';
13
12
  import McpDetailLoading from '@/features/MCP/MCPDetail/Loading';
13
+ import MCPInstallProgress from '@/features/MCP/MCPInstallProgress';
14
14
  import { useMarketAuth } from '@/layout/AuthProvider/MarketAuth';
15
15
  import { useAgentStore } from '@/store/agent';
16
16
  import { agentSelectors } from '@/store/agent/selectors';
@@ -95,6 +95,7 @@ const Item = memo<DiscoverMcpItem>(({ name, description, icon, identifier }) =>
95
95
  },
96
96
  },
97
97
  ]}
98
+ nativeButton={false}
98
99
  placement="bottomRight"
99
100
  >
100
101
  <ActionIcon icon={MoreVerticalIcon} />