Skip to content

Commit 17c39ef

Browse files
committed
🐛 fix(plugin-dev): 修正编辑模式下预览展示问题和 id 重复校验问题
1 parent 7f0787d commit 17c39ef

6 files changed

Lines changed: 183 additions & 144 deletions

File tree

src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const MarketList = memo<{ id: string }>(({ id }) => {
3131
return (
3232
<>
3333
<DevModal
34+
mode={'edit'}
3435
onDelete={() => {
3536
dispatchDevPluginList({ id, type: 'deleteItem' });
3637
}}
@@ -39,7 +40,6 @@ const MarketList = memo<{ id: string }>(({ id }) => {
3940
dispatchDevPluginList({ id, plugin: value, type: 'updateItem' });
4041
}}
4142
open={showModal}
42-
showDelete
4343
value={devPlugin}
4444
/>
4545

Lines changed: 143 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,160 @@
11
import { LobeChatPluginManifest, pluginManifestSchema } from '@lobehub/chat-plugin-sdk';
2-
import { ActionIcon, Form, FormItemProps, Input, Tooltip } from '@lobehub/ui';
3-
import { FormInstance, Radio } from 'antd';
4-
import { RotateCwIcon } from 'lucide-react';
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';
55
import { memo, useState } from 'react';
66
import { useTranslation } from 'react-i18next';
7+
import { Flexbox } from 'react-layout-kit';
78

8-
const ManifestForm = memo<{ form: FormInstance; mode: 'url' | 'local' }>(({ form, mode }) => {
9-
const { t } = useTranslation('plugin');
9+
const ManifestForm = memo<{ form: FormInstance; mode?: 'url' | 'local' }>(
10+
({ form, mode = 'url' }) => {
11+
const { t } = useTranslation('plugin');
1012

11-
const [manifest, setManifest] = useState<LobeChatPluginManifest>();
13+
const [manifest, setManifest] = useState<LobeChatPluginManifest>();
1214

13-
const isUrl = mode === 'url';
15+
const isUrl = mode === 'url';
1416

15-
const configItem: FormItemProps[] = isUrl
16-
? [
17-
{
18-
children: (
19-
<Input
20-
placeholder={'http://localhost:3400/manifest-dev.json'}
21-
suffix={
22-
manifest && (
23-
<ActionIcon
24-
icon={RotateCwIcon}
25-
onClick={(e) => {
26-
e.stopPropagation();
27-
form.validateFields(['manifest']);
28-
}}
29-
size={'small'}
30-
/>
31-
)
32-
}
33-
/>
34-
),
35-
desc: t('dev.meta.manifest.desc'),
36-
hasFeedback: true,
37-
label: t('dev.meta.manifest.label'),
38-
name: 'manifest',
39-
required: true,
40-
rules: [
41-
{ required: true },
42-
{
43-
message: t('dev.meta.manifest.urlError'),
44-
pattern: /^https?:\/\/.*/,
45-
},
46-
{
47-
// message: t('dev.meta.manifest.invalid'),
48-
validator: async (_, value) => {
49-
if (!value) return true;
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?:\/\/.*/,
73+
},
74+
{
75+
// message: t('dev.meta.manifest.invalid'),
76+
validator: async (_, value) => {
77+
if (!value) return true;
5078

51-
let res: Response;
79+
let res: Response;
5280

53-
try {
54-
res = await fetch(value);
55-
} catch {
56-
throw t('dev.meta.manifest.requestError');
57-
}
81+
try {
82+
res = await fetch(value);
83+
} catch {
84+
throw t('dev.meta.manifest.requestError');
85+
}
5886

59-
const json = await res.json().catch(() => {
60-
throw t('dev.meta.manifest.urlError');
61-
});
87+
const json = await res.json().catch(() => {
88+
throw t('dev.meta.manifest.urlError');
89+
});
6290

63-
const valid = pluginManifestSchema.safeParse(json);
64-
if (!valid.success) {
65-
throw t('dev.meta.manifest.jsonInvalid', { error: valid.error });
66-
}
91+
const valid = pluginManifestSchema.safeParse(json);
92+
if (!valid.success) {
93+
throw t('dev.meta.manifest.jsonInvalid', { error: valid.error });
94+
}
6795

68-
setManifest(valid.data);
69-
form.setFieldValue('identifier', valid.data.identifier);
96+
setManifest(json);
97+
form.setFieldValue('identifier', valid.data.identifier);
98+
},
7099
},
71-
},
72-
],
73-
},
74-
]
75-
: // TODO: 后续做成本地配置模式
76-
[
77-
{
78-
children: <Input placeholder={'searchEngine'} />,
79-
desc: t('dev.meta.identifier.desc'),
80-
label: t('dev.meta.identifier.label'),
81-
name: 'name',
82-
required: true,
83-
},
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+
},
84112

85-
{
86-
children: <Input placeholder={t('dev.meta.description.placeholder')} />,
87-
desc: t('dev.meta.description.desc'),
88-
label: t('dev.meta.description.label'),
89-
name: 'description',
90-
required: true,
91-
},
92-
{
93-
children: <Input placeholder={'searchEngine'} />,
94-
desc: t('dev.meta.identifier.desc'),
95-
label: t('dev.meta.identifier.label'),
96-
name: 'identifier',
97-
required: true,
98-
},
99-
];
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+
];
100128

101-
return (
102-
<Form
103-
form={form}
104-
items={[
105-
{
106-
children: configItem,
107-
extra: (
108-
<Radio.Group
109-
onChange={(v) => {
110-
form.setFieldValue('manifestMode', v.target.value);
111-
}}
112-
size={'small'}
113-
value={mode}
114-
>
115-
<Radio.Button value={'url'}>{t('dev.manifest.mode.url')}</Radio.Button>
116-
<Tooltip title={t('dev.manifest.mode.local-tooltip')}>
117-
<Radio.Button disabled value={'local'}>
118-
{t('dev.manifest.mode.local')}
119-
</Radio.Button>
120-
</Tooltip>
121-
</Radio.Group>
122-
),
123-
title: t('dev.tabs.manifest'),
124-
},
125-
]}
126-
layout={isUrl ? 'vertical' : undefined}
127-
/>
128-
);
129-
});
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+
);
130159

131160
export default ManifestForm;

src/features/PluginDevModal/MetaForm.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import { useTranslation } from 'react-i18next';
66
import EmojiPicker from '@/components/EmojiPicker';
77
import { pluginSelectors, usePluginStore } from '@/store/plugin';
88

9-
const MetaForm = memo<{ form: FormInstance }>(({ form }) => {
9+
const MetaForm = memo<{ form: FormInstance; mode?: 'edit' | 'create' }>(({ form, mode }) => {
10+
const isEditMode = mode === 'edit';
11+
1012
const { t } = useTranslation('plugin');
1113
const [plugins] = usePluginStore((s) => [pluginSelectors.pluginList(s).map((i) => i.identifier)]);
1214

@@ -28,14 +30,17 @@ const MetaForm = memo<{ form: FormInstance }>(({ form }) => {
2830
message: t('dev.meta.identifier.pattenErrorMessage'),
2931
pattern: /^[\w-]+$/,
3032
},
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-
},
33+
// 编辑模式下,不进行重复校验
34+
isEditMode
35+
? {}
36+
: {
37+
message: t('dev.meta.identifier.errorDuplicate'),
38+
validator: async (_, value) => {
39+
if (plugins.includes(value)) {
40+
throw new Error('Duplicate');
41+
}
42+
},
43+
},
3944
],
4045
},
4146
{

src/features/PluginDevModal/PluginPreview.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
import { Avatar, Form } from '@lobehub/ui';
2-
import { Card, Switch, Tag } from 'antd';
2+
import { Form as AForm, Card, FormInstance, Switch, Tag } from 'antd';
33
import { memo } from 'react';
44
import { useTranslation } from 'react-i18next';
55
import { Flexbox } from 'react-layout-kit';
66

7-
import { pluginHelpers, usePluginStore } from '@/store/plugin';
7+
import { pluginHelpers } from '@/store/plugin';
8+
import { DevPlugin } from '@/store/plugin/initialState';
89

9-
const PluginPreview = memo(() => {
10+
const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
1011
const { t } = useTranslation('plugin');
11-
const meta = usePluginStore((s) => s.newDevPlugin);
12+
const plugin: DevPlugin = AForm.useWatch([], form);
1213

1314
const items = {
14-
avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta?.meta) || '🧩'} />,
15+
avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(plugin?.meta) || '🧩'} />,
1516
children: <Switch disabled />,
16-
desc: pluginHelpers.getPluginDesc(meta?.meta),
17+
desc: pluginHelpers.getPluginDesc(plugin?.meta),
1718
label: (
1819
<Flexbox align={'center'} gap={8} horizontal>
19-
{pluginHelpers.getPluginTitle(meta?.meta) ?? t('dev.preview.title')}
20+
{pluginHelpers.getPluginTitle(plugin?.meta) ?? t('dev.preview.title')}
2021
<Tag bordered={false} color={'gold'}>
2122
{t('list.item.local.title', { ns: 'plugin' })}
2223
</Tag>
2324
</Flexbox>
2425
),
2526
minWidth: undefined,
26-
tag: !!meta?.identifier ? meta?.identifier : 'id',
27+
tag: !!plugin?.identifier ? plugin?.identifier : 'id',
2728
};
2829

2930
return (

0 commit comments

Comments
 (0)