@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
@@ -1,232 +0,0 @@
1
- import { Avatar, Form, Icon, Tooltip } from '@lobehub/ui';
2
- import { Button, Skeleton, Space, Switch, Tag } from 'antd';
3
- import { createStyles } from 'antd-style';
4
- import isEqual from 'fast-deep-equal';
5
- import { LucideBlocks, LucideSettings, LucideStore, LucideTrash2 } 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 { FORM_STYLE } from '@/const/layoutTokens';
11
- import DevModal from '@/features/PluginDevModal';
12
- import { pluginHelpers, usePluginStore } from '@/store/plugin';
13
- import { pluginSelectors } from '@/store/plugin/selectors';
14
-
15
- import { useStore } from '../store';
16
- import LocalPluginItem from './LocalPluginItem';
17
- import MarketSettingModal from './MarketSettingModal';
18
-
19
- const useStyles = createStyles(({ css, prefixCls }) => ({
20
- avatar: css`
21
- .${prefixCls}-skeleton-header {
22
- padding-right: 0;
23
- }
24
- `,
25
- label: css`
26
- li {
27
- height: 14px !important;
28
- }
29
-
30
- li + li {
31
- margin-block-start: 12px !important;
32
- }
33
- `,
34
- }));
35
-
36
- const MarketList = memo(() => {
37
- const { t } = useTranslation('setting');
38
- const { styles } = useStyles();
39
-
40
- const [showModal, setModal] = useState(false);
41
- const [showSettings, setShowSettings] = useState(false);
42
-
43
- const [userEnabledPlugins, hasPlugin, toggleAgentPlugin] = useStore((s) => [
44
- s.config.plugins || [],
45
- !!s.config.plugins,
46
- s.toggleAgentPlugin,
47
- ]);
48
-
49
- const [useFetchPluginList, fetchPluginManifest, saveToDevList, updateNewDevPlugin] =
50
- usePluginStore((s) => [
51
- s.useFetchPluginStore,
52
- s.installPlugin,
53
- s.saveToCustomPluginList,
54
- s.updateNewCustomPlugin,
55
- ]);
56
- const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
57
- const pluginList = usePluginStore((s) => s.pluginList, isEqual);
58
- const customPluginList = usePluginStore((s) => s.customPluginList, isEqual);
59
- const totalList = usePluginStore(pluginSelectors.pluginList, isEqual);
60
-
61
- useFetchPluginList();
62
-
63
- const togglePlugin = async (pluginId: string, fetchManifest?: boolean) => {
64
- toggleAgentPlugin(pluginId);
65
- if (fetchManifest) {
66
- await fetchPluginManifest(pluginId);
67
- }
68
- };
69
-
70
- // =========== Skeleton Loading =========== //
71
- const loadingItem = {
72
- avatar: (
73
- <Skeleton
74
- active
75
- avatar={{ size: 40 }}
76
- className={styles.avatar}
77
- paragraph={false}
78
- title={false}
79
- />
80
- ),
81
- label: (
82
- <Skeleton
83
- active
84
- avatar={false}
85
- paragraph={{ className: styles.label, style: { marginBottom: 0 } }}
86
- style={{ width: 300 }}
87
- title={false}
88
- />
89
- ),
90
- };
91
- const loadingList = [loadingItem, loadingItem, loadingItem];
92
-
93
- const isEmpty = pluginList.length === 0;
94
-
95
- // =========== Plugin List =========== //
96
-
97
- const list = pluginList.map(({ identifier, meta }) => ({
98
- avatar: <Avatar avatar={meta.avatar} />,
99
- children: (
100
- <Switch
101
- checked={
102
- // 如果在加载中,说明激活了
103
- pluginManifestLoading[identifier] || !hasPlugin
104
- ? false
105
- : userEnabledPlugins.includes(identifier)
106
- }
107
- loading={pluginManifestLoading[identifier]}
108
- onChange={(checked) => {
109
- togglePlugin(identifier, checked);
110
- }}
111
- />
112
- ),
113
- desc: pluginHelpers.getPluginDesc(meta),
114
- label: pluginHelpers.getPluginTitle(meta),
115
- minWidth: undefined,
116
- tag: identifier,
117
- }));
118
-
119
- // =========== Custom Plugin List =========== //
120
-
121
- const customList = customPluginList.map(({ identifier, meta }) => ({
122
- avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta) || '🧩'} />,
123
- children: <LocalPluginItem id={identifier} />,
124
- desc: pluginHelpers.getPluginDesc(meta),
125
- label: (
126
- <Flexbox align={'center'} gap={8} horizontal>
127
- {pluginHelpers.getPluginTitle(meta)}
128
- <Tag bordered={false} color={'gold'}>
129
- {t('list.item.local.title', { ns: 'plugin' })}
130
- </Tag>
131
- </Flexbox>
132
- ),
133
- minWidth: undefined,
134
- tag: identifier,
135
- }));
136
-
137
- // =========== Deprecated Plugin List =========== //
138
-
139
- // 基于 userEnabledPlugins 和完整的 totalList,检查出不在 totalList 中的插件
140
- const deprecatedList = userEnabledPlugins
141
- .filter((pluginId) => totalList.findIndex((p) => p.identifier === pluginId) < 0)
142
- .map((id) => ({
143
- avatar: <Avatar avatar={'♻️'} />,
144
- children: (
145
- <Switch
146
- checked={true}
147
- onChange={(checked) => {
148
- togglePlugin(id, checked);
149
- }}
150
- />
151
- ),
152
- label: (
153
- <Flexbox align={'center'} gap={8} horizontal>
154
- {id}
155
- <Tag bordered={false} color={'red'}>
156
- {t('list.item.deprecated.title', { ns: 'plugin' })}
157
- </Tag>
158
- </Flexbox>
159
- ),
160
- minWidth: undefined,
161
- tag: id,
162
- }));
163
-
164
- const hasDeprecated = deprecatedList.length > 0;
165
-
166
- return (
167
- <>
168
- <DevModal
169
- onOpenChange={setModal}
170
- onSave={async (devPlugin) => {
171
- // 先保存
172
- saveToDevList(devPlugin);
173
- // 再开启插件
174
- await togglePlugin(devPlugin.identifier, true);
175
- }}
176
- onValueChange={updateNewDevPlugin}
177
- open={showModal}
178
- />
179
- <MarketSettingModal onOpenChange={setShowSettings} open={showSettings} />
180
- <Form
181
- items={[
182
- {
183
- children: isEmpty ? loadingList : [...deprecatedList, ...customList, ...list],
184
- extra: (
185
- <Space.Compact style={{ width: 'auto' }}>
186
- <Button
187
- icon={<Icon icon={LucideBlocks} />}
188
- onClick={(e) => {
189
- e.stopPropagation();
190
- setModal(true);
191
- }}
192
- size={'small'}
193
- >
194
- {t('settingPlugin.addTooltip')}
195
- </Button>
196
- {hasDeprecated ? (
197
- <Tooltip title={t('settingPlugin.clearDeprecated')}>
198
- <Button
199
- icon={<Icon icon={LucideTrash2} />}
200
- onClick={(e) => {
201
- e.stopPropagation();
202
- for (const i of deprecatedList) {
203
- togglePlugin(i.tag as string, false);
204
- }
205
- }}
206
- size={'small'}
207
- />
208
- </Tooltip>
209
- ) : null}
210
- <Tooltip title={t('settingPlugin.settings')}>
211
- <Button
212
- icon={<Icon icon={LucideSettings} />}
213
- onClick={(e) => {
214
- e.stopPropagation();
215
- setShowSettings(true);
216
- }}
217
- size={'small'}
218
- />
219
- </Tooltip>
220
- </Space.Compact>
221
- ),
222
- icon: LucideStore,
223
- title: t('settingPlugin.title'),
224
- },
225
- ]}
226
- {...FORM_STYLE}
227
- />
228
- </>
229
- );
230
- });
231
-
232
- export default MarketList;
@@ -1,80 +0,0 @@
1
- import { Form, ItemGroup, Markdown } from '@lobehub/ui';
2
- import { createStyles } from 'antd-style';
3
- import isEqual from 'fast-deep-equal';
4
- import { ToyBrick } from 'lucide-react';
5
- import { memo } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
-
8
- import { FORM_STYLE } from '@/const/layoutTokens';
9
- import { transformPluginSettings } from '@/features/PluginSettings';
10
- import PluginSettingRender from '@/features/PluginSettings/PluginSettingRender';
11
- import { pluginHelpers, usePluginStore } from '@/store/plugin';
12
- import { pluginSelectors } from '@/store/plugin/selectors';
13
-
14
- import { useStore } from '../store';
15
-
16
- const useStyles = createStyles(({ css, token, cx, stylish }) => ({
17
- markdown: cx(
18
- stylish.markdownInChat,
19
- css`
20
- p {
21
- color: ${token.colorTextDescription};
22
- }
23
- `,
24
- ),
25
- }));
26
-
27
- const PluginSettings = memo(() => {
28
- const { t } = useTranslation('setting');
29
- const { styles } = useStyles();
30
-
31
- const [plugins] = useStore((s) => [s.config.plugins || [], !!s.config.plugins]);
32
-
33
- const [useFetchPluginList, updatePluginSettings] = usePluginStore((s) => [
34
- s.useFetchPluginStore,
35
- s.updatePluginSettings,
36
- ]);
37
- const pluginList = usePluginStore(pluginSelectors.pluginList);
38
-
39
- const pluginManifestMap = usePluginStore((s) => s.pluginManifestMap, isEqual);
40
- const pluginsSettings = usePluginStore((s) => s.pluginsSettings, isEqual);
41
- useFetchPluginList();
42
-
43
- const isEmpty = pluginList.length === 0;
44
-
45
- const pluginsConfig = isEmpty
46
- ? []
47
- : (plugins
48
- .map((identifier) => {
49
- const item = pluginList.find((i) => i.identifier === identifier);
50
-
51
- if (!item) return null;
52
- const manifest = pluginManifestMap[identifier];
53
-
54
- if (!manifest?.settings) return null;
55
-
56
- return {
57
- children: transformPluginSettings(manifest.settings).map((item) => ({
58
- ...item,
59
- children: (
60
- <PluginSettingRender
61
- defaultValue={pluginsSettings[identifier]?.[item.name]}
62
- format={item.format}
63
- onChange={(value) => {
64
- updatePluginSettings(identifier, { [item.name]: value });
65
- }}
66
- type={item.type as any}
67
- />
68
- ),
69
- desc: item.desc && <Markdown className={styles.markdown}>{item.desc}</Markdown>,
70
- })),
71
- icon: ToyBrick,
72
- title: t('settingPlugin.config', { id: pluginHelpers.getPluginTitle(item.meta) }),
73
- };
74
- })
75
- .filter(Boolean) as unknown as ItemGroup[]);
76
-
77
- return <Form items={pluginsConfig} {...FORM_STYLE} />;
78
- });
79
-
80
- export default PluginSettings;
@@ -1,38 +0,0 @@
1
- import { FileDispatch, FilesState, filesReducer } from './files';
2
-
3
- describe('filesReducer', () => {
4
- it('should add a file to the state', () => {
5
- const initialState: FilesState = ['file1', 'file2'];
6
- const action: FileDispatch = { type: 'addFile', file: 'file3' };
7
- const newState = filesReducer(initialState, action);
8
- expect(newState).toEqual(['file1', 'file2', 'file3']);
9
- });
10
-
11
- it('should delete a file from the state by ID', () => {
12
- const initialState: FilesState = ['file1', 'file2', 'file3'];
13
- const action: FileDispatch = { type: 'deleteFile', id: 'file2' };
14
- const newState = filesReducer(initialState, action);
15
- expect(newState).toEqual(['file1', 'file3']);
16
- });
17
-
18
- it('should return the state unchanged if file ID does not exist for deletion', () => {
19
- const initialState: FilesState = ['file1', 'file2', 'file3'];
20
- const action: FileDispatch = { type: 'deleteFile', id: 'file4' };
21
- const newState = filesReducer(initialState, action);
22
- expect(newState).toEqual(['file1', 'file2', 'file3']);
23
- });
24
-
25
- it('should add multiple files to the state', () => {
26
- const initialState: FilesState = ['file1', 'file2'];
27
- const action: FileDispatch = { type: 'addFiles', files: ['file3', 'file4'] };
28
- const newState = filesReducer(initialState, action);
29
- expect(newState).toEqual(['file1', 'file2', 'file3', 'file4']);
30
- });
31
-
32
- it('should return the initial state if the action type is unknown', () => {
33
- const initialState: FilesState = ['file1', 'file2'];
34
- const action = { type: 'unknown', id: 'file1' };
35
- const newState = filesReducer(initialState, action as FileDispatch);
36
- expect(newState).toEqual(['file1', 'file2']);
37
- });
38
- });
@@ -1,37 +0,0 @@
1
- import { produce } from 'immer';
2
-
3
- export type FilesState = string[];
4
-
5
- export type FileDispatch =
6
- | { file: string; type: 'addFile' }
7
- | { id: string; type: 'deleteFile' }
8
- | { files: string[]; type: 'addFiles' };
9
-
10
- export const filesReducer = (state: FilesState, payload: FileDispatch): FilesState => {
11
- switch (payload.type) {
12
- case 'addFile': {
13
- return produce(state, (draftState) => {
14
- draftState.push(payload.file);
15
- });
16
- }
17
-
18
- case 'deleteFile': {
19
- return produce(state, (draftState) => {
20
- const index = draftState.indexOf(payload.id);
21
- if (index !== -1) {
22
- draftState.splice(index, 1);
23
- }
24
- });
25
- }
26
-
27
- case 'addFiles': {
28
- return produce(state, (draftState) => {
29
- draftState.push(...payload.files);
30
- });
31
- }
32
-
33
- default: {
34
- return state;
35
- }
36
- }
37
- };
@@ -1,21 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
-
3
- import { usePluginStore } from '../store';
4
-
5
- export const usePluginHydrated = () => {
6
- // 根据 sessions 是否有值来判断是否已经初始化
7
-
8
- // 并且插件的 manifest 也准备完毕
9
- const manifestPrepared = usePluginStore((s) => s.manifestPrepared);
10
- const [isInit, setInit] = useState(false);
11
-
12
- useEffect(() => {
13
- const hasRehydrated = usePluginStore.persist.hasHydrated();
14
-
15
- if (hasRehydrated && !isInit) {
16
- setInit(true);
17
- }
18
- }, []);
19
-
20
- return isInit && manifestPrepared;
21
- };
@@ -1,2 +0,0 @@
1
- export * from './helpers';
2
- export { usePluginStore } from './store';
@@ -1,9 +0,0 @@
1
- import { CustomPluginState, initialCustomPluginState } from './slices/customPlugin';
2
- import { PluginState, initialPluginState } from './slices/plugin';
3
-
4
- export type PluginStoreState = PluginState & CustomPluginState;
5
-
6
- export const initialState: PluginStoreState = {
7
- ...initialPluginState,
8
- ...initialCustomPluginState,
9
- };
@@ -1,173 +0,0 @@
1
- import {
2
- LobeChatPluginManifest,
3
- LobeChatPluginsMarketIndex,
4
- pluginManifestSchema,
5
- } from '@lobehub/chat-plugin-sdk';
6
- import { message } from 'antd';
7
- import { produce } from 'immer';
8
- import { merge, uniq } from 'lodash-es';
9
- import useSWR, { SWRResponse } from 'swr';
10
- import { StateCreator } from 'zustand/vanilla';
11
-
12
- import { pluginService } from '@/services/plugin';
13
- import { pluginSelectors } from '@/store/plugin/selectors';
14
- import { LobeSessions } from '@/types/session';
15
- import { setNamespace } from '@/utils/storeDebug';
16
-
17
- import { PluginStore } from '../../store';
18
- import { PluginDispatch, pluginManifestReducer } from './reducers/manifest';
19
-
20
- const n = setNamespace('plugin');
21
-
22
- /**
23
- * 插件接口
24
- */
25
- export interface PluginAction {
26
- checkLocalEnabledPlugins: (sessions: LobeSessions) => void;
27
- checkPluginsIsInstalled: (plugins: string[]) => void;
28
- deletePluginSettings: (id: string) => void;
29
- dispatchPluginManifest: (payload: PluginDispatch) => void;
30
- installPlugin: (identifier: string) => Promise<void>;
31
- installPlugins: (plugins: string[]) => Promise<void>;
32
- loadPluginStore: () => Promise<LobeChatPluginsMarketIndex>;
33
- resetPluginSettings: () => void;
34
- updateManifestLoadingState: (key: string, value: boolean | undefined) => void;
35
- updatePluginSettings: <T>(id: string, settings: Partial<T>) => void;
36
- useCheckPluginsIsInstalled: (plugins: string[]) => SWRResponse;
37
- useFetchPluginStore: () => SWRResponse<LobeChatPluginsMarketIndex>;
38
- }
39
-
40
- export const createPluginSlice: StateCreator<
41
- PluginStore,
42
- [['zustand/devtools', never]],
43
- [],
44
- PluginAction
45
- > = (set, get) => ({
46
- checkLocalEnabledPlugins: async (sessions) => {
47
- const { checkPluginsIsInstalled } = get();
48
-
49
- let enabledPlugins: string[] = [];
50
-
51
- for (const session of sessions) {
52
- const plugins = session.config.plugins;
53
- if (!plugins || plugins.length === 0) continue;
54
-
55
- enabledPlugins = [...enabledPlugins, ...plugins];
56
- }
57
-
58
- const plugins = uniq(enabledPlugins);
59
-
60
- await checkPluginsIsInstalled(plugins);
61
- },
62
- checkPluginsIsInstalled: async (plugins) => {
63
- // if there is no plugins, just skip.
64
- if (plugins.length === 0) return;
65
-
66
- const { loadPluginStore, installPlugins } = get();
67
-
68
- // check if the store is empty
69
- // if it is, we need to load the plugin store
70
- if (pluginSelectors.onlinePluginStore(get()).length === 0) {
71
- await loadPluginStore();
72
- }
73
-
74
- await installPlugins(plugins);
75
-
76
- set({ manifestPrepared: true }, false, n('checkPluginsIsInstalled'));
77
- },
78
- deletePluginSettings: (id) => {
79
- set(
80
- produce((draft) => {
81
- draft.pluginsSettings[id] = undefined;
82
- }),
83
- false,
84
- n('deletePluginSettings'),
85
- );
86
- },
87
- dispatchPluginManifest: (payload) => {
88
- const { pluginManifestMap } = get();
89
- const nextManifest = pluginManifestReducer(pluginManifestMap, payload);
90
-
91
- set({ pluginManifestMap: nextManifest }, false, n('dispatchPluginManifest', payload));
92
- },
93
- installPlugin: async (name) => {
94
- const plugin = pluginSelectors.getPluginMetaById(name)(get());
95
-
96
- // 1. valid plugin
97
-
98
- if (!plugin) return;
99
-
100
- if (!plugin.manifest) {
101
- message.error('插件未配置 描述文件');
102
- return;
103
- }
104
-
105
- // 2. 发送请求
106
- get().updateManifestLoadingState(name, true);
107
- let data: LobeChatPluginManifest | null;
108
-
109
- try {
110
- const res = await fetch(plugin.manifest);
111
-
112
- data = await res.json();
113
- } catch (error) {
114
- data = null;
115
- console.error(error);
116
- }
117
-
118
- get().updateManifestLoadingState(name, undefined);
119
- if (!data) {
120
- message.error(`插件 ${plugin.meta.title} 描述文件请求失败`);
121
- return;
122
- }
123
-
124
- // 3. 校验插件文件格式规范
125
- const { success } = pluginManifestSchema.safeParse(data);
126
-
127
- if (!success) {
128
- message.error('插件描述文件格式错误');
129
- return;
130
- }
131
-
132
- // 4. 存储 manifest 信息
133
- get().dispatchPluginManifest({ id: plugin.identifier, plugin: data, type: 'addManifest' });
134
- },
135
- installPlugins: async (plugins) => {
136
- const { installPlugin } = get();
137
-
138
- await Promise.all(plugins.map((identifier) => installPlugin(identifier)));
139
- },
140
- loadPluginStore: async () => {
141
- const pluginMarketIndex = await pluginService.getPluginList();
142
-
143
- set({ pluginList: pluginMarketIndex.plugins }, false, n('loadPluginList'));
144
-
145
- return pluginMarketIndex;
146
- },
147
- resetPluginSettings: () => {
148
- set({ pluginsSettings: {} }, false, n('resetPluginSettings'));
149
- },
150
- updateManifestLoadingState: (key, value) => {
151
- set(
152
- produce((draft) => {
153
- draft.pluginManifestLoading[key] = value;
154
- }),
155
- false,
156
- n('updateManifestLoadingState'),
157
- );
158
- },
159
- updatePluginSettings: (id, settings) => {
160
- set(
161
- produce((draft) => {
162
- draft.pluginsSettings[id] = merge({}, draft.pluginsSettings[id], settings);
163
- }),
164
- false,
165
- n('updatePluginSettings'),
166
- );
167
- },
168
-
169
- useCheckPluginsIsInstalled: (plugins) => useSWR(plugins, get().checkPluginsIsInstalled),
170
-
171
- useFetchPluginStore: () =>
172
- useSWR<LobeChatPluginsMarketIndex>('loadPluginStore', get().loadPluginStore),
173
- });