@lobehub/chat 0.63.2 → 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 +62 -0
- package/package.json +1 -1
- package/src/components/EmojiPicker/index.tsx +13 -5
- package/src/const/url.ts +1 -1
- 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,96 @@
|
|
|
1
|
+
import { pluginManifestSchema } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { Form, FormItemProps, Input, Tooltip } from '@lobehub/ui';
|
|
3
|
+
import { FormInstance, Radio } from 'antd';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
const ManifestForm = memo<{ form: FormInstance; mode: 'url' | 'local' }>(({ form, mode }) => {
|
|
8
|
+
const { t } = useTranslation('plugin');
|
|
9
|
+
|
|
10
|
+
const isUrl = mode === 'url';
|
|
11
|
+
|
|
12
|
+
const configItem: FormItemProps[] = isUrl
|
|
13
|
+
? [
|
|
14
|
+
{
|
|
15
|
+
children: <Input placeholder={'http://localhost/manifest.json'} />,
|
|
16
|
+
desc: t('dev.meta.manifest.desc'),
|
|
17
|
+
hasFeedback: true,
|
|
18
|
+
label: t('dev.meta.manifest.label'),
|
|
19
|
+
name: 'manifest',
|
|
20
|
+
required: true,
|
|
21
|
+
rules: [
|
|
22
|
+
{ required: true },
|
|
23
|
+
{
|
|
24
|
+
message: t('dev.meta.manifest.urlError'),
|
|
25
|
+
pattern: /^https?:\/\/.*/,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
message: t('dev.meta.manifest.invalid'),
|
|
29
|
+
validator: async (_, value) => {
|
|
30
|
+
const res = await fetch(value);
|
|
31
|
+
if (!res.ok) return true;
|
|
32
|
+
|
|
33
|
+
const json = await res.json();
|
|
34
|
+
pluginManifestSchema.parse(json);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
]
|
|
40
|
+
: // TODO: 后续做成本地配置模式
|
|
41
|
+
[
|
|
42
|
+
{
|
|
43
|
+
children: <Input placeholder={'searchEngine'} />,
|
|
44
|
+
desc: t('dev.meta.identifier.desc'),
|
|
45
|
+
label: t('dev.meta.identifier.label'),
|
|
46
|
+
name: 'name',
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
children: <Input placeholder={t('dev.meta.description.placeholder')} />,
|
|
52
|
+
desc: t('dev.meta.description.desc'),
|
|
53
|
+
label: t('dev.meta.description.label'),
|
|
54
|
+
name: 'description',
|
|
55
|
+
required: true,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
children: <Input placeholder={'searchEngine'} />,
|
|
59
|
+
desc: t('dev.meta.identifier.desc'),
|
|
60
|
+
label: t('dev.meta.identifier.label'),
|
|
61
|
+
name: 'identifier',
|
|
62
|
+
required: true,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Form
|
|
68
|
+
form={form}
|
|
69
|
+
items={[
|
|
70
|
+
{
|
|
71
|
+
children: configItem,
|
|
72
|
+
extra: (
|
|
73
|
+
<Radio.Group
|
|
74
|
+
onChange={(v) => {
|
|
75
|
+
form.setFieldValue('manifestMode', v.target.value);
|
|
76
|
+
}}
|
|
77
|
+
size={'small'}
|
|
78
|
+
value={mode}
|
|
79
|
+
>
|
|
80
|
+
<Radio.Button value={'url'}>{t('dev.manifest.mode.url')}</Radio.Button>
|
|
81
|
+
<Tooltip title={t('dev.manifest.mode.local-tooltip')}>
|
|
82
|
+
<Radio.Button disabled value={'local'}>
|
|
83
|
+
{t('dev.manifest.mode.local')}
|
|
84
|
+
</Radio.Button>
|
|
85
|
+
</Tooltip>
|
|
86
|
+
</Radio.Group>
|
|
87
|
+
),
|
|
88
|
+
title: t('dev.tabs.manifest'),
|
|
89
|
+
},
|
|
90
|
+
]}
|
|
91
|
+
layout={isUrl ? 'vertical' : undefined}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export default ManifestForm;
|
|
@@ -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,
|