@lobehub/chat 0.108.0 → 0.109.0

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 (93) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/docs/Environment-Variable.md +2 -2
  5. package/docs/Environment-Variable.zh-CN.md +2 -2
  6. package/locales/en_US/plugin.json +19 -0
  7. package/locales/en_US/setting.json +26 -0
  8. package/locales/ja_JP/plugin.json +19 -0
  9. package/locales/ja_JP/setting.json +26 -0
  10. package/locales/ko_KR/plugin.json +19 -0
  11. package/locales/ko_KR/setting.json +26 -0
  12. package/locales/ru_RU/plugin.json +19 -0
  13. package/locales/ru_RU/setting.json +26 -0
  14. package/locales/zh_CN/plugin.json +19 -0
  15. package/locales/zh_CN/setting.json +26 -5
  16. package/locales/zh_TW/plugin.json +19 -0
  17. package/locales/zh_TW/setting.json +26 -0
  18. package/package.json +3 -3
  19. package/src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx +6 -6
  20. package/src/app/chat/features/ChatHeader/PluginTag/index.tsx +4 -4
  21. package/src/app/chat/features/ChatInput/ActionBar/Tools/ToolItem.tsx +37 -0
  22. package/src/app/chat/features/ChatInput/ActionBar/Tools/index.tsx +116 -0
  23. package/src/app/chat/features/ChatInput/ActionBar/config.ts +3 -1
  24. package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +4 -4
  25. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +3 -3
  26. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +4 -4
  27. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +2 -2
  28. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +4 -4
  29. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/index.tsx +2 -2
  30. package/src/app/chat/features/Conversation/useInitConversation.ts +2 -2
  31. package/src/app/settings/common/Common.tsx +2 -2
  32. package/src/features/AgentSetting/AgentPlugin/AddPluginButton.tsx +56 -0
  33. package/src/features/AgentSetting/AgentPlugin/LoadingList.tsx +48 -0
  34. package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +5 -5
  35. package/src/features/AgentSetting/AgentPlugin/PluginAction/index.tsx +36 -0
  36. package/src/features/AgentSetting/AgentPlugin/index.tsx +157 -9
  37. package/src/features/AgentSetting/store/action.ts +0 -1
  38. package/src/features/PluginDevModal/MetaForm.tsx +3 -3
  39. package/src/features/PluginDevModal/PluginPreview.tsx +1 -1
  40. package/src/features/PluginDevModal/index.tsx +4 -1
  41. package/src/features/PluginSettings/index.tsx +3 -3
  42. package/src/features/PluginSettingsModal/index.tsx +22 -17
  43. package/src/features/PluginStore/Loading.tsx +13 -0
  44. package/src/features/PluginStore/PluginItem.tsx +81 -0
  45. package/src/features/PluginStore/PluginSettings.tsx +40 -0
  46. package/src/features/{AgentSetting/AgentPlugin/MarketSettingModal.tsx → PluginStore/StoreSetting.tsx} +2 -2
  47. package/src/features/PluginStore/index.tsx +95 -0
  48. package/src/layout/GlobalLayout/StoreHydration.tsx +3 -3
  49. package/src/locales/default/plugin.ts +19 -0
  50. package/src/locales/default/setting.ts +26 -5
  51. package/src/services/__tests__/chat.test.ts +221 -0
  52. package/src/services/__tests__/message.test.ts +333 -0
  53. package/src/services/__tests__/plugin.test.ts +88 -0
  54. package/src/services/__tests__/session.test.ts +227 -0
  55. package/src/services/__tests__/topic.test.ts +220 -0
  56. package/src/services/chat.ts +4 -6
  57. package/src/services/plugin.ts +12 -1
  58. package/src/store/chat/actions/message.test.ts +0 -1
  59. package/src/store/session/slices/agent/action.ts +24 -8
  60. package/src/store/{plugin → tool}/helpers.ts +2 -13
  61. package/src/store/tool/index.ts +2 -0
  62. package/src/store/tool/initialState.ts +11 -0
  63. package/src/store/tool/selectors.ts +3 -0
  64. package/src/store/tool/slices/customPlugin/action.test.ts +164 -0
  65. package/src/store/{plugin → tool}/slices/customPlugin/action.ts +2 -2
  66. package/src/store/tool/slices/customPlugin/selectors.test.ts +59 -0
  67. package/src/store/tool/slices/customPlugin/selectors.ts +9 -0
  68. package/src/store/tool/slices/plugin/action.test.ts +141 -0
  69. package/src/store/tool/slices/plugin/action.ts +95 -0
  70. package/src/store/{plugin → tool}/slices/plugin/initialState.ts +0 -7
  71. package/src/store/{plugin → tool/slices/plugin}/selectors.test.ts +4 -41
  72. package/src/store/{plugin → tool/slices/plugin}/selectors.ts +17 -19
  73. package/src/store/tool/slices/store/action.test.ts +396 -0
  74. package/src/store/tool/slices/store/action.ts +103 -0
  75. package/src/store/tool/slices/store/index.ts +3 -0
  76. package/src/store/tool/slices/store/initialState.ts +17 -0
  77. package/src/store/tool/slices/store/selectors.test.ts +48 -0
  78. package/src/store/tool/slices/store/selectors.ts +21 -0
  79. package/src/store/{plugin → tool}/store.ts +8 -6
  80. package/vitest.config.ts +1 -0
  81. package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +0 -232
  82. package/src/features/AgentSetting/AgentPlugin/PluginSettings.tsx +0 -80
  83. package/src/store/chat/reducers/files.test.ts +0 -38
  84. package/src/store/chat/reducers/files.ts +0 -37
  85. package/src/store/plugin/hooks/useSessionHydrated.ts +0 -21
  86. package/src/store/plugin/index.ts +0 -2
  87. package/src/store/plugin/initialState.ts +0 -9
  88. package/src/store/plugin/slices/plugin/action.ts +0 -173
  89. /package/src/store/{plugin → tool}/slices/customPlugin/index.ts +0 -0
  90. /package/src/store/{plugin → tool}/slices/customPlugin/initialState.ts +0 -0
  91. /package/src/store/{plugin → tool}/slices/customPlugin/reducers/customPluginList.ts +0 -0
  92. /package/src/store/{plugin → tool}/slices/plugin/index.ts +0 -0
  93. /package/src/store/{plugin → tool}/slices/plugin/reducers/manifest.ts +0 -0
@@ -0,0 +1,116 @@
1
+ import { ActionIcon, Avatar, Icon } from '@lobehub/ui';
2
+ import { Dropdown } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import isEqual from 'fast-deep-equal';
5
+ import { ArrowRight, PocketKnife, Store, ToyBrick } from 'lucide-react';
6
+ import { memo, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import PluginStore from '@/features/PluginStore';
11
+ import { useSessionStore } from '@/store/session';
12
+ import { agentSelectors } from '@/store/session/selectors';
13
+ import { useToolStore } from '@/store/tool';
14
+ import { pluginSelectors } from '@/store/tool/selectors';
15
+
16
+ import ToolItem from './ToolItem';
17
+
18
+ const useStyles = createStyles(({ css, prefixCls }) => ({
19
+ menu: css`
20
+ &.${prefixCls}-dropdown-menu {
21
+ padding-block: 8px;
22
+ }
23
+
24
+ .${prefixCls}-dropdown-menu-item-group-list .${prefixCls}-dropdown-menu-item {
25
+ padding: 0;
26
+ border-radius: 4px;
27
+ }
28
+ `,
29
+ }));
30
+
31
+ const Tools = memo(() => {
32
+ const { t } = useTranslation('setting');
33
+ const list = useToolStore(pluginSelectors.installedPlugins, isEqual);
34
+ const enablePluginCount = useSessionStore((s) => agentSelectors.currentAgentPlugins(s).length);
35
+ const [open, setOpen] = useState(false);
36
+ const { styles } = useStyles();
37
+
38
+ return (
39
+ <>
40
+ <Dropdown
41
+ arrow={false}
42
+ menu={{
43
+ className: styles.menu,
44
+ items: [
45
+ // {
46
+ // children: [
47
+ // {
48
+ // icon: <Icon icon={PaletteIcon} size={{ fontSize: 16 }} style={{ padding: 4 }} />,
49
+ // key: 'dalle3',
50
+ // label: <ToolItem identifier={'dalle3'} label={'DALL·E 3'} />,
51
+ // },
52
+ // ],
53
+ // key: 'builtins',
54
+ // label: t('tools.builtins.groupName'),
55
+ // type: 'group',
56
+ // },
57
+ {
58
+ children: [
59
+ ...list.map((item) => ({
60
+ icon: item.meta?.avatar ? (
61
+ <Avatar avatar={item.meta?.avatar} size={24} />
62
+ ) : (
63
+ <Icon icon={ToyBrick} size={{ fontSize: 16 }} style={{ padding: 4 }} />
64
+ ),
65
+ key: item.identifier,
66
+ label: (
67
+ <ToolItem
68
+ identifier={item.identifier}
69
+ label={item.meta?.title || item.identifier}
70
+ />
71
+ ),
72
+ })),
73
+ {
74
+ icon: <Icon icon={Store} size={{ fontSize: 16 }} style={{ padding: 4 }} />,
75
+
76
+ key: 'plugin-store',
77
+ label: (
78
+ <Flexbox gap={40} horizontal justify={'space-between'} padding={'8px 12px'}>
79
+ {t('tools.plugins.store')} <Icon icon={ArrowRight} />
80
+ </Flexbox>
81
+ ),
82
+ onClick: (e) => {
83
+ e.domEvent.stopPropagation();
84
+ setOpen(true);
85
+ },
86
+ },
87
+ ],
88
+ key: 'plugins',
89
+ label: (
90
+ <Flexbox align={'center'} gap={40} horizontal justify={'space-between'}>
91
+ {t('tools.plugins.groupName')}
92
+ {enablePluginCount === 0 ? null : (
93
+ <div style={{ fontSize: 12, marginInlineEnd: 4 }}>
94
+ {t('tools.plugins.enabled', { num: enablePluginCount })}
95
+ </div>
96
+ )}
97
+ </Flexbox>
98
+ ),
99
+ type: 'group',
100
+ },
101
+ ],
102
+ onClick: (e) => {
103
+ e.domEvent.preventDefault();
104
+ },
105
+ }}
106
+ placement={'topLeft'}
107
+ trigger={['click']}
108
+ >
109
+ <ActionIcon icon={PocketKnife} placement={'bottom'} title={t('tools.title')} />
110
+ </Dropdown>
111
+ <PluginStore open={open} setOpen={setOpen} />
112
+ </>
113
+ );
114
+ });
115
+
116
+ export default Tools;
@@ -5,6 +5,7 @@ import History from './History';
5
5
  import ModelSwitch from './ModelSwitch';
6
6
  import Temperature from './Temperature';
7
7
  import Token from './Token';
8
+ import Tools from './Tools';
8
9
 
9
10
  export const actionMap = {
10
11
  clear: Clear,
@@ -14,6 +15,7 @@ export const actionMap = {
14
15
  stt: STT,
15
16
  temperature: Temperature,
16
17
  token: Token,
18
+ tools: Tools,
17
19
  } as const;
18
20
 
19
21
  type ActionMap = typeof actionMap;
@@ -24,7 +26,7 @@ type getActionList = (mobile?: boolean) => ActionKeys[];
24
26
 
25
27
  // we can make these action lists configurable in the future
26
28
  export const getLeftActionList: getActionList = (mobile) =>
27
- ['model', 'fileUpload', 'temperature', 'history', !mobile && 'stt', 'token'].filter(
29
+ ['model', 'fileUpload', 'temperature', 'history', !mobile && 'stt', 'tools', 'token'].filter(
28
30
  Boolean,
29
31
  ) as ActionKeys[];
30
32
 
@@ -8,8 +8,8 @@ import { Center, Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import PluginSettingsConfig from '@/features/PluginSettings';
10
10
  import { useChatStore } from '@/store/chat';
11
- import { pluginHelpers, usePluginStore } from '@/store/plugin';
12
- import { pluginSelectors } from '@/store/plugin/selectors';
11
+ import { pluginHelpers, useToolStore } from '@/store/tool';
12
+ import { pluginSelectors } from '@/store/tool/selectors';
13
13
 
14
14
  import { ErrorActionContainer, useStyles } from '../style';
15
15
 
@@ -19,8 +19,8 @@ const PluginSettings: RenderErrorMessage['Render'] = memo(({ id, plugin }) => {
19
19
  const theme = useTheme();
20
20
  const [resend, deleteMessage] = useChatStore((s) => [s.resendMessage, s.deleteMessage]);
21
21
  const pluginIdentifier = plugin?.identifier as string;
22
- const pluginMeta = usePluginStore(pluginSelectors.getPluginMetaById(pluginIdentifier), isEqual);
23
- const manifest = usePluginStore(pluginSelectors.getPluginManifestById(pluginIdentifier), isEqual);
22
+ const pluginMeta = useToolStore(pluginSelectors.getPluginMetaById(pluginIdentifier), isEqual);
23
+ const manifest = useToolStore(pluginSelectors.getPluginManifestById(pluginIdentifier), isEqual);
24
24
 
25
25
  return (
26
26
  <ErrorActionContainer>
@@ -4,11 +4,11 @@ import { memo, useState } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import PluginSettingsModal from '@/features/PluginSettingsModal';
7
- import { usePluginStore } from '@/store/plugin';
8
- import { pluginSelectors } from '@/store/plugin/selectors';
7
+ import { useToolStore } from '@/store/tool';
8
+ import { pluginSelectors } from '@/store/tool/selectors';
9
9
 
10
10
  const Settings = memo<{ id: string }>(({ id }) => {
11
- const item = usePluginStore(pluginSelectors.getPluginManifestById(id));
11
+ const item = useToolStore(pluginSelectors.getPluginManifestById(id));
12
12
  const [open, setOpen] = useState(false);
13
13
  const { t } = useTranslation('plugin');
14
14
  return (
@@ -14,8 +14,8 @@ import { memo, useState } from 'react';
14
14
  import { useTranslation } from 'react-i18next';
15
15
  import { Flexbox } from 'react-layout-kit';
16
16
 
17
- import { pluginHelpers, usePluginStore } from '@/store/plugin';
18
- import { pluginSelectors } from '@/store/plugin/selectors';
17
+ import { pluginHelpers, useToolStore } from '@/store/tool';
18
+ import { pluginSelectors } from '@/store/tool/selectors';
19
19
 
20
20
  import PluginResult from './PluginResultJSON';
21
21
  import Settings from './Settings';
@@ -47,8 +47,8 @@ const Inspector = memo<InspectorProps>(
47
47
  const { styles } = useStyles();
48
48
  const [open, setOpen] = useState(false);
49
49
 
50
- const item = usePluginStore(pluginSelectors.getPluginMetaById(id), isEqual);
51
- const showRightAction = usePluginStore(pluginSelectors.hasPluginUI(id));
50
+ const item = useToolStore(pluginSelectors.getPluginMetaById(id), isEqual);
51
+ const showRightAction = useToolStore(pluginSelectors.hasPluginUI(id));
52
52
  const pluginAvatar = pluginHelpers.getPluginAvatar(item?.meta);
53
53
  const pluginTitle = pluginHelpers.getPluginTitle(item?.meta);
54
54
 
@@ -5,7 +5,7 @@ import { Suspense, memo, useMemo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
 
8
- import { usePluginStore } from '@/store/plugin';
8
+ import { useToolStore } from '@/store/tool';
9
9
 
10
10
  import IFrameRender from './IFrameRender';
11
11
 
@@ -72,7 +72,7 @@ export interface PluginDefaultTypeProps {
72
72
  }
73
73
 
74
74
  const PluginDefaultType = memo<PluginDefaultTypeProps>(({ content, name }) => {
75
- const manifest = usePluginStore((s) => s.pluginManifestMap[name || '']);
75
+ const manifest = useToolStore((s) => s.pluginManifestMap[name || '']);
76
76
  let isJSON = true;
77
77
  try {
78
78
  JSON.parse(content);
@@ -4,8 +4,8 @@ import { memo, useRef, useState } from 'react';
4
4
 
5
5
  import { useChatStore } from '@/store/chat';
6
6
  import { chatSelectors } from '@/store/chat/selectors';
7
- import { usePluginStore } from '@/store/plugin';
8
- import { pluginSelectors } from '@/store/plugin/selectors';
7
+ import { useToolStore } from '@/store/tool';
8
+ import { pluginSelectors } from '@/store/tool/selectors';
9
9
 
10
10
  import { useOnPluginReadyForInteraction } from '../utils/iframeOnReady';
11
11
  import {
@@ -25,7 +25,7 @@ import {
25
25
 
26
26
  // just to simplify code a little, don't use this pattern everywhere
27
27
  const getSettings = (identifier: string) =>
28
- pluginSelectors.getPluginSettingsById(identifier)(usePluginStore.getState());
28
+ pluginSelectors.getPluginSettingsById(identifier)(useToolStore.getState());
29
29
  const getMessage = (id: string) => chatSelectors.getMessageById(id)(useChatStore.getState());
30
30
 
31
31
  interface IFrameRenderProps {
@@ -111,7 +111,7 @@ const IFrameRender = memo<IFrameRenderProps>(({ url, id, payload, width = 600, h
111
111
  });
112
112
 
113
113
  // when plugin update settings, we should update it to the plugin settings
114
- const updatePluginSettings = usePluginStore((s) => s.updatePluginSettings);
114
+ const updatePluginSettings = useToolStore((s) => s.updatePluginSettings);
115
115
  useOnPluginSettingsUpdate((value) => {
116
116
  if (!payload?.identifier) return;
117
117
 
@@ -1,7 +1,7 @@
1
1
  import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2
2
  import { memo } from 'react';
3
3
 
4
- import { usePluginStore } from '@/store/plugin';
4
+ import { useToolStore } from '@/store/tool';
5
5
 
6
6
  import IFrameRender from './Iframe';
7
7
 
@@ -12,7 +12,7 @@ export interface PluginStandaloneTypeProps {
12
12
  }
13
13
 
14
14
  const PluginDefaultType = memo<PluginStandaloneTypeProps>(({ payload, id, name = 'unknown' }) => {
15
- const manifest = usePluginStore((s) => s.pluginManifestMap[name]);
15
+ const manifest = useToolStore((s) => s.pluginManifestMap[name]);
16
16
 
17
17
  if (!manifest?.ui) return;
18
18
 
@@ -1,9 +1,9 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
3
  import { useChatStore } from '@/store/chat';
4
- import { usePluginStore } from '@/store/plugin';
5
4
  import { useSessionStore } from '@/store/session';
6
5
  import { agentSelectors } from '@/store/session/selectors';
6
+ import { useToolStore } from '@/store/tool';
7
7
 
8
8
  export const useInitConversation = () => {
9
9
  const [sessionId] = useSessionStore((s) => [s.activeId]);
@@ -18,7 +18,7 @@ export const useInitConversation = () => {
18
18
  useFetchMessages(sessionId, activeTopicId);
19
19
  useFetchTopics(sessionId);
20
20
 
21
- const [useFetchPluginStore, checkPluginsIsInstalled] = usePluginStore((s) => [
21
+ const [useFetchPluginStore, checkPluginsIsInstalled] = useToolStore((s) => [
22
22
  s.useFetchPluginStore,
23
23
  s.useCheckPluginsIsInstalled,
24
24
  ]);
@@ -13,8 +13,8 @@ import { localeOptions } from '@/locales/options';
13
13
  import { useChatStore } from '@/store/chat';
14
14
  import { useFileStore } from '@/store/file';
15
15
  import { settingsSelectors, useGlobalStore } from '@/store/global';
16
- import { usePluginStore } from '@/store/plugin';
17
16
  import { useSessionStore } from '@/store/session';
17
+ import { useToolStore } from '@/store/tool';
18
18
  import { switchLang } from '@/utils/switchLang';
19
19
 
20
20
  import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from '../features/ThemeSwatches';
@@ -35,7 +35,7 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
35
35
  s.clearAllMessages,
36
36
  ]);
37
37
  const [removeAllFiles] = useFileStore((s) => [s.removeAllFiles]);
38
- const resetPluginSettings = usePluginStore((s) => s.resetPluginSettings);
38
+ const resetPluginSettings = useToolStore((s) => s.resetPluginSettings);
39
39
 
40
40
  const settings = useGlobalStore(settingsSelectors.currentSettings, isEqual);
41
41
  const [setThemeMode, setSettings, resetSettings] = useGlobalStore((s) => [
@@ -0,0 +1,56 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { LucideBlocks } from 'lucide-react';
4
+ import { forwardRef, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { useStore } from '@/features/AgentSetting/store';
8
+ import DevModal from '@/features/PluginDevModal';
9
+ import { useToolStore } from '@/store/tool';
10
+
11
+ const AddPluginButton = forwardRef<HTMLButtonElement>((props, ref) => {
12
+ const { t } = useTranslation('setting');
13
+ const [showModal, setModal] = useState(false);
14
+ const [toggleAgentPlugin] = useStore((s) => [s.toggleAgentPlugin]);
15
+ const [installPlugin, saveToDevList, updateNewDevPlugin] = useToolStore((s) => [
16
+ s.installPlugin,
17
+ s.saveToCustomPluginList,
18
+ s.updateNewCustomPlugin,
19
+ ]);
20
+
21
+ const togglePlugin = async (pluginId: string, fetchManifest?: boolean) => {
22
+ toggleAgentPlugin(pluginId);
23
+ if (fetchManifest) {
24
+ await installPlugin(pluginId);
25
+ }
26
+ };
27
+
28
+ return (
29
+ <>
30
+ <DevModal
31
+ onOpenChange={setModal}
32
+ onSave={async (devPlugin) => {
33
+ // 先保存
34
+ saveToDevList(devPlugin);
35
+ // 再开启插件
36
+ await togglePlugin(devPlugin.identifier, true);
37
+ }}
38
+ onValueChange={updateNewDevPlugin}
39
+ open={showModal}
40
+ />
41
+ <Button
42
+ icon={<Icon icon={LucideBlocks} />}
43
+ onClick={(e) => {
44
+ e.stopPropagation();
45
+ setModal(true);
46
+ }}
47
+ ref={ref}
48
+ size={'small'}
49
+ >
50
+ {t('plugin.addTooltip')}
51
+ </Button>
52
+ </>
53
+ );
54
+ });
55
+
56
+ export default AddPluginButton;
@@ -0,0 +1,48 @@
1
+ import { Skeleton } from 'antd';
2
+ import { createStyles } from 'antd-style';
3
+
4
+ const useStyles = createStyles(({ css, prefixCls }) => ({
5
+ avatar: css`
6
+ .${prefixCls}-skeleton-header {
7
+ padding-right: 0;
8
+ }
9
+ `,
10
+ label: css`
11
+ li {
12
+ height: 14px !important;
13
+ }
14
+
15
+ li + li {
16
+ margin-block-start: 12px !important;
17
+ }
18
+ `,
19
+ }));
20
+
21
+ const LoadingList = () => {
22
+ const { styles } = useStyles();
23
+
24
+ const loadingItem = {
25
+ avatar: (
26
+ <Skeleton
27
+ active
28
+ avatar={{ size: 40 }}
29
+ className={styles.avatar}
30
+ paragraph={false}
31
+ title={false}
32
+ />
33
+ ),
34
+ label: (
35
+ <Skeleton
36
+ active
37
+ avatar={false}
38
+ paragraph={{ className: styles.label, style: { marginBottom: 0 } }}
39
+ style={{ width: 300 }}
40
+ title={false}
41
+ />
42
+ ),
43
+ };
44
+
45
+ return [loadingItem, loadingItem, loadingItem];
46
+ };
47
+
48
+ export default LoadingList;
@@ -5,8 +5,8 @@ import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
7
7
  import DevModal from '@/features/PluginDevModal';
8
- import { usePluginStore } from '@/store/plugin';
9
- import { pluginSelectors } from '@/store/plugin/selectors';
8
+ import { useToolStore } from '@/store/tool';
9
+ import { pluginSelectors } from '@/store/tool/selectors';
10
10
 
11
11
  import { useStore } from '../store';
12
12
 
@@ -19,15 +19,15 @@ const MarketList = memo<{ id: string }>(({ id }) => {
19
19
  const plugins = useStore((s) => s.config.plugins || []);
20
20
 
21
21
  const [useFetchPluginList, fetchPluginManifest, deleteCustomPlugin, updateCustomPlugin] =
22
- usePluginStore((s) => [
22
+ useToolStore((s) => [
23
23
  s.useFetchPluginStore,
24
24
  s.installPlugin,
25
25
  s.deleteCustomPlugin,
26
26
  s.updateCustomPlugin,
27
27
  ]);
28
28
 
29
- const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
30
- const devPlugin = usePluginStore(pluginSelectors.getDevPluginById(id), isEqual);
29
+ const pluginManifestLoading = useToolStore((s) => s.pluginInstallLoading, isEqual);
30
+ const devPlugin = useToolStore(pluginSelectors.getDevPluginById(id), isEqual);
31
31
 
32
32
  useFetchPluginList();
33
33
 
@@ -0,0 +1,36 @@
1
+ import { Switch } from 'antd';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useToolStore } from '@/store/tool';
7
+
8
+ import { useStore } from '../../store';
9
+
10
+ const PluginSwitch = memo<{ identifier: string }>(({ identifier }) => {
11
+ const pluginManifestLoading = useToolStore((s) => s.pluginInstallLoading, isEqual);
12
+ const [userEnabledPlugins, hasPlugin, toggleAgentPlugin] = useStore((s) => [
13
+ s.config.plugins || [],
14
+ !!s.config.plugins,
15
+ s.toggleAgentPlugin,
16
+ ]);
17
+
18
+ return (
19
+ <Flexbox align={'center'} gap={8} horizontal>
20
+ <Switch
21
+ checked={
22
+ // 如果在加载中,说明激活了
23
+ pluginManifestLoading[identifier] || !hasPlugin
24
+ ? false
25
+ : userEnabledPlugins.includes(identifier)
26
+ }
27
+ loading={pluginManifestLoading[identifier]}
28
+ onChange={() => {
29
+ toggleAgentPlugin(identifier);
30
+ }}
31
+ />
32
+ </Flexbox>
33
+ );
34
+ });
35
+
36
+ export default PluginSwitch;
@@ -1,13 +1,161 @@
1
- import { memo } from 'react';
1
+ import { Avatar, Form, Icon, Tooltip } from '@lobehub/ui';
2
+ import { Button, Empty, Space, Switch, Tag, Typography } from 'antd';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { LucideToyBrick, LucideTrash2, Store } from 'lucide-react';
5
+ import { memo, useState } from 'react';
6
+ import { Trans, useTranslation } from 'react-i18next';
7
+ import { Center, Flexbox } from 'react-layout-kit';
2
8
 
3
- import MarketList from './MarketList';
4
- import PluginSettings from './PluginSettings';
9
+ import { FORM_STYLE } from '@/const/layoutTokens';
10
+ import PluginStore from '@/features/PluginStore';
11
+ import { pluginHelpers, useToolStore } from '@/store/tool';
12
+ import { pluginSelectors } from '@/store/tool/selectors';
5
13
 
6
- const AgentPlugin = memo(() => (
7
- <>
8
- <MarketList />
9
- <PluginSettings />
10
- </>
11
- ));
14
+ import { useStore } from '../store';
15
+ import AddPluginButton from './AddPluginButton';
16
+ import LocalPluginItem from './LocalPluginItem';
17
+ import PluginAction from './PluginAction';
18
+
19
+ const AgentPlugin = memo(() => {
20
+ const { t } = useTranslation('setting');
21
+
22
+ const [showStore, setShowStore] = useState(false);
23
+
24
+ const [userEnabledPlugins, toggleAgentPlugin] = useStore((s) => [
25
+ s.config.plugins || [],
26
+ s.toggleAgentPlugin,
27
+ ]);
28
+
29
+ const installedPlugins = useToolStore(pluginSelectors.installedPlugins, isEqual);
30
+ const customPluginList = useToolStore((s) => s.customPluginList, isEqual);
31
+
32
+ const isEmpty = installedPlugins.length === 0 && userEnabledPlugins.length === 0;
33
+
34
+ // =========== Plugin List =========== //
35
+
36
+ const list = installedPlugins.map(({ identifier, meta }) => ({
37
+ avatar: <Avatar avatar={meta.avatar} />,
38
+ children: <PluginAction identifier={identifier} />,
39
+ desc: pluginHelpers.getPluginDesc(meta),
40
+ label: pluginHelpers.getPluginTitle(meta),
41
+ minWidth: undefined,
42
+ tag: identifier,
43
+ }));
44
+
45
+ // =========== Custom Plugin List =========== //
46
+
47
+ const customList = customPluginList.map(({ identifier, meta }) => ({
48
+ avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta) || '🧩'} />,
49
+ children: <LocalPluginItem id={identifier} />,
50
+ desc: pluginHelpers.getPluginDesc(meta),
51
+ label: (
52
+ <Flexbox align={'center'} gap={8} horizontal>
53
+ {pluginHelpers.getPluginTitle(meta)}
54
+ <Tag bordered={false} color={'gold'}>
55
+ {t('list.item.local.title', { ns: 'plugin' })}
56
+ </Tag>
57
+ </Flexbox>
58
+ ),
59
+ minWidth: undefined,
60
+ tag: identifier,
61
+ }));
62
+
63
+ // =========== Deprecated Plugin List =========== //
64
+
65
+ // 基于 userEnabledPlugins 和完整的 installedPlugins,检查出不在 totalList 中的插件
66
+ const deprecatedList = userEnabledPlugins
67
+ .filter((pluginId) => installedPlugins.findIndex((p) => p.identifier === pluginId) < 0)
68
+ .map((id) => ({
69
+ avatar: <Avatar avatar={'♻️'} />,
70
+ children: (
71
+ <Switch
72
+ checked={true}
73
+ onChange={() => {
74
+ toggleAgentPlugin(id);
75
+ }}
76
+ />
77
+ ),
78
+ label: (
79
+ <Flexbox align={'center'} gap={8} horizontal>
80
+ {id}
81
+ <Tag bordered={false} color={'red'}>
82
+ {t('plugin.installStatus.deprecated')}
83
+ </Tag>
84
+ </Flexbox>
85
+ ),
86
+ minWidth: undefined,
87
+ tag: id,
88
+ }));
89
+
90
+ const hasDeprecated = deprecatedList.length > 0;
91
+
92
+ return (
93
+ <>
94
+ <PluginStore open={showStore} setOpen={setShowStore} />
95
+ <Form
96
+ items={[
97
+ {
98
+ children: isEmpty ? (
99
+ <Center padding={40}>
100
+ <Empty
101
+ description={
102
+ <Trans i18nKey={'plugin.empty'} ns={'setting'}>
103
+ 暂无安装插件,
104
+ <Typography.Link
105
+ href={'/'}
106
+ onClick={(e) => {
107
+ e.preventDefault();
108
+ setShowStore(true);
109
+ }}
110
+ >
111
+ 前往插件市场
112
+ </Typography.Link>
113
+ 安装
114
+ </Trans>
115
+ }
116
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
117
+ />
118
+ </Center>
119
+ ) : (
120
+ [...deprecatedList, ...customList, ...list]
121
+ ),
122
+ extra: (
123
+ <Space.Compact style={{ width: 'auto' }}>
124
+ <AddPluginButton />
125
+ {hasDeprecated ? (
126
+ <Tooltip title={t('plugin.clearDeprecated')}>
127
+ <Button
128
+ icon={<Icon icon={LucideTrash2} />}
129
+ onClick={(e) => {
130
+ e.stopPropagation();
131
+ for (const i of deprecatedList) {
132
+ toggleAgentPlugin(i.tag as string);
133
+ }
134
+ }}
135
+ size={'small'}
136
+ />
137
+ </Tooltip>
138
+ ) : null}
139
+ <Tooltip title={t('plugin.store')}>
140
+ <Button
141
+ icon={<Icon icon={Store} />}
142
+ onClick={(e) => {
143
+ e.stopPropagation();
144
+ setShowStore(true);
145
+ }}
146
+ size={'small'}
147
+ />
148
+ </Tooltip>
149
+ </Space.Compact>
150
+ ),
151
+ icon: LucideToyBrick,
152
+ title: t('settingPlugin.title'),
153
+ },
154
+ ]}
155
+ {...FORM_STYLE}
156
+ />
157
+ </>
158
+ );
159
+ });
12
160
 
13
161
  export default AgentPlugin;
@@ -232,7 +232,6 @@ export const store: StateCreator<Store, [['zustand/devtools', never]]> = (set, g
232
232
  get().dispatchConfig({ config, type: 'update' });
233
233
  },
234
234
  setAgentMeta: (meta) => {
235
- console.log(meta);
236
235
  get().dispatchMeta({ type: 'update', value: meta });
237
236
  },
238
237