@lobehub/chat 0.65.1 → 0.67.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 +68 -0
- package/locales/ru_RU/common.json +85 -0
- package/locales/ru_RU/empty.json +6 -0
- package/locales/ru_RU/error.json +41 -0
- package/locales/ru_RU/plugin.json +111 -0
- package/locales/ru_RU/setting.json +175 -0
- package/locales/ru_RU/welcome.json +14 -0
- package/package.json +1 -1
- 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/locales/options.ts +4 -0
- package/src/locales/resources/index.ts +2 -0
- package/src/locales/resources/ru_RU.ts +17 -0
- package/src/store/plugin/initialState.ts +5 -34
- package/src/store/plugin/selectors.ts +2 -2
- 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/store/plugin/{reducers → slices/plugin/reducers}/manifest.ts +0 -0
|
@@ -41,22 +41,33 @@ const MarketList = memo(() => {
|
|
|
41
41
|
// setModal(true);
|
|
42
42
|
// }, []);
|
|
43
43
|
|
|
44
|
-
const
|
|
45
|
-
|
|
44
|
+
const [plugins, hasPlugin, toggleAgentPlugin] = useStore((s) => [
|
|
45
|
+
s.config.plugins || [],
|
|
46
|
+
!!s.config.plugins,
|
|
47
|
+
s.toggleAgentPlugin,
|
|
48
|
+
]);
|
|
46
49
|
|
|
47
50
|
const [useFetchPluginList, fetchPluginManifest, saveToDevList, updateNewDevPlugin] =
|
|
48
51
|
usePluginStore((s) => [
|
|
49
52
|
s.useFetchPluginList,
|
|
50
53
|
s.fetchPluginManifest,
|
|
51
|
-
s.
|
|
52
|
-
s.
|
|
54
|
+
s.saveToCustomPluginList,
|
|
55
|
+
s.updateNewCustomPlugin,
|
|
53
56
|
]);
|
|
54
57
|
const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
|
|
55
58
|
const pluginList = usePluginStore((s) => s.pluginList, isEqual);
|
|
56
|
-
const
|
|
59
|
+
const customPluginList = usePluginStore((s) => s.customPluginList, isEqual);
|
|
57
60
|
|
|
58
61
|
useFetchPluginList();
|
|
59
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 =========== //
|
|
60
71
|
const loadingItem = {
|
|
61
72
|
avatar: (
|
|
62
73
|
<Skeleton
|
|
@@ -77,11 +88,12 @@ const MarketList = memo(() => {
|
|
|
77
88
|
/>
|
|
78
89
|
),
|
|
79
90
|
};
|
|
80
|
-
|
|
81
91
|
const loadingList = [loadingItem, loadingItem, loadingItem];
|
|
82
92
|
|
|
83
93
|
const isEmpty = pluginList.length === 0;
|
|
84
94
|
|
|
95
|
+
// =========== Plugin List =========== //
|
|
96
|
+
|
|
85
97
|
const list = pluginList.map(({ identifier, meta }) => ({
|
|
86
98
|
avatar: <Avatar avatar={meta.avatar} />,
|
|
87
99
|
children: (
|
|
@@ -92,10 +104,7 @@ const MarketList = memo(() => {
|
|
|
92
104
|
}
|
|
93
105
|
loading={pluginManifestLoading[identifier]}
|
|
94
106
|
onChange={(checked) => {
|
|
95
|
-
|
|
96
|
-
if (checked) {
|
|
97
|
-
fetchPluginManifest(identifier);
|
|
98
|
-
}
|
|
107
|
+
togglePlugin(identifier, checked);
|
|
99
108
|
}}
|
|
100
109
|
/>
|
|
101
110
|
),
|
|
@@ -105,7 +114,9 @@ const MarketList = memo(() => {
|
|
|
105
114
|
tag: identifier,
|
|
106
115
|
}));
|
|
107
116
|
|
|
108
|
-
|
|
117
|
+
// =========== Custom Plugin List =========== //
|
|
118
|
+
|
|
119
|
+
const customList = customPluginList.map(({ identifier, meta }) => ({
|
|
109
120
|
avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta) || '🧩'} />,
|
|
110
121
|
children: <LocalPluginItem id={identifier} />,
|
|
111
122
|
desc: pluginHelpers.getPluginDesc(meta),
|
|
@@ -125,14 +136,19 @@ const MarketList = memo(() => {
|
|
|
125
136
|
<>
|
|
126
137
|
<DevModal
|
|
127
138
|
onOpenChange={setModal}
|
|
128
|
-
onSave={
|
|
139
|
+
onSave={async (devPlugin) => {
|
|
140
|
+
// 先保存
|
|
141
|
+
saveToDevList(devPlugin);
|
|
142
|
+
// 再开启插件
|
|
143
|
+
await togglePlugin(devPlugin.identifier, true);
|
|
144
|
+
}}
|
|
129
145
|
onValueChange={updateNewDevPlugin}
|
|
130
146
|
open={showModal}
|
|
131
147
|
/>
|
|
132
148
|
<Form
|
|
133
149
|
items={[
|
|
134
150
|
{
|
|
135
|
-
children: isEmpty ? loadingList : [...
|
|
151
|
+
children: isEmpty ? loadingList : [...customList, ...list],
|
|
136
152
|
extra: (
|
|
137
153
|
<Tooltip title={t('settingPlugin.addTooltip')}>
|
|
138
154
|
<Button
|
|
@@ -1,96 +1,160 @@
|
|
|
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 {
|
|
1
|
+
import { LobeChatPluginManifest, pluginManifestSchema } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { ActionIcon, Form, FormItemProps, Highlighter, Input, Tooltip } from '@lobehub/ui';
|
|
3
|
+
import { FormInstance, Popover, Radio } from 'antd';
|
|
4
|
+
import { FileCode, RotateCwIcon } from 'lucide-react';
|
|
5
|
+
import { memo, useState } from 'react';
|
|
5
6
|
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Flexbox } from 'react-layout-kit';
|
|
6
8
|
|
|
7
|
-
const ManifestForm = memo<{ form: FormInstance; mode
|
|
8
|
-
|
|
9
|
+
const ManifestForm = memo<{ form: FormInstance; mode?: 'url' | 'local' }>(
|
|
10
|
+
({ form, mode = 'url' }) => {
|
|
11
|
+
const { t } = useTranslation('plugin');
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
const [manifest, setManifest] = useState<LobeChatPluginManifest>();
|
|
11
14
|
|
|
12
|
-
|
|
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;
|
|
15
|
+
const isUrl = mode === 'url';
|
|
32
16
|
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
const configItem: FormItemProps[] = isUrl
|
|
18
|
+
? [
|
|
19
|
+
{
|
|
20
|
+
children: (
|
|
21
|
+
<Input
|
|
22
|
+
placeholder={'http://localhost:3400/manifest-dev.json'}
|
|
23
|
+
suffix={
|
|
24
|
+
manifest && (
|
|
25
|
+
<ActionIcon
|
|
26
|
+
icon={RotateCwIcon}
|
|
27
|
+
onClick={(e) => {
|
|
28
|
+
e.stopPropagation();
|
|
29
|
+
form.validateFields(['manifest']);
|
|
30
|
+
}}
|
|
31
|
+
size={'small'}
|
|
32
|
+
title={t('dev.meta.manifest.refresh')}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
/>
|
|
37
|
+
),
|
|
38
|
+
extra: (
|
|
39
|
+
<Flexbox horizontal justify={'space-between'} style={{ marginTop: 8 }}>
|
|
40
|
+
{t('dev.meta.manifest.desc')}
|
|
41
|
+
{manifest && (
|
|
42
|
+
<Popover
|
|
43
|
+
arrow={false}
|
|
44
|
+
content={
|
|
45
|
+
<Highlighter language={'json'}>
|
|
46
|
+
{JSON.stringify(manifest, null, 2)}
|
|
47
|
+
</Highlighter>
|
|
48
|
+
}
|
|
49
|
+
placement={'right'}
|
|
50
|
+
style={{ width: 400 }}
|
|
51
|
+
title={'Manifest JSON'}
|
|
52
|
+
trigger={'click'}
|
|
53
|
+
>
|
|
54
|
+
<ActionIcon
|
|
55
|
+
icon={FileCode}
|
|
56
|
+
size={'small'}
|
|
57
|
+
title={t('dev.meta.manifest.preview')}
|
|
58
|
+
/>
|
|
59
|
+
</Popover>
|
|
60
|
+
)}
|
|
61
|
+
</Flexbox>
|
|
62
|
+
),
|
|
63
|
+
// extra: <div>123</div>,
|
|
64
|
+
hasFeedback: true,
|
|
65
|
+
label: t('dev.meta.manifest.label'),
|
|
66
|
+
name: 'manifest',
|
|
67
|
+
required: true,
|
|
68
|
+
rules: [
|
|
69
|
+
{ required: true },
|
|
70
|
+
{
|
|
71
|
+
message: t('dev.meta.manifest.urlError'),
|
|
72
|
+
pattern: /^https?:\/\/.*/,
|
|
35
73
|
},
|
|
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
|
-
},
|
|
74
|
+
{
|
|
75
|
+
// message: t('dev.meta.manifest.invalid'),
|
|
76
|
+
validator: async (_, value) => {
|
|
77
|
+
if (!value) return true;
|
|
49
78
|
|
|
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
|
-
];
|
|
79
|
+
let res: Response;
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
try {
|
|
82
|
+
res = await fetch(value);
|
|
83
|
+
} catch {
|
|
84
|
+
throw t('dev.meta.manifest.requestError');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const json = await res.json().catch(() => {
|
|
88
|
+
throw t('dev.meta.manifest.urlError');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const valid = pluginManifestSchema.safeParse(json);
|
|
92
|
+
if (!valid.success) {
|
|
93
|
+
throw t('dev.meta.manifest.jsonInvalid', { error: valid.error });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setManifest(json);
|
|
97
|
+
form.setFieldValue('identifier', valid.data.identifier);
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
]
|
|
103
|
+
: // TODO: 后续做成本地配置模式
|
|
104
|
+
[
|
|
105
|
+
{
|
|
106
|
+
children: <Input placeholder={'searchEngine'} />,
|
|
107
|
+
desc: t('dev.meta.identifier.desc'),
|
|
108
|
+
label: t('dev.meta.identifier.label'),
|
|
109
|
+
name: 'name',
|
|
110
|
+
required: true,
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
{
|
|
114
|
+
children: <Input placeholder={t('dev.meta.description.placeholder')} />,
|
|
115
|
+
desc: t('dev.meta.description.desc'),
|
|
116
|
+
label: t('dev.meta.description.label'),
|
|
117
|
+
name: 'description',
|
|
118
|
+
required: true,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
children: <Input placeholder={'searchEngine'} />,
|
|
122
|
+
desc: t('dev.meta.identifier.desc'),
|
|
123
|
+
label: t('dev.meta.identifier.label'),
|
|
124
|
+
name: 'identifier',
|
|
125
|
+
required: true,
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Form
|
|
131
|
+
form={form}
|
|
132
|
+
items={[
|
|
133
|
+
{
|
|
134
|
+
children: configItem,
|
|
135
|
+
extra: (
|
|
136
|
+
<Radio.Group
|
|
137
|
+
onChange={(v) => {
|
|
138
|
+
form.setFieldValue('manifestMode', v.target.value);
|
|
139
|
+
}}
|
|
140
|
+
size={'small'}
|
|
141
|
+
value={mode}
|
|
142
|
+
>
|
|
143
|
+
<Radio.Button value={'url'}>{t('dev.manifest.mode.url')}</Radio.Button>
|
|
144
|
+
<Tooltip title={t('dev.manifest.mode.local-tooltip')}>
|
|
145
|
+
<Radio.Button disabled value={'local'}>
|
|
146
|
+
{t('dev.manifest.mode.local')}
|
|
147
|
+
</Radio.Button>
|
|
148
|
+
</Tooltip>
|
|
149
|
+
</Radio.Group>
|
|
150
|
+
),
|
|
151
|
+
title: t('dev.tabs.manifest'),
|
|
152
|
+
},
|
|
153
|
+
]}
|
|
154
|
+
layout={isUrl ? 'vertical' : undefined}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
);
|
|
95
159
|
|
|
96
160
|
export default ManifestForm;
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
import { Form, FormItemProps, Input } from '@lobehub/ui';
|
|
2
2
|
import { FormInstance } from 'antd';
|
|
3
|
-
import { memo
|
|
3
|
+
import { memo } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
|
6
6
|
import EmojiPicker from '@/components/EmojiPicker';
|
|
7
7
|
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
8
8
|
|
|
9
|
-
const MetaForm = memo<{ form: FormInstance }>(({ form }) => {
|
|
9
|
+
const MetaForm = memo<{ form: FormInstance; mode?: 'edit' | 'create' }>(({ form, mode }) => {
|
|
10
|
+
const isEditMode = mode === 'edit';
|
|
11
|
+
|
|
10
12
|
const { t } = useTranslation('plugin');
|
|
11
13
|
const [plugins] = usePluginStore((s) => [pluginSelectors.pluginList(s).map((i) => i.identifier)]);
|
|
12
14
|
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
if (usePluginStore.getState().newDevPlugin) {
|
|
15
|
-
form.setFieldsValue(usePluginStore.getState().newDevPlugin);
|
|
16
|
-
}
|
|
17
|
-
}, []);
|
|
18
|
-
|
|
19
15
|
const configItem: FormItemProps[] = [
|
|
20
16
|
{
|
|
21
|
-
children: <Input placeholder={'searchEngine'} />,
|
|
17
|
+
children: <Input disabled placeholder={'searchEngine'} />,
|
|
22
18
|
desc: t('dev.meta.identifier.desc'),
|
|
23
19
|
label: t('dev.meta.identifier.label'),
|
|
24
20
|
name: 'identifier',
|
|
@@ -28,14 +24,17 @@ const MetaForm = memo<{ form: FormInstance }>(({ form }) => {
|
|
|
28
24
|
message: t('dev.meta.identifier.pattenErrorMessage'),
|
|
29
25
|
pattern: /^[\w-]+$/,
|
|
30
26
|
},
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
// 编辑模式下,不进行重复校验
|
|
28
|
+
isEditMode
|
|
29
|
+
? {}
|
|
30
|
+
: {
|
|
31
|
+
message: t('dev.meta.identifier.errorDuplicate'),
|
|
32
|
+
validator: async (_, value) => {
|
|
33
|
+
if (plugins.includes(value)) {
|
|
34
|
+
throw new Error('Duplicate');
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
},
|
|
39
38
|
],
|
|
40
39
|
},
|
|
41
40
|
{
|
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import { Avatar, Form } from '@lobehub/ui';
|
|
2
|
-
import { Card, Switch, Tag } from 'antd';
|
|
2
|
+
import { Form as AForm, Card, FormInstance, Switch, Tag } from 'antd';
|
|
3
3
|
import { memo } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
6
|
|
|
7
|
-
import { pluginHelpers
|
|
7
|
+
import { pluginHelpers } from '@/store/plugin';
|
|
8
|
+
import { CustomPlugin } from '@/types/plugin';
|
|
8
9
|
|
|
9
|
-
const PluginPreview = memo(() => {
|
|
10
|
+
const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
|
|
10
11
|
const { t } = useTranslation('plugin');
|
|
11
|
-
const
|
|
12
|
+
const plugin: CustomPlugin = AForm.useWatch([], form);
|
|
12
13
|
|
|
13
14
|
const items = {
|
|
14
|
-
avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(
|
|
15
|
+
avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(plugin?.meta) || '🧩'} />,
|
|
15
16
|
children: <Switch disabled />,
|
|
16
|
-
desc: pluginHelpers.getPluginDesc(
|
|
17
|
+
desc: pluginHelpers.getPluginDesc(plugin?.meta),
|
|
17
18
|
label: (
|
|
18
19
|
<Flexbox align={'center'} gap={8} horizontal>
|
|
19
|
-
{pluginHelpers.getPluginTitle(
|
|
20
|
+
{pluginHelpers.getPluginTitle(plugin?.meta) ?? t('dev.preview.title')}
|
|
20
21
|
<Tag bordered={false} color={'gold'}>
|
|
21
22
|
{t('list.item.local.title', { ns: 'plugin' })}
|
|
22
23
|
</Tag>
|
|
23
24
|
</Flexbox>
|
|
24
25
|
),
|
|
25
26
|
minWidth: undefined,
|
|
26
|
-
tag: !!
|
|
27
|
+
tag: !!plugin?.identifier ? plugin?.identifier : 'id',
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
return (
|
|
@@ -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
|
|
package/src/locales/options.ts
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import common from '../../../locales/ru_RU/common.json';
|
|
2
|
+
import empty from '../../../locales/ru_RU/empty.json';
|
|
3
|
+
import error from '../../../locales/ru_RU/error.json';
|
|
4
|
+
import plugin from '../../../locales/ru_RU/plugin.json';
|
|
5
|
+
import setting from '../../../locales/ru_RU/setting.json';
|
|
6
|
+
import welcome from '../../../locales/ru_RU/welcome.json';
|
|
7
|
+
|
|
8
|
+
const resources = {
|
|
9
|
+
common,
|
|
10
|
+
empty,
|
|
11
|
+
error,
|
|
12
|
+
plugin,
|
|
13
|
+
setting,
|
|
14
|
+
welcome,
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export default resources;
|