@lobehub/chat 0.108.0 → 0.109.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +24 -4
- package/README.zh-CN.md +24 -4
- package/docs/Environment-Variable.md +2 -2
- package/docs/Environment-Variable.zh-CN.md +2 -2
- package/locales/en_US/plugin.json +19 -0
- package/locales/en_US/setting.json +26 -0
- package/locales/ja_JP/plugin.json +19 -0
- package/locales/ja_JP/setting.json +26 -0
- package/locales/ko_KR/plugin.json +19 -0
- package/locales/ko_KR/setting.json +26 -0
- package/locales/ru_RU/plugin.json +19 -0
- package/locales/ru_RU/setting.json +26 -0
- package/locales/zh_CN/plugin.json +19 -0
- package/locales/zh_CN/setting.json +26 -5
- package/locales/zh_TW/plugin.json +19 -0
- package/locales/zh_TW/setting.json +26 -0
- package/package.json +3 -3
- package/src/app/chat/features/ChatHeader/PluginTag/PluginStatus.tsx +6 -6
- package/src/app/chat/features/ChatHeader/PluginTag/index.tsx +4 -4
- package/src/app/chat/features/ChatInput/ActionBar/Tools/ToolItem.tsx +37 -0
- package/src/app/chat/features/ChatInput/ActionBar/Tools/index.tsx +116 -0
- package/src/app/chat/features/ChatInput/ActionBar/config.ts +3 -1
- package/src/app/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +4 -4
- package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +3 -3
- package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +4 -4
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +2 -2
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/Iframe.tsx +4 -4
- package/src/app/chat/features/Conversation/ChatList/Plugins/Render/StandaloneType/index.tsx +2 -2
- package/src/app/chat/features/Conversation/useInitConversation.ts +2 -2
- package/src/app/settings/common/Common.tsx +2 -2
- package/src/features/AgentSetting/AgentPlugin/AddPluginButton.tsx +56 -0
- package/src/features/AgentSetting/AgentPlugin/LoadingList.tsx +48 -0
- package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +5 -5
- package/src/features/AgentSetting/AgentPlugin/PluginAction/index.tsx +36 -0
- package/src/features/AgentSetting/AgentPlugin/index.tsx +157 -9
- package/src/features/AgentSetting/store/action.ts +0 -1
- package/src/features/PluginDevModal/MetaForm.tsx +3 -3
- package/src/features/PluginDevModal/PluginPreview.tsx +1 -1
- package/src/features/PluginDevModal/index.tsx +4 -1
- package/src/features/PluginSettings/index.tsx +3 -3
- package/src/features/PluginSettingsModal/index.tsx +22 -17
- package/src/features/PluginStore/Loading.tsx +13 -0
- package/src/features/PluginStore/PluginItem.tsx +81 -0
- package/src/features/PluginStore/PluginSettings.tsx +40 -0
- package/src/features/{AgentSetting/AgentPlugin/MarketSettingModal.tsx → PluginStore/StoreSetting.tsx} +2 -2
- package/src/features/PluginStore/index.tsx +95 -0
- package/src/layout/GlobalLayout/StoreHydration.tsx +3 -3
- package/src/locales/default/plugin.ts +19 -0
- package/src/locales/default/setting.ts +26 -5
- package/src/services/__tests__/chat.test.ts +221 -0
- package/src/services/__tests__/message.test.ts +333 -0
- package/src/services/__tests__/plugin.test.ts +88 -0
- package/src/services/__tests__/session.test.ts +227 -0
- package/src/services/__tests__/topic.test.ts +220 -0
- package/src/services/chat.ts +4 -6
- package/src/services/plugin.ts +12 -1
- package/src/store/chat/actions/message.test.ts +0 -1
- package/src/store/session/slices/agent/action.ts +24 -8
- package/src/store/{plugin → tool}/helpers.ts +3 -14
- package/src/store/tool/index.ts +2 -0
- package/src/store/tool/initialState.ts +11 -0
- package/src/store/tool/selectors.ts +3 -0
- package/src/store/tool/slices/customPlugin/action.test.ts +164 -0
- package/src/store/{plugin → tool}/slices/customPlugin/action.ts +2 -2
- package/src/store/tool/slices/customPlugin/selectors.test.ts +59 -0
- package/src/store/tool/slices/customPlugin/selectors.ts +9 -0
- package/src/store/tool/slices/plugin/action.test.ts +141 -0
- package/src/store/tool/slices/plugin/action.ts +95 -0
- package/src/store/{plugin → tool}/slices/plugin/initialState.ts +0 -7
- package/src/store/{plugin → tool/slices/plugin}/selectors.test.ts +4 -41
- package/src/store/{plugin → tool/slices/plugin}/selectors.ts +17 -19
- package/src/store/tool/slices/store/action.test.ts +396 -0
- package/src/store/tool/slices/store/action.ts +103 -0
- package/src/store/tool/slices/store/index.ts +3 -0
- package/src/store/tool/slices/store/initialState.ts +17 -0
- package/src/store/tool/slices/store/selectors.test.ts +48 -0
- package/src/store/tool/slices/store/selectors.ts +21 -0
- package/src/store/{plugin → tool}/store.ts +8 -6
- package/vitest.config.ts +1 -0
- package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +0 -232
- package/src/features/AgentSetting/AgentPlugin/PluginSettings.tsx +0 -80
- package/src/store/chat/reducers/files.test.ts +0 -38
- package/src/store/chat/reducers/files.ts +0 -37
- package/src/store/plugin/hooks/useSessionHydrated.ts +0 -21
- package/src/store/plugin/index.ts +0 -2
- package/src/store/plugin/initialState.ts +0 -9
- package/src/store/plugin/slices/plugin/action.ts +0 -173
- /package/src/store/{plugin → tool}/slices/customPlugin/index.ts +0 -0
- /package/src/store/{plugin → tool}/slices/customPlugin/initialState.ts +0 -0
- /package/src/store/{plugin → tool}/slices/customPlugin/reducers/customPluginList.ts +0 -0
- /package/src/store/{plugin → tool}/slices/plugin/index.ts +0 -0
- /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,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
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|