@lobehub/chat 0.66.0 → 0.68.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.
- package/.eslintrc.js +1 -0
- package/CHANGELOG.md +68 -0
- package/README-zh_CN.md +4 -1
- package/README.md +4 -1
- package/locales/en_US/plugin.json +8 -2
- package/package.json +2 -2
- package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +10 -8
- package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +29 -13
- package/src/features/PluginDevModal/ManifestForm.tsx +150 -86
- package/src/features/PluginDevModal/MetaForm.tsx +16 -17
- package/src/features/PluginDevModal/PluginPreview.tsx +9 -8
- package/src/features/PluginDevModal/index.tsx +26 -23
- package/src/locales/default/plugin.ts +9 -5
- package/src/pages/chat/features/Conversation/ChatList/Plugins/IFrameRender/hooks.ts +29 -0
- package/src/pages/chat/features/Conversation/ChatList/Plugins/IFrameRender/index.tsx +62 -0
- package/src/pages/chat/features/Conversation/ChatList/Plugins/IFrameRender/utils.ts +17 -0
- package/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx +20 -4
- package/src/pages/chat/features/Conversation/ChatList/Plugins/{CustomRender.tsx → SystemJsRender/index.tsx} +5 -4
- package/src/store/plugin/initialState.ts +5 -34
- package/src/store/plugin/selectors.ts +12 -7
- package/src/store/plugin/slices/customPlugin/action.ts +64 -0
- package/src/store/plugin/slices/customPlugin/index.ts +3 -0
- package/src/store/plugin/slices/customPlugin/initialState.ts +16 -0
- package/src/store/plugin/{reducers/devPluginList.ts → slices/customPlugin/reducers/customPluginList.ts} +6 -6
- package/src/store/plugin/{action.ts → slices/plugin/action.ts} +13 -25
- package/src/store/plugin/slices/plugin/index.ts +3 -0
- package/src/store/plugin/slices/plugin/initialState.ts +22 -0
- package/src/store/plugin/store.ts +6 -4
- package/src/types/plugin.ts +8 -1
- /package/src/pages/chat/features/Conversation/ChatList/Plugins/{dynamticLoader.ts → SystemJsRender/utils.ts} +0 -0
- /package/src/store/plugin/{reducers → slices/plugin/reducers}/manifest.ts +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Icon } from '@lobehub/ui';
|
|
2
2
|
import { App, Button, ConfigProvider, Form, Modal, Popconfirm } from 'antd';
|
|
3
|
-
import { createStyles
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
4
|
import { LucideBlocks } from 'lucide-react';
|
|
5
5
|
import { lighten } from 'polished';
|
|
6
|
-
import { memo, useEffect } from 'react';
|
|
6
|
+
import { memo, useEffect, useState } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
import { Flexbox } from 'react-layout-kit';
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { CustomPlugin } from '@/types/plugin';
|
|
11
11
|
|
|
12
12
|
import ManifestForm from './ManifestForm';
|
|
13
13
|
import MetaForm from './MetaForm';
|
|
@@ -26,33 +26,31 @@ const useStyles = createStyles(({ css, token, prefixCls }) => ({
|
|
|
26
26
|
}));
|
|
27
27
|
|
|
28
28
|
interface DevModalProps {
|
|
29
|
+
mode?: 'edit' | 'create';
|
|
29
30
|
onDelete?: () => void;
|
|
30
31
|
onOpenChange: (open: boolean) => void;
|
|
31
|
-
onSave?: (value:
|
|
32
|
-
onValueChange?: (value: Partial<
|
|
32
|
+
onSave?: (value: CustomPlugin) => Promise<void> | void;
|
|
33
|
+
onValueChange?: (value: Partial<CustomPlugin>) => void;
|
|
33
34
|
open?: boolean;
|
|
34
|
-
|
|
35
|
-
value?: DevPlugin;
|
|
35
|
+
value?: CustomPlugin;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const DevModal = memo<DevModalProps>(
|
|
39
|
-
({ open,
|
|
39
|
+
({ open, mode = 'create', value, onValueChange, onSave, onOpenChange, onDelete }) => {
|
|
40
|
+
const isEditMode = mode === 'edit';
|
|
40
41
|
const { t } = useTranslation('plugin');
|
|
41
|
-
|
|
42
|
-
const [form] = Form.useForm();
|
|
43
|
-
const theme = useTheme();
|
|
44
|
-
|
|
45
|
-
const { styles } = useStyles();
|
|
46
|
-
|
|
42
|
+
const { styles, theme } = useStyles();
|
|
47
43
|
const { message } = App.useApp();
|
|
48
44
|
|
|
45
|
+
const [submitting, setSubmitting] = useState(false);
|
|
46
|
+
const [form] = Form.useForm();
|
|
49
47
|
useEffect(() => {
|
|
50
48
|
form.setFieldsValue(value);
|
|
51
49
|
}, []);
|
|
52
50
|
|
|
53
51
|
const footer = (
|
|
54
52
|
<Flexbox horizontal justify={'space-between'} style={{ marginTop: 24 }}>
|
|
55
|
-
{
|
|
53
|
+
{isEditMode ? (
|
|
56
54
|
<Popconfirm
|
|
57
55
|
okButtonProps={{
|
|
58
56
|
danger: true,
|
|
@@ -80,24 +78,30 @@ const DevModal = memo<DevModalProps>(
|
|
|
80
78
|
{t('cancel', { ns: 'common' })}
|
|
81
79
|
</Button>
|
|
82
80
|
<Button
|
|
81
|
+
loading={submitting}
|
|
83
82
|
onClick={() => {
|
|
84
83
|
form.submit();
|
|
85
84
|
}}
|
|
86
85
|
type={'primary'}
|
|
87
86
|
>
|
|
88
|
-
{t('dev.save')}
|
|
87
|
+
{t(isEditMode ? 'dev.update' : 'dev.save')}
|
|
89
88
|
</Button>
|
|
90
89
|
</Flexbox>
|
|
91
90
|
</Flexbox>
|
|
92
91
|
);
|
|
92
|
+
|
|
93
93
|
return (
|
|
94
94
|
<Form.Provider
|
|
95
95
|
onFormChange={() => {
|
|
96
96
|
onValueChange?.(form.getFieldsValue());
|
|
97
97
|
}}
|
|
98
|
-
onFormFinish={(_, info) => {
|
|
99
|
-
onSave
|
|
100
|
-
|
|
98
|
+
onFormFinish={async (_, info) => {
|
|
99
|
+
if (onSave) {
|
|
100
|
+
setSubmitting(true);
|
|
101
|
+
await onSave?.(info.values as CustomPlugin);
|
|
102
|
+
setSubmitting(false);
|
|
103
|
+
}
|
|
104
|
+
message.success(t(isEditMode ? 'dev.updateSuccess' : 'dev.saveSuccess'));
|
|
101
105
|
onOpenChange(false);
|
|
102
106
|
}}
|
|
103
107
|
>
|
|
@@ -121,7 +125,6 @@ const DevModal = memo<DevModalProps>(
|
|
|
121
125
|
form.submit();
|
|
122
126
|
}}
|
|
123
127
|
open={open}
|
|
124
|
-
style={{ height: 750 }}
|
|
125
128
|
title={
|
|
126
129
|
<Flexbox gap={8} horizontal>
|
|
127
130
|
<Icon icon={LucideBlocks} />
|
|
@@ -139,9 +142,9 @@ const DevModal = memo<DevModalProps>(
|
|
|
139
142
|
{/* { children: <ManifestForm />, key: 'manifest', label: t('dev.tabs.manifest') },*/}
|
|
140
143
|
{/* ]}*/}
|
|
141
144
|
{/*/>*/}
|
|
142
|
-
<PluginPreview />
|
|
143
|
-
<ManifestForm form={form}
|
|
144
|
-
<MetaForm form={form} />
|
|
145
|
+
<PluginPreview form={form} />
|
|
146
|
+
<ManifestForm form={form} />
|
|
147
|
+
<MetaForm form={form} mode={mode} />
|
|
145
148
|
</Flexbox>
|
|
146
149
|
</Modal>
|
|
147
150
|
</ConfigProvider>
|
|
@@ -43,16 +43,19 @@ export default {
|
|
|
43
43
|
label: '首页',
|
|
44
44
|
},
|
|
45
45
|
identifier: {
|
|
46
|
-
desc: '
|
|
46
|
+
desc: '插件的唯一标识,将从 manifest 中自动识别',
|
|
47
47
|
errorDuplicate: '标识符和已有插件重复,请修改标识符',
|
|
48
48
|
label: '标识符',
|
|
49
49
|
pattenErrorMessage: '只能输入英文字符、数字 、- 和_ 这两个符号',
|
|
50
50
|
},
|
|
51
51
|
manifest: {
|
|
52
52
|
desc: 'LobeChat 将会通过该链接安装插件',
|
|
53
|
-
|
|
53
|
+
jsonInvalid: ' manifest 不符合规范,校验结果: \n\n {{error}}',
|
|
54
54
|
label: '插件描述文件 Url 地址',
|
|
55
|
-
|
|
55
|
+
preview: '预览 Manifest',
|
|
56
|
+
refresh: '刷新',
|
|
57
|
+
requestError: '请求该链接失败,请输入一个有效的链接,并检查链接是否允许跨域访问',
|
|
58
|
+
urlError: '该链接没有返回 JSON 格式的内容, 请输入一个有效的链接',
|
|
56
59
|
},
|
|
57
60
|
title: {
|
|
58
61
|
desc: '插件标题',
|
|
@@ -73,13 +76,14 @@ export default {
|
|
|
73
76
|
manifest: '功能描述清单 (Manifest)',
|
|
74
77
|
meta: '插件元信息',
|
|
75
78
|
},
|
|
76
|
-
|
|
77
79
|
title: '添加自定义插件',
|
|
80
|
+
update: '更新',
|
|
81
|
+
updateSuccess: '插件设置更新成功',
|
|
78
82
|
},
|
|
79
83
|
list: {
|
|
80
84
|
item: {
|
|
81
85
|
'local.config': '配置',
|
|
82
|
-
'local.title': '
|
|
86
|
+
'local.title': '自定义',
|
|
83
87
|
},
|
|
84
88
|
},
|
|
85
89
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import { onPluginFetchMessage, onPluginReady } from './utils';
|
|
4
|
+
|
|
5
|
+
export const useOnPluginReady = (onReady: () => void) => {
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const fn = (e: MessageEvent) => {
|
|
8
|
+
onPluginReady(e, onReady);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
window.addEventListener('message', fn);
|
|
12
|
+
return () => {
|
|
13
|
+
window.removeEventListener('message', fn);
|
|
14
|
+
};
|
|
15
|
+
}, []);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const useOnPluginFetchMessage = (onRequest: (data: any) => void, deps: any[] = []) => {
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const fn = (e: MessageEvent) => {
|
|
21
|
+
onPluginFetchMessage(e, onRequest);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
window.addEventListener('message', fn);
|
|
25
|
+
return () => {
|
|
26
|
+
window.removeEventListener('message', fn);
|
|
27
|
+
};
|
|
28
|
+
}, deps);
|
|
29
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { PluginRenderProps } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { Skeleton } from 'antd';
|
|
3
|
+
import { memo, useEffect, useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useOnPluginFetchMessage, useOnPluginReady } from './hooks';
|
|
6
|
+
import { sendMessageToPlugin } from './utils';
|
|
7
|
+
|
|
8
|
+
interface IFrameRenderProps extends PluginRenderProps {
|
|
9
|
+
height?: number;
|
|
10
|
+
url: string;
|
|
11
|
+
width?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const IFrameRender = memo<IFrameRenderProps>(({ url, width = 800, height = 300, ...props }) => {
|
|
15
|
+
const [loading, setLoading] = useState(true);
|
|
16
|
+
const [readyForRender, setReady] = useState(false);
|
|
17
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
18
|
+
|
|
19
|
+
useOnPluginReady(() => setReady(true));
|
|
20
|
+
|
|
21
|
+
// 当 props 发生变化时,主动向 iframe 发送数据
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const iframeWin = iframeRef.current?.contentWindow;
|
|
24
|
+
|
|
25
|
+
if (iframeWin && readyForRender) {
|
|
26
|
+
sendMessageToPlugin(iframeWin, props);
|
|
27
|
+
}
|
|
28
|
+
}, [readyForRender, props]);
|
|
29
|
+
|
|
30
|
+
// 当接收到来自 iframe 的请求时,触发发送数据
|
|
31
|
+
useOnPluginFetchMessage(() => {
|
|
32
|
+
const iframeWin = iframeRef.current?.contentWindow;
|
|
33
|
+
if (iframeWin) {
|
|
34
|
+
sendMessageToPlugin(iframeWin, props);
|
|
35
|
+
}
|
|
36
|
+
}, [props]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
{loading && <Skeleton active style={{ width }} />}
|
|
41
|
+
<iframe
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
allowtransparency="true"
|
|
44
|
+
height={height}
|
|
45
|
+
hidden={loading}
|
|
46
|
+
onLoad={() => {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
}}
|
|
49
|
+
ref={iframeRef}
|
|
50
|
+
src={url}
|
|
51
|
+
style={{
|
|
52
|
+
border: 0,
|
|
53
|
+
// iframe 在 color-scheme:dark 模式下无法透明
|
|
54
|
+
// refs: https://www.jianshu.com/p/bc5a37bb6a7b
|
|
55
|
+
colorScheme: 'light',
|
|
56
|
+
}}
|
|
57
|
+
width={width}
|
|
58
|
+
/>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
export default IFrameRender;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PluginChannel } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
|
|
3
|
+
export const onPluginReady = (e: MessageEvent, onReady: () => void) => {
|
|
4
|
+
if (e.data.type === PluginChannel.pluginReadyForRender) {
|
|
5
|
+
onReady();
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const onPluginFetchMessage = (e: MessageEvent, onRequest: (data: any) => void) => {
|
|
10
|
+
if (e.data.type === PluginChannel.fetchPluginMessage) {
|
|
11
|
+
onRequest(e.data);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const sendMessageToPlugin = (window: Window, props: any) => {
|
|
16
|
+
window.postMessage({ props, type: PluginChannel.renderPlugin }, '*');
|
|
17
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Loading3QuartersOutlined } from '@ant-design/icons';
|
|
2
|
-
import { memo } from 'react';
|
|
2
|
+
import { memo, useMemo } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
|
6
6
|
import { usePluginStore } from '@/store/plugin';
|
|
7
7
|
import { ChatMessage } from '@/types/chatMessage';
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import IFrameRender from './IFrameRender';
|
|
10
|
+
import SystemJsRender from './SystemJsRender';
|
|
10
11
|
|
|
11
12
|
export interface FunctionMessageProps extends ChatMessage {
|
|
12
13
|
loading?: boolean;
|
|
@@ -23,6 +24,8 @@ const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
|
|
|
23
24
|
isJSON = false;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
const contentObj = useMemo(() => (isJSON ? JSON.parse(content) : content), [content]);
|
|
28
|
+
|
|
26
29
|
// if (!loading)
|
|
27
30
|
|
|
28
31
|
if (!isJSON) {
|
|
@@ -36,10 +39,23 @@ const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
|
|
|
36
39
|
);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
if (!manifest?.ui
|
|
42
|
+
if (!manifest?.ui) return;
|
|
43
|
+
|
|
44
|
+
const ui = manifest.ui;
|
|
45
|
+
|
|
46
|
+
if (!ui.url) return;
|
|
47
|
+
|
|
48
|
+
if (ui.mode === 'module')
|
|
49
|
+
return <SystemJsRender content={contentObj} name={name || 'unknown'} url={ui.url} />;
|
|
40
50
|
|
|
41
51
|
return (
|
|
42
|
-
<
|
|
52
|
+
<IFrameRender
|
|
53
|
+
content={contentObj}
|
|
54
|
+
height={ui.height}
|
|
55
|
+
name={name || 'unknown'}
|
|
56
|
+
url={ui.url}
|
|
57
|
+
width={ui.width}
|
|
58
|
+
/>
|
|
43
59
|
);
|
|
44
60
|
});
|
|
45
61
|
|
|
@@ -2,12 +2,13 @@ import { PluginRender, PluginRenderProps } from '@lobehub/chat-plugin-sdk';
|
|
|
2
2
|
import { Skeleton } from 'antd';
|
|
3
3
|
import { memo, useEffect, useState } from 'react';
|
|
4
4
|
|
|
5
|
-
import { system } from './
|
|
5
|
+
import { system } from './utils';
|
|
6
6
|
|
|
7
|
-
interface
|
|
7
|
+
interface SystemJsRenderProps extends PluginRenderProps {
|
|
8
8
|
url: string;
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
const SystemJsRender = memo<SystemJsRenderProps>(({ url, ...props }) => {
|
|
11
12
|
const [component, setComp] = useState<PluginRender | null>(null);
|
|
12
13
|
|
|
13
14
|
useEffect(() => {
|
|
@@ -31,4 +32,4 @@ const CustomRender = memo<CustomRenderProps>(({ url, ...props }) => {
|
|
|
31
32
|
|
|
32
33
|
return <Render {...props} />;
|
|
33
34
|
});
|
|
34
|
-
export default
|
|
35
|
+
export default SystemJsRender;
|
|
@@ -1,38 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CustomPluginState, initialCustomPluginState } from './slices/customPlugin';
|
|
2
|
+
import { PluginState, initialPluginState } from './slices/plugin';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type PluginManifestLoadingState = Record<string, boolean>;
|
|
6
|
-
export type PluginsSettings = Record<string, any>;
|
|
7
|
-
|
|
8
|
-
export interface DevPlugin extends LobeChatPluginMeta {
|
|
9
|
-
apiMode: 'openapi' | 'simple';
|
|
10
|
-
enableSettings: boolean;
|
|
11
|
-
manifestConfig?: LobeChatPluginManifest;
|
|
12
|
-
manifestMode: 'local' | 'url';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface PluginStoreState {
|
|
16
|
-
devPluginList: DevPlugin[];
|
|
17
|
-
manifestPrepared: boolean;
|
|
18
|
-
newDevPlugin: Partial<DevPlugin>;
|
|
19
|
-
pluginList: LobeChatPluginMeta[];
|
|
20
|
-
pluginManifestLoading: PluginManifestLoadingState;
|
|
21
|
-
pluginManifestMap: PluginManifestMap;
|
|
22
|
-
pluginsSettings: PluginsSettings;
|
|
23
|
-
}
|
|
24
|
-
export const defaultDevPlugin: Partial<DevPlugin> = {
|
|
25
|
-
apiMode: 'simple',
|
|
26
|
-
enableSettings: false,
|
|
27
|
-
manifestMode: 'url',
|
|
28
|
-
};
|
|
4
|
+
export type PluginStoreState = PluginState & CustomPluginState;
|
|
29
5
|
|
|
30
6
|
export const initialState: PluginStoreState = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
newDevPlugin: defaultDevPlugin,
|
|
34
|
-
pluginList: [],
|
|
35
|
-
pluginManifestLoading: {},
|
|
36
|
-
pluginManifestMap: {},
|
|
37
|
-
pluginsSettings: {},
|
|
7
|
+
...initialPluginState,
|
|
8
|
+
...initialCustomPluginState,
|
|
38
9
|
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { uniqBy } from 'lodash-es';
|
|
2
|
+
import { ChatCompletionFunctions } from 'openai-edge/types/api';
|
|
3
|
+
|
|
1
4
|
import { PLUGIN_SCHEMA_SEPARATOR } from '@/const/plugin';
|
|
2
5
|
import { pluginHelpers } from '@/store/plugin/helpers';
|
|
3
6
|
|
|
@@ -5,12 +8,12 @@ import { PluginStoreState } from './initialState';
|
|
|
5
8
|
|
|
6
9
|
const enabledSchema =
|
|
7
10
|
(enabledPlugins: string[] = []) =>
|
|
8
|
-
(s: PluginStoreState) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// 如果不存在 enabledPlugins,那么全部不启用
|
|
12
|
-
if (!enabledPlugins) return false;
|
|
11
|
+
(s: PluginStoreState): ChatCompletionFunctions[] => {
|
|
12
|
+
// 如果不存在 enabledPlugins,那么全部不启用
|
|
13
|
+
if (!enabledPlugins) return [];
|
|
13
14
|
|
|
15
|
+
const list = Object.values(s.pluginManifestMap)
|
|
16
|
+
.filter((p) => {
|
|
14
17
|
// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
|
|
15
18
|
return enabledPlugins.includes(p.identifier);
|
|
16
19
|
})
|
|
@@ -21,15 +24,17 @@ const enabledSchema =
|
|
|
21
24
|
name: manifest.identifier + PLUGIN_SCHEMA_SEPARATOR + m.name,
|
|
22
25
|
})),
|
|
23
26
|
);
|
|
27
|
+
|
|
28
|
+
return uniqBy(list, 'name');
|
|
24
29
|
};
|
|
25
30
|
|
|
26
|
-
const pluginList = (s: PluginStoreState) => [...s.pluginList, ...s.
|
|
31
|
+
const pluginList = (s: PluginStoreState) => [...s.pluginList, ...s.customPluginList];
|
|
27
32
|
|
|
28
33
|
const getPluginMetaById = (id: string) => (s: PluginStoreState) =>
|
|
29
34
|
pluginHelpers.getPluginFormList(pluginList(s), id);
|
|
30
35
|
|
|
31
36
|
const getDevPluginById = (id: string) => (s: PluginStoreState) =>
|
|
32
|
-
s.
|
|
37
|
+
s.customPluginList.find((i) => i.identifier === id);
|
|
33
38
|
|
|
34
39
|
const getPluginManifestById = (id: string) => (s: PluginStoreState) => s.pluginManifestMap[id];
|
|
35
40
|
const getPluginSettingsById = (id: string) => (s: PluginStoreState) => s.pluginsSettings[id];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { merge } from 'lodash-es';
|
|
2
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
3
|
+
|
|
4
|
+
import { CustomPlugin } from '@/types/plugin';
|
|
5
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
6
|
+
|
|
7
|
+
import { PluginStore } from '../../store';
|
|
8
|
+
import { defaultCustomPlugin } from './initialState';
|
|
9
|
+
import { CustomPluginListDispatch, devPluginListReducer } from './reducers/customPluginList';
|
|
10
|
+
|
|
11
|
+
const t = setNamespace('customPlugin');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 代理行为接口
|
|
15
|
+
*/
|
|
16
|
+
export interface CustomPluginAction {
|
|
17
|
+
deleteCustomPlugin: (id: string) => void;
|
|
18
|
+
dispatchCustomPluginList: (payload: CustomPluginListDispatch) => void;
|
|
19
|
+
saveToCustomPluginList: (value: CustomPlugin) => void;
|
|
20
|
+
updateCustomPlugin: (id: string, value: CustomPlugin) => void;
|
|
21
|
+
updateNewCustomPlugin: (value: Partial<CustomPlugin>) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const createCustomPluginSlice: StateCreator<
|
|
25
|
+
PluginStore,
|
|
26
|
+
[['zustand/devtools', never]],
|
|
27
|
+
[],
|
|
28
|
+
CustomPluginAction
|
|
29
|
+
> = (set, get) => ({
|
|
30
|
+
deleteCustomPlugin: (id) => {
|
|
31
|
+
const { dispatchCustomPluginList, dispatchPluginManifest, deletePluginSettings } = get();
|
|
32
|
+
// 1.删除插件项
|
|
33
|
+
dispatchCustomPluginList({ id, type: 'deleteItem' });
|
|
34
|
+
// 2.删除本地 manifest 记录
|
|
35
|
+
dispatchPluginManifest({ id, type: 'deleteManifest' });
|
|
36
|
+
// 3.删除插件配置
|
|
37
|
+
deletePluginSettings(id);
|
|
38
|
+
},
|
|
39
|
+
dispatchCustomPluginList: (payload) => {
|
|
40
|
+
const { customPluginList } = get();
|
|
41
|
+
|
|
42
|
+
const nextList = devPluginListReducer(customPluginList, payload);
|
|
43
|
+
set({ customPluginList: nextList }, false, t('dispatchCustomPluginList', payload));
|
|
44
|
+
},
|
|
45
|
+
saveToCustomPluginList: (value) => {
|
|
46
|
+
get().dispatchCustomPluginList({ plugin: value, type: 'addItem' });
|
|
47
|
+
set({ newCustomPlugin: defaultCustomPlugin }, false, t('saveToCustomPluginList'));
|
|
48
|
+
},
|
|
49
|
+
updateCustomPlugin: (id, value) => {
|
|
50
|
+
const { dispatchCustomPluginList, fetchPluginManifest } = get();
|
|
51
|
+
// 1. 更新 list 项信息
|
|
52
|
+
dispatchCustomPluginList({ id, plugin: value, type: 'updateItem' });
|
|
53
|
+
// 2. 更新 重新拉取 manifest
|
|
54
|
+
fetchPluginManifest(id);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
updateNewCustomPlugin: (newCustomPlugin) => {
|
|
58
|
+
set(
|
|
59
|
+
{ newCustomPlugin: merge({}, get().newCustomPlugin, newCustomPlugin) },
|
|
60
|
+
false,
|
|
61
|
+
t('updateNewDevPlugin'),
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CustomPlugin } from '@/types/plugin';
|
|
2
|
+
|
|
3
|
+
export interface CustomPluginState {
|
|
4
|
+
customPluginList: CustomPlugin[];
|
|
5
|
+
newCustomPlugin: Partial<CustomPlugin>;
|
|
6
|
+
}
|
|
7
|
+
export const defaultCustomPlugin: Partial<CustomPlugin> = {
|
|
8
|
+
apiMode: 'simple',
|
|
9
|
+
enableSettings: false,
|
|
10
|
+
manifestMode: 'url',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const initialCustomPluginState: CustomPluginState = {
|
|
14
|
+
customPluginList: [],
|
|
15
|
+
newCustomPlugin: defaultCustomPlugin,
|
|
16
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { produce } from 'immer';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CustomPlugin } from '@/types/plugin';
|
|
4
4
|
|
|
5
|
-
export type DevListState =
|
|
5
|
+
export type DevListState = CustomPlugin[];
|
|
6
6
|
|
|
7
7
|
export type AddPluginAction = {
|
|
8
|
-
plugin:
|
|
8
|
+
plugin: CustomPlugin;
|
|
9
9
|
type: 'addItem';
|
|
10
10
|
};
|
|
11
11
|
|
|
@@ -16,15 +16,15 @@ export type DeletePluginAction = {
|
|
|
16
16
|
|
|
17
17
|
export type UpdatePluginAction = {
|
|
18
18
|
id: string;
|
|
19
|
-
plugin:
|
|
19
|
+
plugin: CustomPlugin;
|
|
20
20
|
type: 'updateItem';
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export type
|
|
23
|
+
export type CustomPluginListDispatch = AddPluginAction | DeletePluginAction | UpdatePluginAction;
|
|
24
24
|
|
|
25
25
|
export const devPluginListReducer = (
|
|
26
26
|
state: DevListState,
|
|
27
|
-
payload:
|
|
27
|
+
payload: CustomPluginListDispatch,
|
|
28
28
|
): DevListState => {
|
|
29
29
|
switch (payload.type) {
|
|
30
30
|
case 'addItem': {
|
|
@@ -14,10 +14,8 @@ import { pluginSelectors } from '@/store/plugin/selectors';
|
|
|
14
14
|
import { LobeSessions } from '@/types/session';
|
|
15
15
|
import { setNamespace } from '@/utils/storeDebug';
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
import { DevListDispatch, devPluginListReducer } from './reducers/devPluginList';
|
|
17
|
+
import { PluginStore } from '../../store';
|
|
19
18
|
import { PluginDispatch, pluginManifestReducer } from './reducers/manifest';
|
|
20
|
-
import { PluginStore } from './store';
|
|
21
19
|
|
|
22
20
|
const t = setNamespace('plugin');
|
|
23
21
|
|
|
@@ -26,12 +24,10 @@ const t = setNamespace('plugin');
|
|
|
26
24
|
*/
|
|
27
25
|
export interface PluginAction {
|
|
28
26
|
checkLocalEnabledPlugins: (sessions: LobeSessions) => void;
|
|
29
|
-
|
|
27
|
+
deletePluginSettings: (id: string) => void;
|
|
30
28
|
dispatchPluginManifest: (payload: PluginDispatch) => void;
|
|
31
|
-
fetchPluginManifest: (
|
|
32
|
-
saveToDevList: (value: DevPlugin) => void;
|
|
29
|
+
fetchPluginManifest: (identifier: string) => Promise<void>;
|
|
33
30
|
updateManifestLoadingState: (key: string, value: boolean | undefined) => void;
|
|
34
|
-
updateNewDevPlugin: (value: Partial<DevPlugin>) => void;
|
|
35
31
|
updatePluginSettings: <T>(id: string, settings: Partial<T>) => void;
|
|
36
32
|
useFetchPluginList: () => SWRResponse<LobeChatPluginsMarketIndex>;
|
|
37
33
|
}
|
|
@@ -56,15 +52,18 @@ export const createPluginSlice: StateCreator<
|
|
|
56
52
|
|
|
57
53
|
const plugins = uniq(enabledPlugins);
|
|
58
54
|
|
|
59
|
-
await Promise.all(plugins.map((
|
|
55
|
+
await Promise.all(plugins.map((identifier) => fetchPluginManifest(identifier)));
|
|
60
56
|
|
|
61
57
|
set({ manifestPrepared: true }, false, t('checkLocalEnabledPlugins'));
|
|
62
58
|
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
deletePluginSettings: (id) => {
|
|
60
|
+
set(
|
|
61
|
+
produce((draft) => {
|
|
62
|
+
draft.pluginsSettings[id] = undefined;
|
|
63
|
+
}),
|
|
64
|
+
false,
|
|
65
|
+
t('deletePluginSettings'),
|
|
66
|
+
);
|
|
68
67
|
},
|
|
69
68
|
dispatchPluginManifest: (payload) => {
|
|
70
69
|
const { pluginManifestMap } = get();
|
|
@@ -72,6 +71,7 @@ export const createPluginSlice: StateCreator<
|
|
|
72
71
|
|
|
73
72
|
set({ pluginManifestMap: nextManifest }, false, t('dispatchPluginManifest', payload));
|
|
74
73
|
},
|
|
74
|
+
|
|
75
75
|
fetchPluginManifest: async (name) => {
|
|
76
76
|
const plugin = pluginSelectors.getPluginMetaById(name)(get());
|
|
77
77
|
// 1. 校验文件
|
|
@@ -113,11 +113,6 @@ export const createPluginSlice: StateCreator<
|
|
|
113
113
|
// 4. 存储 manifest 信息
|
|
114
114
|
get().dispatchPluginManifest({ id: plugin.identifier, plugin: data, type: 'addManifest' });
|
|
115
115
|
},
|
|
116
|
-
saveToDevList: (value) => {
|
|
117
|
-
get().dispatchDevPluginList({ plugin: value, type: 'addItem' });
|
|
118
|
-
set({ newDevPlugin: defaultDevPlugin }, false, t('saveToDevList'));
|
|
119
|
-
},
|
|
120
|
-
|
|
121
116
|
updateManifestLoadingState: (key, value) => {
|
|
122
117
|
set(
|
|
123
118
|
produce((draft) => {
|
|
@@ -127,13 +122,6 @@ export const createPluginSlice: StateCreator<
|
|
|
127
122
|
t('updateManifestLoadingState'),
|
|
128
123
|
);
|
|
129
124
|
},
|
|
130
|
-
updateNewDevPlugin: (newDevPlugin) => {
|
|
131
|
-
set(
|
|
132
|
-
{ newDevPlugin: merge({}, get().newDevPlugin, newDevPlugin) },
|
|
133
|
-
false,
|
|
134
|
-
t('updateNewDevPlugin'),
|
|
135
|
-
);
|
|
136
|
-
},
|
|
137
125
|
updatePluginSettings: (id, settings) => {
|
|
138
126
|
set(
|
|
139
127
|
produce((draft) => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
|
|
3
|
+
import { PluginManifestMap } from '@/types/plugin';
|
|
4
|
+
|
|
5
|
+
export type PluginManifestLoadingState = Record<string, boolean>;
|
|
6
|
+
export type PluginsSettings = Record<string, any>;
|
|
7
|
+
|
|
8
|
+
export interface PluginState {
|
|
9
|
+
manifestPrepared: boolean;
|
|
10
|
+
pluginList: LobeChatPluginMeta[];
|
|
11
|
+
pluginManifestLoading: PluginManifestLoadingState;
|
|
12
|
+
pluginManifestMap: PluginManifestMap;
|
|
13
|
+
pluginsSettings: PluginsSettings;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const initialPluginState: PluginState = {
|
|
17
|
+
manifestPrepared: false,
|
|
18
|
+
pluginList: [],
|
|
19
|
+
pluginManifestLoading: {},
|
|
20
|
+
pluginManifestMap: {},
|
|
21
|
+
pluginsSettings: {},
|
|
22
|
+
};
|