@lobehub/chat 1.84.3 → 1.84.4
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 +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/plugin.json +18 -0
- package/locales/bg-BG/plugin.json +18 -0
- package/locales/de-DE/plugin.json +18 -0
- package/locales/en-US/plugin.json +18 -0
- package/locales/es-ES/plugin.json +18 -0
- package/locales/fa-IR/plugin.json +18 -0
- package/locales/fr-FR/plugin.json +18 -0
- package/locales/it-IT/plugin.json +18 -0
- package/locales/ja-JP/plugin.json +18 -0
- package/locales/ko-KR/plugin.json +18 -0
- package/locales/nl-NL/plugin.json +18 -0
- package/locales/pl-PL/plugin.json +18 -0
- package/locales/pt-BR/plugin.json +18 -0
- package/locales/ru-RU/plugin.json +18 -0
- package/locales/tr-TR/plugin.json +18 -0
- package/locales/vi-VN/plugin.json +18 -0
- package/locales/zh-CN/plugin.json +18 -0
- package/locales/zh-TW/plugin.json +18 -0
- package/package.json +2 -2
- package/src/features/PluginDevModal/MCPManifestForm/index.tsx +209 -142
- package/src/features/PluginDevModal/PluginPreview/ApiVisualizer.tsx +180 -0
- package/src/features/PluginDevModal/PluginPreview/EmptyState.tsx +78 -0
- package/src/features/PluginDevModal/PluginPreview/index.tsx +72 -0
- package/src/features/PluginDevModal/index.tsx +75 -62
- package/src/locales/default/plugin.ts +18 -0
- package/src/features/PluginDevModal/PluginPreview.tsx +0 -34
@@ -0,0 +1,180 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Block, Icon, Tag } from '@lobehub/ui';
|
4
|
+
import { Input, Space, Typography } from 'antd';
|
5
|
+
import { createStyles } from 'antd-style';
|
6
|
+
import { ChevronDown, ChevronRight } from 'lucide-react';
|
7
|
+
import { memo, useState } from 'react';
|
8
|
+
import { useTranslation } from 'react-i18next';
|
9
|
+
import { Flexbox } from 'react-layout-kit';
|
10
|
+
|
11
|
+
const useStyles = createStyles(({ css, token }) => ({
|
12
|
+
apiHeader: css`
|
13
|
+
cursor: pointer;
|
14
|
+
display: flex;
|
15
|
+
align-items: center;
|
16
|
+
justify-content: space-between;
|
17
|
+
`,
|
18
|
+
|
19
|
+
apiTitle: css`
|
20
|
+
font-family: ${token.fontFamilyCode};
|
21
|
+
`,
|
22
|
+
|
23
|
+
emptyState: css`
|
24
|
+
padding: 32px;
|
25
|
+
color: ${token.colorTextDisabled};
|
26
|
+
text-align: center;
|
27
|
+
`,
|
28
|
+
header: css`
|
29
|
+
display: flex;
|
30
|
+
gap: 8px;
|
31
|
+
align-items: center;
|
32
|
+
margin-block-end: 24px;
|
33
|
+
`,
|
34
|
+
paramDesc: css`
|
35
|
+
font-size: 12px;
|
36
|
+
line-height: 18px;
|
37
|
+
color: ${token.colorTextSecondary};
|
38
|
+
`,
|
39
|
+
paramGrid: css`
|
40
|
+
display: grid;
|
41
|
+
grid-template-columns: 1fr 2fr;
|
42
|
+
gap: 12px;
|
43
|
+
align-items: center;
|
44
|
+
|
45
|
+
margin-block-end: 12px;
|
46
|
+
`,
|
47
|
+
paramName: css`
|
48
|
+
display: flex;
|
49
|
+
gap: 6px;
|
50
|
+
align-items: center;
|
51
|
+
font-family: monospace;
|
52
|
+
`,
|
53
|
+
params: css`
|
54
|
+
color: ${token.colorTextQuaternary};
|
55
|
+
`,
|
56
|
+
required: css`
|
57
|
+
margin-inline-start: 2px;
|
58
|
+
color: ${token.colorError};
|
59
|
+
`,
|
60
|
+
searchIcon: css`
|
61
|
+
position: absolute;
|
62
|
+
z-index: 1;
|
63
|
+
inset-block-start: 50%;
|
64
|
+
inset-inline-start: 12px;
|
65
|
+
transform: translateY(-50%);
|
66
|
+
|
67
|
+
color: ${token.colorTextSecondary};
|
68
|
+
`,
|
69
|
+
searchWrapper: css`
|
70
|
+
position: relative;
|
71
|
+
`,
|
72
|
+
typeTag: css`
|
73
|
+
height: 20px;
|
74
|
+
padding-block: 0;
|
75
|
+
padding-inline: 6px;
|
76
|
+
|
77
|
+
font-size: 12px;
|
78
|
+
line-height: 20px;
|
79
|
+
`,
|
80
|
+
}));
|
81
|
+
|
82
|
+
interface ApiItemProps {
|
83
|
+
api: {
|
84
|
+
description: string;
|
85
|
+
name: string;
|
86
|
+
parameters: {
|
87
|
+
properties: Record<string, { description: string; type: string }>;
|
88
|
+
required: string[];
|
89
|
+
};
|
90
|
+
};
|
91
|
+
}
|
92
|
+
|
93
|
+
const ApiItem = memo<ApiItemProps>(({ api }) => {
|
94
|
+
const { styles, theme } = useStyles();
|
95
|
+
const [expanded, setExpanded] = useState(false);
|
96
|
+
const { t } = useTranslation('plugin');
|
97
|
+
|
98
|
+
const params = Object.entries(api.parameters.properties || {});
|
99
|
+
return (
|
100
|
+
<Block gap={8} padding={16}>
|
101
|
+
<div className={styles.apiHeader} onClick={() => setExpanded(!expanded)}>
|
102
|
+
<Flexbox gap={4}>
|
103
|
+
<div className={styles.apiTitle}>{api.name}</div>
|
104
|
+
<Typography.Text type="secondary">{api.description}</Typography.Text>
|
105
|
+
</Flexbox>
|
106
|
+
|
107
|
+
<Icon icon={expanded ? ChevronDown : ChevronRight} />
|
108
|
+
</div>
|
109
|
+
|
110
|
+
{expanded && (
|
111
|
+
<Flexbox
|
112
|
+
gap={12}
|
113
|
+
padding={16}
|
114
|
+
style={{ background: theme.colorFillQuaternary, borderRadius: 6 }}
|
115
|
+
>
|
116
|
+
{params.length === 0 ? (
|
117
|
+
<div className={styles.params}>{t('dev.preview.api.noParams')}</div>
|
118
|
+
) : (
|
119
|
+
<>
|
120
|
+
<div className={styles.params}>{t('dev.preview.api.params')}</div>
|
121
|
+
<Space direction="vertical" style={{ width: '100%' }}>
|
122
|
+
{params.map(([name, param]) => {
|
123
|
+
const isRequired = api.parameters.required?.includes(name);
|
124
|
+
return (
|
125
|
+
<div className={styles.paramGrid} key={name}>
|
126
|
+
<div className={styles.paramName}>
|
127
|
+
<span>{name}</span>
|
128
|
+
{isRequired && <span className={styles.required}>*</span>}
|
129
|
+
<Tag className={styles.typeTag}>{param.type}</Tag>
|
130
|
+
</div>
|
131
|
+
<div className={styles.paramDesc}>{param.description}</div>
|
132
|
+
</div>
|
133
|
+
);
|
134
|
+
})}
|
135
|
+
</Space>
|
136
|
+
</>
|
137
|
+
)}
|
138
|
+
</Flexbox>
|
139
|
+
)}
|
140
|
+
</Block>
|
141
|
+
);
|
142
|
+
});
|
143
|
+
|
144
|
+
interface ApiVisualizerProps {
|
145
|
+
apis: ApiItemProps['api'][];
|
146
|
+
}
|
147
|
+
|
148
|
+
const ApiVisualizer = memo<ApiVisualizerProps>(({ apis = [] }) => {
|
149
|
+
const { styles } = useStyles();
|
150
|
+
const [searchQuery, setSearchQuery] = useState('');
|
151
|
+
const { t } = useTranslation('plugin');
|
152
|
+
|
153
|
+
const filteredApis = apis.filter(
|
154
|
+
(api) =>
|
155
|
+
api.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
156
|
+
api.description.toLowerCase().includes(searchQuery.toLowerCase()),
|
157
|
+
);
|
158
|
+
|
159
|
+
return (
|
160
|
+
<Flexbox gap={8} width={'100%'}>
|
161
|
+
<div className={styles.searchWrapper}>
|
162
|
+
<Input.Search
|
163
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
164
|
+
placeholder={t('dev.preview.api.searchPlaceholder')}
|
165
|
+
value={searchQuery}
|
166
|
+
/>
|
167
|
+
</div>
|
168
|
+
|
169
|
+
<Space direction="vertical" style={{ width: '100%' }}>
|
170
|
+
{filteredApis.length > 0 ? (
|
171
|
+
filteredApis.map((api, index) => <ApiItem api={api} key={index} />)
|
172
|
+
) : (
|
173
|
+
<div className={styles.emptyState}>{t('dev.preview.api.noResults')}</div>
|
174
|
+
)}
|
175
|
+
</Space>
|
176
|
+
</Flexbox>
|
177
|
+
);
|
178
|
+
});
|
179
|
+
|
180
|
+
export default ApiVisualizer;
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
2
|
+
import { Space, Typography } from 'antd';
|
3
|
+
import { createStyles } from 'antd-style';
|
4
|
+
import { Puzzle } from 'lucide-react';
|
5
|
+
import { useTranslation } from 'react-i18next';
|
6
|
+
|
7
|
+
const { Title, Paragraph } = Typography;
|
8
|
+
|
9
|
+
// Create styles using antd-style
|
10
|
+
const useStyles = createStyles(({ token, css }) => ({
|
11
|
+
container: css`
|
12
|
+
display: flex;
|
13
|
+
flex-direction: column;
|
14
|
+
align-items: center;
|
15
|
+
justify-content: center;
|
16
|
+
|
17
|
+
width: 100%;
|
18
|
+
height: 100%;
|
19
|
+
padding: ${token.paddingLG}px;
|
20
|
+
`,
|
21
|
+
description: css`
|
22
|
+
max-width: 320px;
|
23
|
+
color: ${token.colorTextSecondary};
|
24
|
+
text-align: center;
|
25
|
+
`,
|
26
|
+
iconWrapper: css`
|
27
|
+
display: flex;
|
28
|
+
align-items: center;
|
29
|
+
justify-content: center;
|
30
|
+
|
31
|
+
width: 64px;
|
32
|
+
height: 64px;
|
33
|
+
margin-block-end: ${token.marginMD}px;
|
34
|
+
border-radius: 50%;
|
35
|
+
|
36
|
+
background-color: ${token.colorPrimaryBg};
|
37
|
+
`,
|
38
|
+
line: css`
|
39
|
+
height: 6px;
|
40
|
+
border-radius: 3px;
|
41
|
+
background: ${token.colorBorderSecondary};
|
42
|
+
`,
|
43
|
+
placeholderLine: css`
|
44
|
+
height: 6px;
|
45
|
+
margin-block: ${token.marginXS}px;
|
46
|
+
margin-inline: 0;
|
47
|
+
border-radius: ${token.borderRadiusLG}px;
|
48
|
+
|
49
|
+
background-color: ${token.colorBorderSecondary};
|
50
|
+
`,
|
51
|
+
title: css`
|
52
|
+
margin-block-end: ${token.marginXS}px;
|
53
|
+
font-size: ${token.fontSizeLG}px;
|
54
|
+
font-weight: 500;
|
55
|
+
`,
|
56
|
+
}));
|
57
|
+
|
58
|
+
export default function PluginEmptyState() {
|
59
|
+
const { styles } = useStyles();
|
60
|
+
const { t } = useTranslation('plugin');
|
61
|
+
|
62
|
+
return (
|
63
|
+
<div className={styles.container}>
|
64
|
+
<div className={styles.iconWrapper}>
|
65
|
+
<Icon icon={Puzzle} size={32} />
|
66
|
+
</div>
|
67
|
+
<Title className={styles.title} level={4}>
|
68
|
+
{t('dev.preview.empty.title')}
|
69
|
+
</Title>
|
70
|
+
<Paragraph className={styles.description}>{t('dev.preview.empty.desc')}</Paragraph>
|
71
|
+
<Space align="center" direction="vertical" style={{ marginTop: 24 }}>
|
72
|
+
<div className={styles.line} style={{ width: 128 }} />
|
73
|
+
<div className={styles.line} style={{ width: 96 }} />
|
74
|
+
<div className={styles.line} style={{ width: 48 }} />
|
75
|
+
</Space>
|
76
|
+
</div>
|
77
|
+
);
|
78
|
+
}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
2
|
+
import { Block, Icon } from '@lobehub/ui';
|
3
|
+
import { Form as AForm, Button, FormInstance, Typography } from 'antd';
|
4
|
+
import { useTheme } from 'antd-style';
|
5
|
+
import { FileCode } from 'lucide-react';
|
6
|
+
import { memo } from 'react';
|
7
|
+
import { useTranslation } from 'react-i18next';
|
8
|
+
import { Flexbox } from 'react-layout-kit';
|
9
|
+
|
10
|
+
import ManifestPreviewer from '@/components/ManifestPreviewer';
|
11
|
+
import PluginAvatar from '@/features/PluginStore/PluginItem/PluginAvatar';
|
12
|
+
import PluginTag from '@/features/PluginStore/PluginItem/PluginTag';
|
13
|
+
import { pluginHelpers } from '@/store/tool';
|
14
|
+
|
15
|
+
import ApiVisualizer from './ApiVisualizer';
|
16
|
+
import PluginEmptyState from './EmptyState';
|
17
|
+
|
18
|
+
const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
|
19
|
+
const { t } = useTranslation('plugin');
|
20
|
+
const theme = useTheme();
|
21
|
+
const manifest: LobeChatPluginManifest = AForm.useWatch(['manifest'], form);
|
22
|
+
const meta = manifest?.meta;
|
23
|
+
|
24
|
+
if (!manifest)
|
25
|
+
return (
|
26
|
+
<Flexbox flex={2} height={'100%'} style={{ background: theme.colorBgLayout }}>
|
27
|
+
<PluginEmptyState />
|
28
|
+
</Flexbox>
|
29
|
+
);
|
30
|
+
|
31
|
+
return (
|
32
|
+
<Flexbox
|
33
|
+
flex={2}
|
34
|
+
gap={24}
|
35
|
+
padding={12}
|
36
|
+
style={{ background: theme.colorBgLayout, overflowY: 'auto' }}
|
37
|
+
>
|
38
|
+
<Block
|
39
|
+
gap={16}
|
40
|
+
horizontal
|
41
|
+
justify={'space-between'}
|
42
|
+
padding={16}
|
43
|
+
title={t('dev.preview.card')}
|
44
|
+
variant={'outlined'}
|
45
|
+
>
|
46
|
+
<Flexbox gap={16} horizontal>
|
47
|
+
<PluginAvatar avatar={pluginHelpers.getPluginAvatar(meta)} size={40} />
|
48
|
+
<Flexbox gap={2}>
|
49
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
50
|
+
{pluginHelpers.getPluginTitle(meta) || 'Plugin Title'}
|
51
|
+
<PluginTag type={'customPlugin'} />
|
52
|
+
</Flexbox>
|
53
|
+
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
|
54
|
+
{pluginHelpers.getPluginDesc(meta) || 'Plugin Description'}
|
55
|
+
</Typography.Text>
|
56
|
+
</Flexbox>
|
57
|
+
</Flexbox>
|
58
|
+
|
59
|
+
{manifest && (
|
60
|
+
<ManifestPreviewer manifest={manifest}>
|
61
|
+
<Flexbox>
|
62
|
+
<Button icon={<Icon icon={FileCode} />}>{t('dev.mcp.previewManifest')}</Button>
|
63
|
+
</Flexbox>
|
64
|
+
</ManifestPreviewer>
|
65
|
+
)}
|
66
|
+
</Block>
|
67
|
+
{manifest && <ApiVisualizer apis={manifest.api as any} />}
|
68
|
+
</Flexbox>
|
69
|
+
);
|
70
|
+
});
|
71
|
+
|
72
|
+
export default PluginPreview;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Alert, Button,
|
1
|
+
import { Alert, Button, Drawer, Icon, Segmented, Tag } from '@lobehub/ui';
|
2
2
|
import { App, Form, Popconfirm } from 'antd';
|
3
3
|
import { useResponsive } from 'antd-style';
|
4
4
|
import { MoveUpRight } from 'lucide-react';
|
@@ -29,7 +29,9 @@ const DevModal = memo<DevModalProps>(
|
|
29
29
|
const [configMode, setConfigMode] = useState<'url' | 'mcp'>('mcp');
|
30
30
|
const { t } = useTranslation('plugin');
|
31
31
|
const { message } = App.useApp();
|
32
|
+
|
32
33
|
const [submitting, setSubmitting] = useState(false);
|
34
|
+
|
33
35
|
const { mobile } = useResponsive();
|
34
36
|
const [form] = Form.useForm();
|
35
37
|
useEffect(() => {
|
@@ -106,83 +108,94 @@ const DevModal = memo<DevModalProps>(
|
|
106
108
|
onOpenChange(false);
|
107
109
|
}}
|
108
110
|
>
|
109
|
-
<
|
110
|
-
|
111
|
+
<Drawer
|
112
|
+
containerMaxWidth={'auto'}
|
111
113
|
destroyOnClose
|
112
114
|
footer={footer}
|
113
|
-
|
114
|
-
|
115
|
+
height={'100vh'}
|
116
|
+
onClose={(e) => {
|
115
117
|
e.stopPropagation();
|
116
118
|
onOpenChange(false);
|
117
119
|
}}
|
118
|
-
onOk={(e) => {
|
119
|
-
e.stopPropagation();
|
120
|
-
form.submit();
|
121
|
-
}}
|
122
120
|
open={open}
|
121
|
+
placement={'bottom'}
|
122
|
+
push={false}
|
123
|
+
styles={{
|
124
|
+
body: {
|
125
|
+
padding: 0,
|
126
|
+
},
|
127
|
+
bodyContent: {
|
128
|
+
height: '100%',
|
129
|
+
},
|
130
|
+
}}
|
123
131
|
title={t(isEditMode ? 'dev.title.edit' : 'dev.title.create')}
|
132
|
+
width={mobile ? '100%' : 800}
|
124
133
|
>
|
125
134
|
<Flexbox
|
126
|
-
gap={
|
135
|
+
gap={0}
|
136
|
+
height={'100%'}
|
137
|
+
horizontal
|
127
138
|
onClick={(e) => {
|
128
139
|
e.stopPropagation();
|
129
140
|
}}
|
130
141
|
>
|
131
|
-
<
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
{
|
138
|
-
|
139
|
-
|
140
|
-
{
|
141
|
-
|
142
|
-
<
|
143
|
-
{
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
142
|
+
<Flexbox flex={3} gap={16} padding={24} style={{ overflowY: 'auto' }}>
|
143
|
+
<Segmented
|
144
|
+
block
|
145
|
+
onChange={(e) => {
|
146
|
+
setConfigMode(e as 'url' | 'mcp');
|
147
|
+
}}
|
148
|
+
options={[
|
149
|
+
{
|
150
|
+
label: (
|
151
|
+
<Flexbox align={'center'} gap={4} horizontal justify={'center'}>
|
152
|
+
{t('dev.manifest.mode.mcp')}
|
153
|
+
<div>
|
154
|
+
<Tag bordered={false} color={'warning'}>
|
155
|
+
{t('dev.manifest.mode.mcpExp')}
|
156
|
+
</Tag>
|
157
|
+
</div>
|
158
|
+
</Flexbox>
|
159
|
+
),
|
160
|
+
value: 'mcp',
|
161
|
+
},
|
162
|
+
{
|
163
|
+
label: t('dev.manifest.mode.url'),
|
164
|
+
value: 'url',
|
165
|
+
},
|
166
|
+
]}
|
167
|
+
value={configMode}
|
168
|
+
variant={'filled'}
|
169
|
+
/>
|
158
170
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
171
|
+
{configMode === 'url' && (
|
172
|
+
<>
|
173
|
+
<Alert
|
174
|
+
message={
|
175
|
+
<Trans i18nKey={'dev.modalDesc'} ns={'plugin'}>
|
176
|
+
添加自定义插件后,可用于插件开发验证,也可直接在会话中使用。插件开发文档请参考:
|
177
|
+
<a
|
178
|
+
href={WIKI_PLUGIN_GUIDE}
|
179
|
+
rel="noreferrer"
|
180
|
+
style={{ paddingInline: 8 }}
|
181
|
+
target={'_blank'}
|
182
|
+
>
|
183
|
+
文档
|
184
|
+
</a>
|
185
|
+
<Icon icon={MoveUpRight} />
|
186
|
+
</Trans>
|
187
|
+
}
|
188
|
+
showIcon
|
189
|
+
type={'info'}
|
190
|
+
/>
|
191
|
+
<UrlManifestForm form={form} isEditMode={isEditMode} />
|
192
|
+
</>
|
193
|
+
)}
|
194
|
+
{configMode === 'mcp' && <MCPManifestForm form={form} isEditMode={isEditMode} />}
|
195
|
+
</Flexbox>
|
183
196
|
<PluginPreview form={form} />
|
184
197
|
</Flexbox>
|
185
|
-
</
|
198
|
+
</Drawer>
|
186
199
|
</Form.Provider>
|
187
200
|
);
|
188
201
|
},
|
@@ -1,4 +1,5 @@
|
|
1
1
|
export default {
|
2
|
+
confirm: '确定',
|
2
3
|
debug: {
|
3
4
|
arguments: '调用参数',
|
4
5
|
function_call: '函数调用',
|
@@ -80,6 +81,13 @@ export default {
|
|
80
81
|
required: '请输入 MCP 服务标识符',
|
81
82
|
},
|
82
83
|
previewManifest: '预览插件描述文件',
|
84
|
+
quickImport: '快速导入 JSON 配置',
|
85
|
+
quickImportError: {
|
86
|
+
empty: '输入内容不能为空',
|
87
|
+
invalidJson: '无效的 JSON 格式',
|
88
|
+
invalidStructure: 'JSON 格式无效',
|
89
|
+
},
|
90
|
+
stdioNotSupported: '当前环境不支持 stdio 类型的 MCP 插件',
|
83
91
|
testConnection: '测试连接',
|
84
92
|
testConnectionTip: '测试连接成功后 MCP 插件才可以被正常使用',
|
85
93
|
type: {
|
@@ -148,8 +156,18 @@ export default {
|
|
148
156
|
schema: 'Schema',
|
149
157
|
},
|
150
158
|
preview: {
|
159
|
+
api: {
|
160
|
+
noParams: '该工具没有参数',
|
161
|
+
noResults: '未找到符合搜索条件的 API',
|
162
|
+
params: '参数:',
|
163
|
+
searchPlaceholder: '搜索工具...',
|
164
|
+
},
|
151
165
|
card: '预览插件展示效果',
|
152
166
|
desc: '预览插件描述',
|
167
|
+
empty: {
|
168
|
+
desc: '完成配置后,将能够在此处预览插件支持的工具能力',
|
169
|
+
title: '配置插件后开始预览',
|
170
|
+
},
|
153
171
|
title: '插件名称预览',
|
154
172
|
},
|
155
173
|
save: '安装插件',
|
@@ -1,34 +0,0 @@
|
|
1
|
-
import { Block } from '@lobehub/ui';
|
2
|
-
import { Form as AForm, FormInstance, Typography } from 'antd';
|
3
|
-
import { memo } from 'react';
|
4
|
-
import { useTranslation } from 'react-i18next';
|
5
|
-
import { Flexbox } from 'react-layout-kit';
|
6
|
-
|
7
|
-
import PluginAvatar from '@/features/PluginStore/PluginItem/PluginAvatar';
|
8
|
-
import PluginTag from '@/features/PluginStore/PluginItem/PluginTag';
|
9
|
-
import { pluginHelpers } from '@/store/tool';
|
10
|
-
import { LobeToolCustomPlugin } from '@/types/tool/plugin';
|
11
|
-
|
12
|
-
const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
|
13
|
-
const { t } = useTranslation('plugin');
|
14
|
-
|
15
|
-
const plugin: LobeToolCustomPlugin = AForm.useWatch([], form);
|
16
|
-
const meta = plugin?.manifest?.meta;
|
17
|
-
|
18
|
-
return (
|
19
|
-
<Block gap={16} horizontal padding={16} title={t('dev.preview.card')} variant={'outlined'}>
|
20
|
-
<PluginAvatar avatar={pluginHelpers.getPluginAvatar(meta)} size={40} />
|
21
|
-
<Flexbox gap={2}>
|
22
|
-
<Flexbox align={'center'} gap={8} horizontal>
|
23
|
-
{pluginHelpers.getPluginTitle(meta) || 'Plugin Title'}
|
24
|
-
<PluginTag type={'customPlugin'} />
|
25
|
-
</Flexbox>
|
26
|
-
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
|
27
|
-
{pluginHelpers.getPluginDesc(meta) || 'Plugin Description'}
|
28
|
-
</Typography.Text>
|
29
|
-
</Flexbox>
|
30
|
-
</Block>
|
31
|
-
);
|
32
|
-
});
|
33
|
-
|
34
|
-
export default PluginPreview;
|