@lobehub/chat 0.63.3 → 0.64.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/CHANGELOG.md +37 -0
- package/package.json +1 -1
- package/src/components/EmojiPicker/index.tsx +13 -5
- package/src/features/AgentSetting/AgentMeta/index.tsx +1 -1
- package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +72 -0
- package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +160 -0
- package/src/features/AgentSetting/AgentPlugin/PluginSettings.tsx +76 -0
- package/src/features/AgentSetting/AgentPlugin/index.tsx +8 -143
- package/src/features/PluginDevModal/ManifestForm.tsx +96 -0
- package/src/features/PluginDevModal/MetaForm.tsx +90 -0
- package/src/features/PluginDevModal/PluginPreview.tsx +36 -0
- package/src/features/PluginDevModal/index.tsx +153 -0
- package/src/locales/default/error.ts +2 -0
- package/src/locales/default/plugin.ts +79 -3
- package/src/locales/default/setting.ts +3 -0
- package/src/pages/chat/features/Conversation/ChatList/Error/index.tsx +2 -1
- package/src/pages/chat/features/Conversation/ChatList/Plugins/FunctionCall.tsx +7 -4
- package/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx +27 -33
- package/src/services/plugin.ts +6 -3
- package/src/store/plugin/action.ts +23 -2
- package/src/store/plugin/initialState.ts +17 -1
- package/src/store/plugin/reducers/devPluginList.ts +56 -0
- package/src/store/plugin/selectors.ts +9 -2
- package/src/store/plugin/store.ts +5 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Form, FormItemProps, Input } from '@lobehub/ui';
|
|
2
|
+
import { FormInstance } from 'antd';
|
|
3
|
+
import { memo, useEffect } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import EmojiPicker from '@/components/EmojiPicker';
|
|
7
|
+
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
8
|
+
|
|
9
|
+
const MetaForm = memo<{ form: FormInstance }>(({ form }) => {
|
|
10
|
+
const { t } = useTranslation('plugin');
|
|
11
|
+
const [plugins] = usePluginStore((s) => [pluginSelectors.pluginList(s).map((i) => i.identifier)]);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (usePluginStore.getState().newDevPlugin) {
|
|
15
|
+
form.setFieldsValue(usePluginStore.getState().newDevPlugin);
|
|
16
|
+
}
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
const configItem: FormItemProps[] = [
|
|
20
|
+
{
|
|
21
|
+
children: <Input placeholder={'searchEngine'} />,
|
|
22
|
+
desc: t('dev.meta.identifier.desc'),
|
|
23
|
+
label: t('dev.meta.identifier.label'),
|
|
24
|
+
name: 'identifier',
|
|
25
|
+
rules: [
|
|
26
|
+
{ required: true },
|
|
27
|
+
{
|
|
28
|
+
message: t('dev.meta.identifier.pattenErrorMessage'),
|
|
29
|
+
pattern: /^[\w-]+$/,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
message: t('dev.meta.identifier.errorDuplicate'),
|
|
33
|
+
validator: async (_, value) => {
|
|
34
|
+
if (plugins.includes(value)) {
|
|
35
|
+
throw new Error('Duplicate');
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
children: <Input placeholder={t('dev.meta.title.placeholder')} />,
|
|
43
|
+
desc: t('dev.meta.title.desc'),
|
|
44
|
+
label: t('dev.meta.title.label'),
|
|
45
|
+
name: ['meta', 'title'],
|
|
46
|
+
rules: [{ required: true }],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
children: <Input placeholder={t('dev.meta.description.placeholder')} />,
|
|
50
|
+
desc: t('dev.meta.description.desc'),
|
|
51
|
+
label: t('dev.meta.description.label'),
|
|
52
|
+
name: ['meta', 'description'],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
children: <Input placeholder={'LobeHub'} />,
|
|
56
|
+
desc: t('dev.meta.author.desc'),
|
|
57
|
+
label: t('dev.meta.author.label'),
|
|
58
|
+
name: 'author',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
children: <Input placeholder={'https://www.lobehub.com'} />,
|
|
62
|
+
desc: t('dev.meta.homepage.desc'),
|
|
63
|
+
label: t('dev.meta.homepage.label'),
|
|
64
|
+
name: 'homepage',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
children: <EmojiPicker defaultAvatar={'🧩'} />,
|
|
68
|
+
desc: t('dev.meta.avatar.desc'),
|
|
69
|
+
label: t('dev.meta.avatar.label'),
|
|
70
|
+
name: ['meta', 'avatar'],
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Form
|
|
76
|
+
form={form}
|
|
77
|
+
items={[
|
|
78
|
+
{
|
|
79
|
+
children: configItem,
|
|
80
|
+
title: t('dev.metaConfig'),
|
|
81
|
+
},
|
|
82
|
+
]}
|
|
83
|
+
validateMessages={{
|
|
84
|
+
required: () => t('dev.meta.formFieldRequired'),
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export default MetaForm;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Avatar, Form } from '@lobehub/ui';
|
|
2
|
+
import { Card, Switch, Tag } from 'antd';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { Flexbox } from 'react-layout-kit';
|
|
6
|
+
|
|
7
|
+
import { pluginHelpers, usePluginStore } from '@/store/plugin';
|
|
8
|
+
|
|
9
|
+
const PluginPreview = memo(() => {
|
|
10
|
+
const { t } = useTranslation('plugin');
|
|
11
|
+
const meta = usePluginStore((s) => s.newDevPlugin);
|
|
12
|
+
|
|
13
|
+
const items = {
|
|
14
|
+
avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta?.meta) || '🧩'} />,
|
|
15
|
+
children: <Switch disabled />,
|
|
16
|
+
desc: pluginHelpers.getPluginDesc(meta?.meta),
|
|
17
|
+
label: (
|
|
18
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
19
|
+
{pluginHelpers.getPluginTitle(meta?.meta) ?? t('dev.preview.title')}
|
|
20
|
+
<Tag bordered={false} color={'gold'}>
|
|
21
|
+
{t('list.item.local.title', { ns: 'plugin' })}
|
|
22
|
+
</Tag>
|
|
23
|
+
</Flexbox>
|
|
24
|
+
),
|
|
25
|
+
minWidth: undefined,
|
|
26
|
+
tag: !!meta?.identifier ? meta?.identifier : 'id',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Card size={'small'} title={t('dev.preview.card')}>
|
|
31
|
+
<Form.Item {...items} colon={false} style={{ marginBottom: 0 }} />
|
|
32
|
+
</Card>
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export default PluginPreview;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
|
2
|
+
import { App, Button, ConfigProvider, Form, Modal, Popconfirm } from 'antd';
|
|
3
|
+
import { createStyles, useTheme } from 'antd-style';
|
|
4
|
+
import { LucideBlocks } from 'lucide-react';
|
|
5
|
+
import { lighten } from 'polished';
|
|
6
|
+
import { memo, useEffect } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
import { Flexbox } from 'react-layout-kit';
|
|
9
|
+
|
|
10
|
+
import { DevPlugin } from '@/store/plugin/initialState';
|
|
11
|
+
|
|
12
|
+
import ManifestForm from './ManifestForm';
|
|
13
|
+
import MetaForm from './MetaForm';
|
|
14
|
+
import PluginPreview from './PluginPreview';
|
|
15
|
+
|
|
16
|
+
const useStyles = createStyles(({ css, token, prefixCls }) => ({
|
|
17
|
+
content: css`
|
|
18
|
+
.${prefixCls}-modal-content {
|
|
19
|
+
border: 1px solid ${token.colorSplit};
|
|
20
|
+
border-radius: 12px;
|
|
21
|
+
}
|
|
22
|
+
`,
|
|
23
|
+
root: css`
|
|
24
|
+
backdrop-filter: blur(5px);
|
|
25
|
+
`,
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
interface DevModalProps {
|
|
29
|
+
onDelete?: () => void;
|
|
30
|
+
onOpenChange: (open: boolean) => void;
|
|
31
|
+
onSave?: (value: DevPlugin) => void;
|
|
32
|
+
onValueChange?: (value: Partial<DevPlugin>) => void;
|
|
33
|
+
open?: boolean;
|
|
34
|
+
showDelete?: boolean;
|
|
35
|
+
value?: DevPlugin;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const DevModal = memo<DevModalProps>(
|
|
39
|
+
({ open, showDelete, value, onValueChange, onSave, onOpenChange, onDelete }) => {
|
|
40
|
+
const { t } = useTranslation('plugin');
|
|
41
|
+
|
|
42
|
+
const [form] = Form.useForm();
|
|
43
|
+
const theme = useTheme();
|
|
44
|
+
|
|
45
|
+
const { styles } = useStyles();
|
|
46
|
+
|
|
47
|
+
const { message } = App.useApp();
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
form.setFieldsValue(value);
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const footer = (
|
|
54
|
+
<Flexbox horizontal justify={'space-between'} style={{ marginTop: 24 }}>
|
|
55
|
+
{showDelete ? (
|
|
56
|
+
<Popconfirm
|
|
57
|
+
okButtonProps={{
|
|
58
|
+
danger: true,
|
|
59
|
+
type: 'primary',
|
|
60
|
+
}}
|
|
61
|
+
onConfirm={() => {
|
|
62
|
+
onDelete?.();
|
|
63
|
+
message.success(t('dev.deleteSuccess'));
|
|
64
|
+
}}
|
|
65
|
+
placement={'topLeft'}
|
|
66
|
+
title={t('dev.confirmDeleteDevPlugin')}
|
|
67
|
+
>
|
|
68
|
+
<Button danger>{t('delete', { ns: 'common' })}</Button>
|
|
69
|
+
</Popconfirm>
|
|
70
|
+
) : (
|
|
71
|
+
<div />
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
<Flexbox horizontal>
|
|
75
|
+
<Button
|
|
76
|
+
onClick={() => {
|
|
77
|
+
onOpenChange(false);
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{t('cancel', { ns: 'common' })}
|
|
81
|
+
</Button>
|
|
82
|
+
<Button
|
|
83
|
+
onClick={() => {
|
|
84
|
+
form.submit();
|
|
85
|
+
}}
|
|
86
|
+
type={'primary'}
|
|
87
|
+
>
|
|
88
|
+
{t('dev.save')}
|
|
89
|
+
</Button>
|
|
90
|
+
</Flexbox>
|
|
91
|
+
</Flexbox>
|
|
92
|
+
);
|
|
93
|
+
return (
|
|
94
|
+
<Form.Provider
|
|
95
|
+
onFormChange={() => {
|
|
96
|
+
onValueChange?.(form.getFieldsValue());
|
|
97
|
+
}}
|
|
98
|
+
onFormFinish={(_, info) => {
|
|
99
|
+
onSave?.(info.values as DevPlugin);
|
|
100
|
+
message.success(t('dev.saveSuccess'));
|
|
101
|
+
onOpenChange(false);
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<ConfigProvider
|
|
105
|
+
theme={{
|
|
106
|
+
token: {
|
|
107
|
+
colorBgElevated: lighten(0.005, theme.colorBgContainer),
|
|
108
|
+
},
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<Modal
|
|
112
|
+
centered
|
|
113
|
+
closable
|
|
114
|
+
footer={footer}
|
|
115
|
+
maskClosable
|
|
116
|
+
okText={t('dev.save')}
|
|
117
|
+
onCancel={() => {
|
|
118
|
+
onOpenChange(false);
|
|
119
|
+
}}
|
|
120
|
+
onOk={() => {
|
|
121
|
+
form.submit();
|
|
122
|
+
}}
|
|
123
|
+
open={open}
|
|
124
|
+
style={{ height: 750 }}
|
|
125
|
+
title={
|
|
126
|
+
<Flexbox gap={8} horizontal>
|
|
127
|
+
<Icon icon={LucideBlocks} />
|
|
128
|
+
{t('dev.title')}
|
|
129
|
+
</Flexbox>
|
|
130
|
+
}
|
|
131
|
+
width={700}
|
|
132
|
+
wrapClassName={styles.root}
|
|
133
|
+
>
|
|
134
|
+
<Flexbox gap={12}>
|
|
135
|
+
{t('dev.modalDesc')}
|
|
136
|
+
{/*<Tabs*/}
|
|
137
|
+
{/* items={[*/}
|
|
138
|
+
{/* { children: <MetaForm />, key: 'meta', label: t('dev.tabs.meta') },*/}
|
|
139
|
+
{/* { children: <ManifestForm />, key: 'manifest', label: t('dev.tabs.manifest') },*/}
|
|
140
|
+
{/* ]}*/}
|
|
141
|
+
{/*/>*/}
|
|
142
|
+
<PluginPreview />
|
|
143
|
+
<ManifestForm form={form} mode={'url'} />
|
|
144
|
+
<MetaForm form={form} />
|
|
145
|
+
</Flexbox>
|
|
146
|
+
</Modal>
|
|
147
|
+
</ConfigProvider>
|
|
148
|
+
</Form.Provider>
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
export default DevModal;
|
|
@@ -37,6 +37,8 @@ export default {
|
|
|
37
37
|
'很抱歉,该插件请求的入参校验未通过,请检查入参与 Api 描述信息是否匹配',
|
|
38
38
|
[PluginErrorType.PluginSettingsInvalid]:
|
|
39
39
|
'该插件需要正确配置后才可以使用,请检查你的配置是否正确',
|
|
40
|
+
[PluginErrorType.PluginServerError]:
|
|
41
|
+
'插件服务端请求返回出错,请检查根据下面的报错信息检查你的插件描述文件、插件配置或服务端实现',
|
|
40
42
|
|
|
41
43
|
[ChatErrorType.InvalidAccessCode]:
|
|
42
44
|
'密码不正确或为空,请输入正确的访问密码,或者添加自定义 OpenAI API Key',
|
|
@@ -4,15 +4,91 @@ export default {
|
|
|
4
4
|
function_call: '函数调用',
|
|
5
5
|
response: '返回结果',
|
|
6
6
|
},
|
|
7
|
+
dev: {
|
|
8
|
+
confirmDeleteDevPlugin: '即将删除该本地插件,删除后将无法找回,是否删除该插件?',
|
|
9
|
+
deleteSuccess: '插件删除成功',
|
|
10
|
+
manifest: {
|
|
11
|
+
identifier: {
|
|
12
|
+
desc: '插件的唯一标识',
|
|
13
|
+
label: '标识符',
|
|
14
|
+
},
|
|
15
|
+
mode: {
|
|
16
|
+
'local': '可视化配置',
|
|
17
|
+
'local-tooltip': '暂时不支持可视化配置',
|
|
18
|
+
'url': '在线链接',
|
|
19
|
+
},
|
|
20
|
+
name: {
|
|
21
|
+
desc: '插件标题',
|
|
22
|
+
label: '标题',
|
|
23
|
+
placeholder: '搜索引擎',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
meta: {
|
|
27
|
+
author: {
|
|
28
|
+
desc: '插件的作者',
|
|
29
|
+
label: '作者',
|
|
30
|
+
},
|
|
31
|
+
avatar: {
|
|
32
|
+
desc: '插件的图标,可以使用 Emoji,也可以使用 URL',
|
|
33
|
+
label: '图标',
|
|
34
|
+
},
|
|
35
|
+
description: {
|
|
36
|
+
desc: '插件描述',
|
|
37
|
+
label: '描述',
|
|
38
|
+
placeholder: '查询搜索引擎获取信息',
|
|
39
|
+
},
|
|
40
|
+
formFieldRequired: '该字段为必填项',
|
|
41
|
+
homepage: {
|
|
42
|
+
desc: '插件的首页',
|
|
43
|
+
label: '首页',
|
|
44
|
+
},
|
|
45
|
+
identifier: {
|
|
46
|
+
desc: '插件的唯一标识,仅支持英文字符、数字、破折号 - 、下划线 _',
|
|
47
|
+
errorDuplicate: '标识符和已有插件重复,请修改标识符',
|
|
48
|
+
label: '标识符',
|
|
49
|
+
pattenErrorMessage: '只能输入英文字符、数字 、- 和_ 这两个符号',
|
|
50
|
+
},
|
|
51
|
+
manifest: {
|
|
52
|
+
desc: 'LobeChat 将会通过该链接安装插件',
|
|
53
|
+
invalid: '输入的 manifest 链接无效,或 manifest 不符合规范',
|
|
54
|
+
label: '插件描述文件 Url 地址',
|
|
55
|
+
urlError: '请输入一个有效的网址',
|
|
56
|
+
},
|
|
57
|
+
title: {
|
|
58
|
+
desc: '插件标题',
|
|
59
|
+
label: '标题',
|
|
60
|
+
placeholder: '搜索引擎',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
metaConfig: '插件元信息配置',
|
|
64
|
+
modalDesc: '添加自定义插件后,可用于插件开发验证,也可直接在会话中使用。插件开发文档请参考',
|
|
65
|
+
preview: {
|
|
66
|
+
card: '预览插件展示效果',
|
|
67
|
+
desc: '预览插件描述',
|
|
68
|
+
title: '插件名称预览',
|
|
69
|
+
},
|
|
70
|
+
save: '保存',
|
|
71
|
+
saveSuccess: '插件设置保存成功',
|
|
72
|
+
tabs: {
|
|
73
|
+
manifest: '功能描述清单 (Manifest)',
|
|
74
|
+
meta: '插件元信息',
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
title: '添加自定义插件',
|
|
78
|
+
},
|
|
79
|
+
list: {
|
|
80
|
+
item: {
|
|
81
|
+
'local.config': '配置',
|
|
82
|
+
'local.title': '本地',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
|
|
7
86
|
loading: {
|
|
8
87
|
content: '数据获取中...',
|
|
9
88
|
plugin: '插件运行中...',
|
|
10
89
|
},
|
|
11
90
|
pluginList: '插件列表',
|
|
12
91
|
plugins: {
|
|
13
|
-
realtimeWeather: '实时天气预报',
|
|
14
|
-
searchEngine: '搜索引擎',
|
|
15
92
|
unknown: '插件检测中...',
|
|
16
|
-
websiteCrawler: '网页内容提取',
|
|
17
93
|
},
|
|
18
94
|
};
|
|
@@ -18,7 +18,8 @@ export const renderErrorMessage: RenderErrorMessage = (error, message: ChatMessa
|
|
|
18
18
|
case PluginErrorType.PluginManifestInvalid:
|
|
19
19
|
case PluginErrorType.PluginManifestNotFound:
|
|
20
20
|
case PluginErrorType.PluginApiNotFound:
|
|
21
|
-
case PluginErrorType.PluginApiParamsError:
|
|
21
|
+
case PluginErrorType.PluginApiParamsError:
|
|
22
|
+
case PluginErrorType.PluginServerError: {
|
|
22
23
|
return <PluginError content={(error as any).body} id={message.id} />;
|
|
23
24
|
}
|
|
24
25
|
case PluginErrorType.PluginSettingsInvalid: {
|
|
@@ -6,7 +6,7 @@ import { memo, useState } from 'react';
|
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { Flexbox } from 'react-layout-kit';
|
|
8
8
|
|
|
9
|
-
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
9
|
+
import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
10
10
|
|
|
11
11
|
import PluginResult from './PluginResultRender';
|
|
12
12
|
import { useStyles } from './style';
|
|
@@ -27,8 +27,11 @@ const FunctionCall = memo<FunctionCallProps>(
|
|
|
27
27
|
|
|
28
28
|
const item = usePluginStore(pluginSelectors.getPluginMetaById(id));
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
|
|
30
|
+
const pluginAvatar = pluginHelpers.getPluginAvatar(item?.meta);
|
|
31
|
+
const pluginTitle = pluginHelpers.getPluginTitle(item?.meta);
|
|
32
|
+
|
|
33
|
+
const avatar = pluginAvatar ? (
|
|
34
|
+
<Avatar avatar={pluginAvatar} size={32} />
|
|
32
35
|
) : (
|
|
33
36
|
<Icon icon={LucideToyBrick} />
|
|
34
37
|
);
|
|
@@ -54,7 +57,7 @@ const FunctionCall = memo<FunctionCallProps>(
|
|
|
54
57
|
) : (
|
|
55
58
|
avatar
|
|
56
59
|
)}
|
|
57
|
-
{t(
|
|
60
|
+
{pluginTitle ?? t('plugins.unknown')}
|
|
58
61
|
<Icon icon={open ? LucideChevronUp : LucideChevronDown} />
|
|
59
62
|
</Flexbox>
|
|
60
63
|
{open && (
|
|
@@ -12,41 +12,35 @@ export interface FunctionMessageProps extends ChatMessage {
|
|
|
12
12
|
loading?: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const PluginMessage = memo<FunctionMessageProps>(
|
|
16
|
-
(
|
|
17
|
-
content,
|
|
18
|
-
// loading,
|
|
19
|
-
name,
|
|
20
|
-
}) => {
|
|
21
|
-
const { t } = useTranslation('plugin');
|
|
22
|
-
|
|
23
|
-
const manifest = usePluginStore((s) => s.pluginManifestMap[name || '']);
|
|
24
|
-
let isJSON = true;
|
|
25
|
-
try {
|
|
26
|
-
JSON.parse(content);
|
|
27
|
-
} catch {
|
|
28
|
-
isJSON = false;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// if (!loading)
|
|
32
|
-
|
|
33
|
-
if (!isJSON) {
|
|
34
|
-
return (
|
|
35
|
-
<Flexbox gap={8} horizontal>
|
|
36
|
-
<div>
|
|
37
|
-
<Loading3QuartersOutlined spin />
|
|
38
|
-
</div>
|
|
39
|
-
{t('loading.content')}
|
|
40
|
-
</Flexbox>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!manifest?.ui?.url) return;
|
|
15
|
+
const PluginMessage = memo<FunctionMessageProps>(({ content, name }) => {
|
|
16
|
+
const { t } = useTranslation('plugin');
|
|
45
17
|
|
|
18
|
+
const manifest = usePluginStore((s) => s.pluginManifestMap[name || '']);
|
|
19
|
+
let isJSON = true;
|
|
20
|
+
try {
|
|
21
|
+
JSON.parse(content);
|
|
22
|
+
} catch {
|
|
23
|
+
isJSON = false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// if (!loading)
|
|
27
|
+
|
|
28
|
+
if (!isJSON) {
|
|
46
29
|
return (
|
|
47
|
-
<
|
|
30
|
+
<Flexbox gap={8} horizontal>
|
|
31
|
+
<div>
|
|
32
|
+
<Loading3QuartersOutlined spin />
|
|
33
|
+
</div>
|
|
34
|
+
{t('loading.content')}
|
|
35
|
+
</Flexbox>
|
|
48
36
|
);
|
|
49
|
-
}
|
|
50
|
-
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!manifest?.ui?.url) return;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<CustomRender content={JSON.parse(content)} name={name || 'unknown'} url={manifest.ui?.url} />
|
|
43
|
+
);
|
|
44
|
+
});
|
|
51
45
|
|
|
52
46
|
export default PluginMessage;
|
package/src/services/plugin.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PluginRequestPayload, createHeadersWithPluginSettings } from '@lobehub/chat-plugin-sdk';
|
|
2
2
|
|
|
3
|
-
import { usePluginStore } from '@/store/plugin';
|
|
3
|
+
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
4
4
|
import { getMessageError } from '@/utils/fetch';
|
|
5
5
|
|
|
6
6
|
import { URLS } from './url';
|
|
@@ -17,10 +17,13 @@ export const fetchPlugin = async (
|
|
|
17
17
|
params: PluginRequestPayload,
|
|
18
18
|
options?: FetchChatModelOptions,
|
|
19
19
|
) => {
|
|
20
|
-
const
|
|
20
|
+
const s = usePluginStore.getState();
|
|
21
|
+
|
|
22
|
+
const settings = pluginSelectors.getPluginSettingsById(params.identifier)(s);
|
|
23
|
+
const manifest = pluginSelectors.getPluginManifestById(params.identifier)(s);
|
|
21
24
|
|
|
22
25
|
const res = await fetch(URLS.plugins, {
|
|
23
|
-
body: JSON.stringify(params),
|
|
26
|
+
body: JSON.stringify({ ...params, manifest }),
|
|
24
27
|
headers: createHeadersWithPluginSettings(settings),
|
|
25
28
|
method: 'POST',
|
|
26
29
|
signal: options?.signal,
|
|
@@ -14,6 +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 { DevPlugin, defaultDevPlugin } from './initialState';
|
|
18
|
+
import { DevListDispatch, devPluginListReducer } from './reducers/devPluginList';
|
|
17
19
|
import { PluginDispatch, pluginManifestReducer } from './reducers/manifest';
|
|
18
20
|
import { PluginStore } from './store';
|
|
19
21
|
|
|
@@ -24,9 +26,12 @@ const t = setNamespace('plugin');
|
|
|
24
26
|
*/
|
|
25
27
|
export interface PluginAction {
|
|
26
28
|
checkLocalEnabledPlugins: (sessions: LobeSessions) => void;
|
|
29
|
+
dispatchDevPluginList: (payload: DevListDispatch) => void;
|
|
27
30
|
dispatchPluginManifest: (payload: PluginDispatch) => void;
|
|
28
31
|
fetchPluginManifest: (name: string) => Promise<void>;
|
|
32
|
+
saveToDevList: (value: DevPlugin) => void;
|
|
29
33
|
updateManifestLoadingState: (key: string, value: boolean | undefined) => void;
|
|
34
|
+
updateNewDevPlugin: (value: Partial<DevPlugin>) => void;
|
|
30
35
|
updatePluginSettings: <T>(id: string, settings: Partial<T>) => void;
|
|
31
36
|
useFetchPluginList: () => SWRResponse<LobeChatPluginsMarketIndex>;
|
|
32
37
|
}
|
|
@@ -55,13 +60,18 @@ export const createPluginSlice: StateCreator<
|
|
|
55
60
|
|
|
56
61
|
set({ manifestPrepared: true }, false, t('checkLocalEnabledPlugins'));
|
|
57
62
|
},
|
|
63
|
+
dispatchDevPluginList: (payload) => {
|
|
64
|
+
const { devPluginList } = get();
|
|
65
|
+
|
|
66
|
+
const nextList = devPluginListReducer(devPluginList, payload);
|
|
67
|
+
set({ devPluginList: nextList }, false, t('dispatchDevList', payload));
|
|
68
|
+
},
|
|
58
69
|
dispatchPluginManifest: (payload) => {
|
|
59
70
|
const { pluginManifestMap } = get();
|
|
60
71
|
const nextManifest = pluginManifestReducer(pluginManifestMap, payload);
|
|
61
72
|
|
|
62
73
|
set({ pluginManifestMap: nextManifest }, false, t('dispatchPluginManifest', payload));
|
|
63
74
|
},
|
|
64
|
-
|
|
65
75
|
fetchPluginManifest: async (name) => {
|
|
66
76
|
const plugin = pluginSelectors.getPluginMetaById(name)(get());
|
|
67
77
|
// 1. 校验文件
|
|
@@ -103,6 +113,10 @@ export const createPluginSlice: StateCreator<
|
|
|
103
113
|
// 4. 存储 manifest 信息
|
|
104
114
|
get().dispatchPluginManifest({ id: plugin.identifier, plugin: data, type: 'addManifest' });
|
|
105
115
|
},
|
|
116
|
+
saveToDevList: (value) => {
|
|
117
|
+
get().dispatchDevPluginList({ plugin: value, type: 'addItem' });
|
|
118
|
+
set({ newDevPlugin: defaultDevPlugin }, false, t('saveToDevList'));
|
|
119
|
+
},
|
|
106
120
|
|
|
107
121
|
updateManifestLoadingState: (key, value) => {
|
|
108
122
|
set(
|
|
@@ -113,7 +127,13 @@ export const createPluginSlice: StateCreator<
|
|
|
113
127
|
t('updateManifestLoadingState'),
|
|
114
128
|
);
|
|
115
129
|
},
|
|
116
|
-
|
|
130
|
+
updateNewDevPlugin: (newDevPlugin) => {
|
|
131
|
+
set(
|
|
132
|
+
{ newDevPlugin: merge({}, get().newDevPlugin, newDevPlugin) },
|
|
133
|
+
false,
|
|
134
|
+
t('updateNewDevPlugin'),
|
|
135
|
+
);
|
|
136
|
+
},
|
|
117
137
|
updatePluginSettings: (id, settings) => {
|
|
118
138
|
set(
|
|
119
139
|
produce((draft) => {
|
|
@@ -123,6 +143,7 @@ export const createPluginSlice: StateCreator<
|
|
|
123
143
|
t('updatePluginSettings'),
|
|
124
144
|
);
|
|
125
145
|
},
|
|
146
|
+
|
|
126
147
|
useFetchPluginList: () =>
|
|
127
148
|
useSWR<LobeChatPluginsMarketIndex>('fetchPluginList', getPluginList, {
|
|
128
149
|
onSuccess: (pluginMarketIndex) => {
|
|
@@ -1,20 +1,36 @@
|
|
|
1
|
-
import { LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
|
|
1
|
+
import { LobeChatPluginManifest, LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
|
|
2
2
|
|
|
3
3
|
import { PluginManifestMap } from '@/types/plugin';
|
|
4
4
|
|
|
5
5
|
export type PluginManifestLoadingState = Record<string, boolean>;
|
|
6
6
|
export type PluginsSettings = Record<string, any>;
|
|
7
7
|
|
|
8
|
+
export interface DevPlugin extends LobeChatPluginMeta {
|
|
9
|
+
apiMode: 'openapi' | 'simple';
|
|
10
|
+
enableSettings: boolean;
|
|
11
|
+
manifestConfig?: LobeChatPluginManifest;
|
|
12
|
+
manifestMode: 'local' | 'url';
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
export interface PluginStoreState {
|
|
16
|
+
devPluginList: DevPlugin[];
|
|
9
17
|
manifestPrepared: boolean;
|
|
18
|
+
newDevPlugin: Partial<DevPlugin>;
|
|
10
19
|
pluginList: LobeChatPluginMeta[];
|
|
11
20
|
pluginManifestLoading: PluginManifestLoadingState;
|
|
12
21
|
pluginManifestMap: PluginManifestMap;
|
|
13
22
|
pluginsSettings: PluginsSettings;
|
|
14
23
|
}
|
|
24
|
+
export const defaultDevPlugin: Partial<DevPlugin> = {
|
|
25
|
+
apiMode: 'simple',
|
|
26
|
+
enableSettings: false,
|
|
27
|
+
manifestMode: 'url',
|
|
28
|
+
};
|
|
15
29
|
|
|
16
30
|
export const initialState: PluginStoreState = {
|
|
31
|
+
devPluginList: [],
|
|
17
32
|
manifestPrepared: false,
|
|
33
|
+
newDevPlugin: defaultDevPlugin,
|
|
18
34
|
pluginList: [],
|
|
19
35
|
pluginManifestLoading: {},
|
|
20
36
|
pluginManifestMap: {},
|