@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.
@@ -1,4 +1,5 @@
1
1
  {
2
+ "confirm": "Xác nhận",
2
3
  "debug": {
3
4
  "arguments": "Tham số gọi",
4
5
  "function_call": "Gọi hàm",
@@ -80,6 +81,13 @@
80
81
  "required": "Vui lòng nhập định danh dịch vụ MCP"
81
82
  },
82
83
  "previewManifest": "Xem trước tệp mô tả plugin",
84
+ "quickImport": "Nhập khẩu nhanh cấu hình JSON",
85
+ "quickImportError": {
86
+ "empty": "Nội dung nhập vào không được để trống",
87
+ "invalidJson": "Định dạng JSON không hợp lệ",
88
+ "invalidStructure": "Định dạng JSON không hợp lệ"
89
+ },
90
+ "stdioNotSupported": "Môi trường hiện tại không hỗ trợ plugin MCP loại stdio",
83
91
  "testConnection": "Kiểm tra kết nối",
84
92
  "testConnectionTip": "Chỉ sau khi kiểm tra kết nối thành công, plugin MCP mới có thể được sử dụng bình thường",
85
93
  "type": {
@@ -147,8 +155,18 @@
147
155
  "schema": "Schema"
148
156
  },
149
157
  "preview": {
158
+ "api": {
159
+ "noParams": "Công cụ này không có tham số",
160
+ "noResults": "Không tìm thấy API nào phù hợp với điều kiện tìm kiếm",
161
+ "params": "Tham số:",
162
+ "searchPlaceholder": "Tìm kiếm công cụ..."
163
+ },
150
164
  "card": "Xem trước hiệu ứng plugin",
151
165
  "desc": "Xem trước mô tả plugin",
166
+ "empty": {
167
+ "desc": "Sau khi hoàn thành cấu hình, bạn sẽ có thể xem trước khả năng của các công cụ hỗ trợ plugin tại đây",
168
+ "title": "Bắt đầu xem trước sau khi cấu hình plugin"
169
+ },
152
170
  "title": "Xem trước tên plugin"
153
171
  },
154
172
  "save": "Cài đặt plugin",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "confirm": "确定",
2
3
  "debug": {
3
4
  "arguments": "调用参数",
4
5
  "function_call": "函数调用",
@@ -80,6 +81,13 @@
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": {
@@ -147,8 +155,18 @@
147
155
  "schema": "Schema"
148
156
  },
149
157
  "preview": {
158
+ "api": {
159
+ "noParams": "该工具没有参数",
160
+ "noResults": "未找到符合搜索条件的 API",
161
+ "params": "参数:",
162
+ "searchPlaceholder": "搜索工具..."
163
+ },
150
164
  "card": "预览插件展示效果",
151
165
  "desc": "预览插件描述",
166
+ "empty": {
167
+ "desc": "完成配置后,将能够在此处预览插件支持的工具能力",
168
+ "title": "配置插件后开始预览"
169
+ },
152
170
  "title": "插件名称预览"
153
171
  },
154
172
  "save": "安装插件",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "confirm": "確定",
2
3
  "debug": {
3
4
  "arguments": "參數",
4
5
  "function_call": "函式呼叫",
@@ -80,6 +81,13 @@
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": {
@@ -147,8 +155,18 @@
147
155
  "schema": "Schema"
148
156
  },
149
157
  "preview": {
158
+ "api": {
159
+ "noParams": "該工具沒有參數",
160
+ "noResults": "未找到符合搜索條件的 API",
161
+ "params": "參數:",
162
+ "searchPlaceholder": "搜索工具..."
163
+ },
150
164
  "card": "外掛顯示預覽",
151
165
  "desc": "外掛描述預覽",
166
+ "empty": {
167
+ "desc": "完成配置後,將能夠在此處預覽插件支持的工具能力",
168
+ "title": "配置插件後開始預覽"
169
+ },
152
170
  "title": "外掛名稱預覽"
153
171
  },
154
172
  "save": "安裝插件",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.84.3",
3
+ "version": "1.84.4",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -146,7 +146,7 @@
146
146
  "@lobehub/chat-plugins-gateway": "^1.9.0",
147
147
  "@lobehub/icons": "^2.0.0",
148
148
  "@lobehub/tts": "^2.0.0",
149
- "@lobehub/ui": "^2.0.5",
149
+ "@lobehub/ui": "^2.0.6",
150
150
  "@modelcontextprotocol/sdk": "^1.10.1",
151
151
  "@neondatabase/serverless": "^1.0.0",
152
152
  "@next/third-parties": "^15.3.0",
@@ -7,14 +7,12 @@ import {
7
7
  SiPython,
8
8
  } from '@icons-pack/react-simple-icons';
9
9
  import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
10
- import { Alert, AutoComplete, Button, FormItem, Icon, Input } from '@lobehub/ui';
11
- import { Form, FormInstance } from 'antd';
12
- import { FileCode } from 'lucide-react';
13
- import { ChangeEvent, FC, useState } from 'react';
10
+ import { Alert, AutoComplete, FormItem, Input, TextArea } from '@lobehub/ui';
11
+ import { Button, Form, FormInstance } from 'antd';
12
+ import { FC, useState } from 'react';
14
13
  import { useTranslation } from 'react-i18next';
15
14
  import { Flexbox } from 'react-layout-kit';
16
15
 
17
- import ManifestPreviewer from '@/components/ManifestPreviewer';
18
16
  import { isDesktop } from '@/const/version';
19
17
  import { mcpService } from '@/services/mcp';
20
18
  import { useToolStore } from '@/store/tool';
@@ -58,57 +56,81 @@ const MCP_TYPE = ['customParams', 'mcp', 'type'];
58
56
  const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
59
57
  const { t } = useTranslation('plugin');
60
58
  const mcpType = Form.useWatch(MCP_TYPE, form);
61
- const [manifest, setManifest] = useState<LobeChatPluginManifest>();
59
+
62
60
  const pluginIds = useToolStore(pluginSelectors.storeAndInstallPluginsIdList);
63
- const [pasteError, setPasteError] = useState<string | null>(null);
64
61
  const [isTesting, setIsTesting] = useState(false);
65
62
  const [connectionError, setConnectionError] = useState<string | null>(null);
63
+ const [isImportModalVisible, setIsImportModalVisible] = useState(false);
64
+ const [jsonInput, setJsonInput] = useState('');
65
+ const [importError, setImportError] = useState<string | null>(null);
66
66
 
67
- const handleIdentifierChange = (e: ChangeEvent<HTMLInputElement>) => {
68
- const value = e.target.value.trim();
69
- setPasteError(null); // Clear previous errors on new input
70
- setConnectionError(null); // Clear connection error on identifier change
67
+ const handleImportConfirm = () => {
68
+ setImportError(null); // Clear previous import error
69
+ setConnectionError(null); // Clear connection error
71
70
 
71
+ const value = jsonInput.trim(); // Use the text area input
72
+ if (!value) {
73
+ setImportError(t('dev.mcp.quickImportError.empty'));
74
+ return;
75
+ }
76
+
77
+ // Use the existing parseMcpInput function
72
78
  const parseResult = parseMcpInput(value);
73
79
 
74
- if (parseResult.status !== 'success') return;
80
+ // Handle parsing errors from parseMcpInput
81
+ if (parseResult.status === 'error') {
82
+ // Assuming parseMcpInput returns an error message or code in parseResult
83
+ // We might need a more specific error message based on parseResult.error
84
+ setImportError(parseResult.errorCode);
85
+ return;
86
+ }
87
+
88
+ if (parseResult.status === 'noop') {
89
+ setImportError(t('dev.mcp.quickImportError.invalidJson'));
90
+ return;
91
+ }
75
92
 
93
+ // Extract identifier and mcpConfig from the successful parse result
76
94
  const { identifier, mcpConfig } = parseResult;
77
95
 
96
+ // Check for desktop requirement for stdio
78
97
  if (!isDesktop && mcpConfig.type === 'stdio') {
98
+ setImportError(t('dev.mcp.stdioNotSupported'));
79
99
  return;
80
100
  }
81
101
 
82
102
  // Check for duplicate identifier (only in create mode)
83
103
  if (!isEditMode && pluginIds.includes(identifier)) {
84
- setPasteError(t('dev.meta.identifier.errorDuplicate'));
85
104
  // Update form fields even if duplicate, so user sees the pasted values
86
105
  form.setFieldsValue({
87
- // Update identifier field
88
- customParams: {
89
- mcp: mcpConfig, // Spread the parsed config (includes type)
90
- },
106
+ customParams: { mcp: mcpConfig },
91
107
  identifier: identifier,
92
108
  });
93
109
  // Trigger validation to show Form.Item error
94
110
  form.validateFields(['identifier']);
111
+ setIsImportModalVisible(false); // Close modal even on duplicate error
112
+ setJsonInput(''); // Clear modal input
95
113
  return;
96
114
  }
97
115
 
98
- // No duplicate or in edit mode, fill the form
116
+ // All checks passed, fill the form
99
117
  form.setFieldsValue({
100
118
  customParams: { mcp: mcpConfig },
101
119
  identifier: identifier,
102
120
  });
103
121
 
104
- // Clear potential old validation error on identifier
122
+ // Clear potential old validation error on identifier field
105
123
  form.setFields([{ errors: [], name: 'identifier' }]);
124
+
125
+ // Clear modal state and close (or rather, hide the import UI)
126
+ setIsImportModalVisible(false);
127
+ // setJsonInput(''); // Keep input for potential edits?
128
+ setImportError(null);
106
129
  };
107
130
 
108
131
  const handleTestConnection = async () => {
109
132
  setIsTesting(true);
110
133
  setConnectionError(null);
111
- setManifest(undefined); // Reset manifest before testing
112
134
 
113
135
  // Manually trigger validation for fields needed for the test
114
136
  let isValid = false;
@@ -142,7 +164,6 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
142
164
  throw new Error('Invalid MCP type'); // Internal error
143
165
  }
144
166
 
145
- setManifest(data);
146
167
  // Optionally update form if manifest ID differs or to store the fetched manifest
147
168
  // Be careful about overwriting user input if not desired
148
169
  form.setFieldsValue({ manifest: data });
@@ -165,138 +186,184 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
165
186
  };
166
187
 
167
188
  return (
168
- <Form form={form} layout={'vertical'}>
169
- <Flexbox>
170
- <Form.Item
171
- label={t('dev.mcp.type.title')}
172
- name={['customParams', 'mcp', 'type']}
173
- rules={[{ required: true }]}
174
- >
175
- <MCPTypeSelect />
176
- </Form.Item>
177
- {/* 仅在有粘贴相关错误时显示 Alert */}
178
- {pasteError && (
179
- <Alert message={pasteError} showIcon style={{ marginBottom: 16 }} type="error" />
180
- )}
181
- <FormItem
182
- desc={t('dev.mcp.identifier.desc')}
183
- label={t('dev.mcp.identifier.label')}
184
- name={'identifier'}
185
- rules={[
186
- { message: t('dev.mcp.identifier.required'), required: true },
187
- {
188
- message: t('dev.mcp.identifier.invalid'),
189
- pattern: /^[\w-]+$/,
190
- },
191
- isEditMode
192
- ? {}
193
- : {
194
- message: t('dev.meta.identifier.errorDuplicate'),
195
- validator: async () => {
196
- const id = form.getFieldValue('identifier');
197
- if (!id) return true;
198
- if (pluginIds.includes(id)) {
199
- throw new Error('Duplicate');
200
- }
201
- },
202
- },
203
- ]}
204
- tag={'identifier'}
205
- >
206
- <Input
207
- onChange={handleIdentifierChange}
208
- placeholder={t('dev.mcp.identifier.placeholder')}
189
+ <>
190
+ {isImportModalVisible ? (
191
+ <Flexbox gap={8}>
192
+ {importError && (
193
+ <Alert message={importError} showIcon style={{ marginBottom: 8 }} type="error" />
194
+ )}
195
+ <TextArea
196
+ autoSize={{ maxRows: 15, minRows: 10 }}
197
+ onChange={(e) => {
198
+ setJsonInput(e.target.value);
199
+ if (importError) setImportError(null);
200
+ }}
201
+ placeholder={`{
202
+ "mcpServers": {
203
+ "github": {
204
+ "command": "npx",
205
+ "args": [
206
+ "-y",
207
+ "@modelcontextprotocol/server-github"
208
+ ],
209
+ "env": {
210
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "<your-api-key>"
211
+ }
212
+ }
213
+ }
214
+ }`}
215
+ value={jsonInput}
209
216
  />
210
- </FormItem>
217
+ <Flexbox horizontal justify={'space-between'}>
218
+ <Button
219
+ onClick={() => {
220
+ setIsImportModalVisible(false);
221
+ }}
222
+ size={'small'}
223
+ >
224
+ 取消
225
+ </Button>
226
+ <Button onClick={handleImportConfirm} size={'small'} type={'primary'}>
227
+ 导入
228
+ </Button>
229
+ </Flexbox>
230
+ </Flexbox>
231
+ ) : (
232
+ <div>
233
+ <Button
234
+ block // Make button full width
235
+ onClick={() => {
236
+ setImportError(null); // Clear previous errors when opening
237
+ setIsImportModalVisible(true);
238
+ }}
239
+ style={{ marginBottom: 16 }} // Add some spacing
240
+ type="dashed"
241
+ >
242
+ {t('dev.mcp.quickImport')}
243
+ </Button>
244
+ </div>
245
+ )}
246
+
247
+ <Form form={form} layout={'vertical'}>
248
+ <Flexbox>
249
+ <Form.Item
250
+ label={t('dev.mcp.type.title')}
251
+ name={['customParams', 'mcp', 'type']}
252
+ rules={[{ required: true }]}
253
+ >
254
+ <MCPTypeSelect />
255
+ </Form.Item>
211
256
 
212
- {mcpType === 'http' && (
213
257
  <FormItem
214
- desc={t('dev.mcp.url.desc')}
215
- label={t('dev.mcp.url.label')}
216
- name={HTTP_URL_KEY}
258
+ desc={t('dev.mcp.identifier.desc')}
259
+ label={t('dev.mcp.identifier.label')}
260
+ name={'identifier'}
217
261
  rules={[
218
- { message: t('dev.mcp.url.required'), required: true },
219
- { message: t('dev.mcp.url.invalid'), type: 'url' },
262
+ { message: t('dev.mcp.identifier.required'), required: true },
263
+ {
264
+ message: t('dev.mcp.identifier.invalid'),
265
+ pattern: /^[\w-]+$/,
266
+ },
267
+ isEditMode
268
+ ? {}
269
+ : {
270
+ message: t('dev.meta.identifier.errorDuplicate'),
271
+ validator: async () => {
272
+ const id = form.getFieldValue('identifier');
273
+ if (!id) return true;
274
+ if (pluginIds.includes(id)) {
275
+ throw new Error('Duplicate');
276
+ }
277
+ },
278
+ },
220
279
  ]}
221
- tag={'url'}
280
+ tag={'identifier'}
222
281
  >
223
- <Input placeholder="https://mcp.higress.ai/mcp-github/xxxxx" />
282
+ <Input placeholder={t('dev.mcp.identifier.placeholder')} />
224
283
  </FormItem>
225
- )}
226
284
 
227
- {mcpType === 'stdio' && (
228
- <>
229
- <FormItem
230
- desc={t('dev.mcp.command.desc')}
231
- label={t('dev.mcp.command.label')}
232
- name={STDIO_COMMAND}
233
- rules={[{ message: t('dev.mcp.command.required'), required: true }]}
234
- tag={'command'}
235
- >
236
- <AutoComplete
237
- options={STDIO_COMMAND_OPTIONS.map(({ value, icon: Icon, color }) => ({
238
- label: (
239
- <Flexbox align={'center'} gap={8} horizontal>
240
- {Icon && <Icon color={color} size={16} />}
241
- {value}
242
- </Flexbox>
243
- ),
244
- value: value,
245
- }))}
246
- placeholder={t('dev.mcp.command.placeholder')}
247
- />
248
- </FormItem>
249
- <FormItem
250
- desc={t('dev.mcp.args.desc')}
251
- label={t('dev.mcp.args.label')}
252
- name={STDIO_ARGS}
253
- rules={[{ message: t('dev.mcp.args.required'), required: true }]}
254
- tag={'args'}
255
- >
256
- <ArgsInput placeholder={t('dev.mcp.args.placeholder')} />
257
- </FormItem>
285
+ {mcpType === 'http' && (
258
286
  <FormItem
259
- extra={t('dev.mcp.env.desc')}
260
- label={t('dev.mcp.env.label')}
261
- name={STDIO_ENV}
262
- tag={'env'}
287
+ desc={t('dev.mcp.url.desc')}
288
+ label={t('dev.mcp.url.label')}
289
+ name={HTTP_URL_KEY}
290
+ rules={[
291
+ { message: t('dev.mcp.url.required'), required: true },
292
+ { message: t('dev.mcp.url.invalid'), type: 'url' },
293
+ ]}
294
+ tag={'url'}
263
295
  >
264
- <EnvEditor />
296
+ <Input placeholder="https://mcp.higress.ai/mcp-github/xxxxx" />
265
297
  </FormItem>
266
- </>
267
- )}
268
- <FormItem colon={false} label={t('dev.mcp.testConnectionTip')} layout={'horizontal'}>
269
- <Flexbox align={'center'} gap={8} horizontal justify={'flex-end'}>
270
- {manifest && !connectionError && !isTesting && (
271
- <ManifestPreviewer manifest={manifest}>
272
- <Flexbox>
273
- <Button icon={<Icon icon={FileCode} />}>{t('dev.mcp.previewManifest')}</Button>
274
- </Flexbox>
275
- </ManifestPreviewer>
276
- )}
277
- <Button
278
- loading={isTesting}
279
- onClick={handleTestConnection}
280
- type={!!mcpType ? 'primary' : undefined}
281
- >
282
- {t('dev.mcp.testConnection')}
283
- </Button>
284
- </Flexbox>
285
- </FormItem>
286
-
287
- {connectionError && (
288
- <Alert
289
- closable
290
- message={connectionError}
291
- onClose={() => setConnectionError(null)}
292
- showIcon
293
- style={{ marginBottom: 16 }}
294
- type="error"
295
- />
296
- )}
297
- <FormItem name={'manifest'} noStyle />
298
- </Flexbox>
299
- </Form>
298
+ )}
299
+
300
+ {mcpType === 'stdio' && (
301
+ <>
302
+ <FormItem
303
+ desc={t('dev.mcp.command.desc')}
304
+ label={t('dev.mcp.command.label')}
305
+ name={STDIO_COMMAND}
306
+ rules={[{ message: t('dev.mcp.command.required'), required: true }]}
307
+ tag={'command'}
308
+ >
309
+ <AutoComplete
310
+ options={STDIO_COMMAND_OPTIONS.map(({ value, icon: Icon, color }) => ({
311
+ label: (
312
+ <Flexbox align={'center'} gap={8} horizontal>
313
+ {Icon && <Icon color={color} size={16} />}
314
+ {value}
315
+ </Flexbox>
316
+ ),
317
+ value: value,
318
+ }))}
319
+ placeholder={t('dev.mcp.command.placeholder')}
320
+ />
321
+ </FormItem>
322
+ <FormItem
323
+ desc={t('dev.mcp.args.desc')}
324
+ label={t('dev.mcp.args.label')}
325
+ name={STDIO_ARGS}
326
+ rules={[{ message: t('dev.mcp.args.required'), required: true }]}
327
+ tag={'args'}
328
+ >
329
+ <ArgsInput placeholder={t('dev.mcp.args.placeholder')} />
330
+ </FormItem>
331
+ <FormItem
332
+ extra={t('dev.mcp.env.desc')}
333
+ label={t('dev.mcp.env.label')}
334
+ name={STDIO_ENV}
335
+ tag={'env'}
336
+ >
337
+ <EnvEditor />
338
+ </FormItem>
339
+ </>
340
+ )}
341
+ <FormItem colon={false} label={t('dev.mcp.testConnectionTip')} layout={'horizontal'}>
342
+ <Flexbox align={'center'} gap={8} horizontal justify={'flex-end'}>
343
+ <Button
344
+ loading={isTesting}
345
+ onClick={handleTestConnection}
346
+ type={!!mcpType ? 'primary' : undefined}
347
+ >
348
+ {t('dev.mcp.testConnection')}
349
+ </Button>
350
+ </Flexbox>
351
+ </FormItem>
352
+
353
+ {connectionError && (
354
+ <Alert
355
+ closable
356
+ message={connectionError}
357
+ onClose={() => setConnectionError(null)}
358
+ showIcon
359
+ style={{ marginBottom: 16 }}
360
+ type="error"
361
+ />
362
+ )}
363
+ <FormItem name={'manifest'} noStyle />
364
+ </Flexbox>
365
+ </Form>
366
+ </>
300
367
  );
301
368
  };
302
369