@lobehub/chat 1.109.1 → 1.110.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/.cursor/rules/i18n.mdc +1 -2
- package/CHANGELOG.md +25 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/apps/desktop/electron-builder.js +22 -0
- package/apps/desktop/src/main/controllers/McpInstallCtr.ts +153 -0
- package/apps/desktop/src/main/controllers/index.ts +19 -0
- package/apps/desktop/src/main/core/App.ts +46 -0
- package/apps/desktop/src/main/core/infrastructure/IoCContainer.ts +4 -0
- package/apps/desktop/src/main/core/infrastructure/ProtocolManager.ts +256 -0
- package/apps/desktop/src/main/types/protocol.ts +60 -0
- package/apps/desktop/src/main/utils/__tests__/protocol.test.ts +203 -0
- package/apps/desktop/src/main/utils/protocol.ts +210 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/plugin.json +196 -136
- package/locales/bg-BG/plugin.json +204 -144
- package/locales/de-DE/plugin.json +176 -116
- package/locales/en-US/plugin.json +192 -132
- package/locales/es-ES/plugin.json +203 -143
- package/locales/fa-IR/plugin.json +155 -95
- package/locales/fr-FR/plugin.json +161 -101
- package/locales/it-IT/plugin.json +193 -133
- package/locales/ja-JP/plugin.json +195 -135
- package/locales/ko-KR/plugin.json +163 -103
- package/locales/nl-NL/plugin.json +211 -151
- package/locales/pl-PL/plugin.json +171 -111
- package/locales/pt-BR/plugin.json +180 -120
- package/locales/ru-RU/plugin.json +191 -131
- package/locales/tr-TR/plugin.json +187 -127
- package/locales/vi-VN/plugin.json +152 -92
- package/locales/zh-CN/plugin.json +60 -0
- package/locales/zh-TW/plugin.json +157 -97
- package/package.json +2 -1
- package/packages/electron-client-ipc/src/events/index.ts +5 -2
- package/packages/electron-client-ipc/src/events/protocol.ts +29 -0
- package/packages/electron-client-ipc/src/types/index.ts +1 -0
- package/packages/electron-client-ipc/src/types/mcpInstall.ts +19 -0
- package/packages/types/src/plugins/mcp.ts +38 -1
- package/packages/types/src/plugins/protocol.ts +166 -0
- package/src/app/[variants]/(main)/chat/_layout/Desktop/index.tsx +4 -1
- package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/ActionButton/index.tsx +1 -2
- package/src/components/KeyValueEditor/index.tsx +4 -2
- package/src/features/MCP/MCPInstallProgress/index.tsx +1 -1
- package/src/features/PluginDevModal/MCPManifestForm/index.tsx +30 -36
- package/src/features/PluginStore/McpList/List/Item.tsx +1 -1
- package/src/features/ProtocolUrlHandler/InstallPlugin/ConfigDisplay.tsx +211 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/CustomPluginInstallModal.tsx +228 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/Detail.tsx +44 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/index.tsx +105 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/index.tsx +55 -0
- package/src/features/ProtocolUrlHandler/InstallPlugin/types.ts +45 -0
- package/src/features/ProtocolUrlHandler/index.tsx +30 -0
- package/src/locales/default/plugin.ts +60 -0
- package/src/store/tool/slices/mcpStore/action.ts +127 -1
- package/src/store/tool/slices/mcpStore/initialState.ts +8 -13
- package/src/store/tool/slices/mcpStore/selectors.ts +13 -0
@@ -0,0 +1,166 @@
|
|
1
|
+
/**
|
2
|
+
* 协议来源类型
|
3
|
+
*/
|
4
|
+
export enum ProtocolSource {
|
5
|
+
/** 社区贡献 */
|
6
|
+
COMMUNITY = 'community',
|
7
|
+
/** 开发者自定义 */
|
8
|
+
DEVELOPER = 'developer',
|
9
|
+
/** GitHub 官方 */
|
10
|
+
GITHUB_OFFICIAL = 'github_official',
|
11
|
+
/** 官方LobeHub市场 */
|
12
|
+
OFFICIAL = 'official',
|
13
|
+
/** 第三方市场 */
|
14
|
+
THIRD_PARTY = 'third_party',
|
15
|
+
}
|
16
|
+
|
17
|
+
/**
|
18
|
+
* MCP Schema - stdio 配置类型
|
19
|
+
*/
|
20
|
+
export interface McpStdioConfig {
|
21
|
+
args?: string[];
|
22
|
+
command: string;
|
23
|
+
env?: Record<string, string>;
|
24
|
+
type: 'stdio';
|
25
|
+
}
|
26
|
+
|
27
|
+
/**
|
28
|
+
* MCP Schema - http 配置类型
|
29
|
+
*/
|
30
|
+
export interface McpHttpConfig {
|
31
|
+
headers?: Record<string, string>;
|
32
|
+
type: 'http';
|
33
|
+
url: string;
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* MCP Schema 配置类型
|
38
|
+
*/
|
39
|
+
export type McpConfig = McpStdioConfig | McpHttpConfig;
|
40
|
+
|
41
|
+
/**
|
42
|
+
* MCP Schema 对象
|
43
|
+
* 符合 RFC 0001 定义
|
44
|
+
*/
|
45
|
+
export interface McpSchema {
|
46
|
+
/** 插件作者 */
|
47
|
+
author: string;
|
48
|
+
/** 插件配置 */
|
49
|
+
config: McpConfig;
|
50
|
+
/** 插件描述 */
|
51
|
+
description: string;
|
52
|
+
/** 插件主页 */
|
53
|
+
homepage?: string;
|
54
|
+
/** 插件图标 */
|
55
|
+
icon?: string;
|
56
|
+
/** 插件唯一标识符,必须与URL中的id参数匹配 */
|
57
|
+
identifier: string;
|
58
|
+
/** 插件名称 */
|
59
|
+
name: string;
|
60
|
+
/** 插件版本 (semver) */
|
61
|
+
version: string;
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* RFC 0001 协议参数
|
66
|
+
* lobehub://plugin/install?id=xxx&schema=xxx&marketId=xxx&meta_*=xxx
|
67
|
+
*/
|
68
|
+
export interface McpInstallProtocolParamsRFC {
|
69
|
+
/** 可选的 UI 显示元数据,以 meta_ 为前缀 */
|
70
|
+
[key: `meta_${string}`]: string | undefined;
|
71
|
+
/** 插件的唯一标识符 */
|
72
|
+
id: string;
|
73
|
+
/** 提供该插件的 Marketplace 的唯一标识符 */
|
74
|
+
marketId?: string;
|
75
|
+
/** Base64URL 编码的 MCP Schema 对象 */
|
76
|
+
schema: string;
|
77
|
+
/** 插件类型,对于 MCP 固定为 'mcp' */
|
78
|
+
type: 'mcp';
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* 协议URL解析结果
|
83
|
+
*/
|
84
|
+
export interface ProtocolUrlParsed {
|
85
|
+
/** 操作类型 (如: 'install') */
|
86
|
+
action: 'install' | 'configure' | 'update';
|
87
|
+
/** 解析后的参数 */
|
88
|
+
params: {
|
89
|
+
id: string;
|
90
|
+
marketId?: string;
|
91
|
+
type: string;
|
92
|
+
};
|
93
|
+
/** MCP Schema 对象 */
|
94
|
+
schema: McpSchema;
|
95
|
+
/** 协议来源 */
|
96
|
+
source: ProtocolSource;
|
97
|
+
/** 插件类型 (如: 'mcp') */
|
98
|
+
type: 'mcp' | 'plugin';
|
99
|
+
/** URL类型 (如: 'plugin') */
|
100
|
+
urlType: string;
|
101
|
+
}
|
102
|
+
|
103
|
+
/**
|
104
|
+
* 安装确认弹窗信息
|
105
|
+
*/
|
106
|
+
export interface InstallConfirmationInfo {
|
107
|
+
dependencies?: string[];
|
108
|
+
permissions?: {
|
109
|
+
filesystem?: boolean;
|
110
|
+
network?: boolean;
|
111
|
+
system?: boolean;
|
112
|
+
};
|
113
|
+
pluginInfo: {
|
114
|
+
author?: string;
|
115
|
+
description: string;
|
116
|
+
homepage?: string;
|
117
|
+
icon?: string;
|
118
|
+
identifier: string;
|
119
|
+
name: string;
|
120
|
+
version: string;
|
121
|
+
};
|
122
|
+
source: {
|
123
|
+
platform?: {
|
124
|
+
name: string;
|
125
|
+
url?: string;
|
126
|
+
};
|
127
|
+
type: ProtocolSource;
|
128
|
+
verified: boolean; // 是否为验证来源
|
129
|
+
};
|
130
|
+
}
|
131
|
+
|
132
|
+
/**
|
133
|
+
* 协议处理器接口
|
134
|
+
*/
|
135
|
+
export interface ProtocolHandler {
|
136
|
+
/**
|
137
|
+
* 处理协议URL
|
138
|
+
*/
|
139
|
+
handle(
|
140
|
+
parsed: ProtocolUrlParsed,
|
141
|
+
): Promise<{ error?: string; success: boolean; targetWindow?: string }>;
|
142
|
+
|
143
|
+
/**
|
144
|
+
* 支持的操作
|
145
|
+
*/
|
146
|
+
readonly supportedActions: string[];
|
147
|
+
|
148
|
+
/**
|
149
|
+
* 协议类型
|
150
|
+
*/
|
151
|
+
readonly type: string;
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* 协议路由配置
|
156
|
+
*/
|
157
|
+
export interface ProtocolRouteConfig {
|
158
|
+
/** 操作类型 */
|
159
|
+
action: string;
|
160
|
+
/** 目标路径(相对于窗口base路径) */
|
161
|
+
targetPath?: string;
|
162
|
+
/** 目标窗口 */
|
163
|
+
targetWindow: 'chat' | 'settings';
|
164
|
+
/** 协议类型 */
|
165
|
+
type: string;
|
166
|
+
}
|
@@ -1,7 +1,9 @@
|
|
1
1
|
import { Suspense } from 'react';
|
2
2
|
import { Flexbox } from 'react-layout-kit';
|
3
3
|
|
4
|
+
import { isDesktop } from '@/const/version';
|
4
5
|
import InitClientDB from '@/features/InitClientDB';
|
6
|
+
import ProtocolUrlHandler from '@/features/ProtocolUrlHandler';
|
5
7
|
|
6
8
|
import { LayoutProps } from '../type';
|
7
9
|
import RegisterHotkeys from './RegisterHotkeys';
|
@@ -20,13 +22,14 @@ const Layout = ({ children, session }: LayoutProps) => {
|
|
20
22
|
<SessionPanel>{session}</SessionPanel>
|
21
23
|
<Workspace>{children}</Workspace>
|
22
24
|
</Flexbox>
|
23
|
-
<InitClientDB bottom={60} />
|
25
|
+
{!isDesktop && <InitClientDB bottom={60} />}
|
24
26
|
{/* ↓ cloud slot ↓ */}
|
25
27
|
|
26
28
|
{/* ↑ cloud slot ↑ */}
|
27
29
|
<Suspense>
|
28
30
|
<RegisterHotkeys />
|
29
31
|
</Suspense>
|
32
|
+
{isDesktop && <ProtocolUrlHandler />}
|
30
33
|
</>
|
31
34
|
);
|
32
35
|
};
|
@@ -22,6 +22,7 @@ const useStyles = createStyles(({ css }) => ({
|
|
22
22
|
}));
|
23
23
|
|
24
24
|
const ActionButton = memo(() => {
|
25
|
+
const { t } = useTranslation(['discover', 'plugin']);
|
25
26
|
const { identifier } = useDetailContext();
|
26
27
|
const { styles } = useStyles();
|
27
28
|
const [isLoading, setIsLoading] = useState(false);
|
@@ -32,8 +33,6 @@ const ActionButton = memo(() => {
|
|
32
33
|
s.uninstallMCPPlugin,
|
33
34
|
]);
|
34
35
|
|
35
|
-
const { t } = useTranslation(['discover', 'plugin']);
|
36
|
-
|
37
36
|
const installPlugin = async () => {
|
38
37
|
if (!identifier) return;
|
39
38
|
setIsLoading(true);
|
@@ -3,7 +3,7 @@ import { Button } from 'antd';
|
|
3
3
|
import { createStyles } from 'antd-style';
|
4
4
|
import fastDeepEqual from 'fast-deep-equal';
|
5
5
|
import { LucidePlus, LucideTrash } from 'lucide-react';
|
6
|
-
import { memo, useEffect, useRef, useState } from 'react';
|
6
|
+
import { CSSProperties, memo, useEffect, useRef, useState } from 'react';
|
7
7
|
import { useTranslation } from 'react-i18next';
|
8
8
|
import { Flexbox } from 'react-layout-kit';
|
9
9
|
import { v4 as uuidv4 } from 'uuid';
|
@@ -44,6 +44,7 @@ export interface KeyValueEditorProps {
|
|
44
44
|
duplicateKeyErrorText?: string;
|
45
45
|
keyPlaceholder?: string;
|
46
46
|
onChange?: (value: Record<string, string>) => void;
|
47
|
+
style?: CSSProperties;
|
47
48
|
value?: Record<string, string>;
|
48
49
|
valuePlaceholder?: string;
|
49
50
|
}
|
@@ -57,6 +58,7 @@ const KeyValueEditor = memo<KeyValueEditorProps>(
|
|
57
58
|
addButtonText,
|
58
59
|
duplicateKeyErrorText,
|
59
60
|
deleteTooltip,
|
61
|
+
style,
|
60
62
|
}) => {
|
61
63
|
const { styles } = useStyles();
|
62
64
|
const { t } = useTranslation('components');
|
@@ -125,7 +127,7 @@ const KeyValueEditor = memo<KeyValueEditorProps>(
|
|
125
127
|
const duplicateKeys = getDuplicateKeys(items);
|
126
128
|
|
127
129
|
return (
|
128
|
-
<div className={styles.container}>
|
130
|
+
<div className={styles.container} style={style}>
|
129
131
|
<Flexbox className={styles.title} gap={8} horizontal>
|
130
132
|
<Flexbox flex={1}>{keyPlaceholder || t('KeyValueEditor.keyPlaceholder')}</Flexbox>
|
131
133
|
<Flexbox flex={2}>{valuePlaceholder || t('KeyValueEditor.valuePlaceholder')}</Flexbox>
|
@@ -9,7 +9,7 @@ import { Flexbox } from 'react-layout-kit';
|
|
9
9
|
|
10
10
|
import { useToolStore } from '@/store/tool';
|
11
11
|
import { mcpStoreSelectors } from '@/store/tool/selectors';
|
12
|
-
import { MCPInstallStep } from '@/
|
12
|
+
import { MCPInstallStep } from '@/types/plugins';
|
13
13
|
|
14
14
|
import InstallError from './InstallError';
|
15
15
|
import MCPConfigForm from './MCPConfigForm';
|
@@ -1,15 +1,14 @@
|
|
1
|
-
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
2
1
|
import { Alert, FormItem, Input, InputPassword } from '@lobehub/ui';
|
3
2
|
import { Button, Divider, Form, FormInstance, Radio } from 'antd';
|
3
|
+
import isEqual from 'fast-deep-equal';
|
4
4
|
import { useState } from 'react';
|
5
5
|
import { useTranslation } from 'react-i18next';
|
6
6
|
import { Flexbox } from 'react-layout-kit';
|
7
7
|
|
8
8
|
import KeyValueEditor from '@/components/KeyValueEditor';
|
9
9
|
import MCPStdioCommandInput from '@/components/MCPStdioCommandInput';
|
10
|
-
import { mcpService } from '@/services/mcp';
|
11
10
|
import { useToolStore } from '@/store/tool';
|
12
|
-
import { pluginSelectors } from '@/store/tool/selectors';
|
11
|
+
import { mcpStoreSelectors, pluginSelectors } from '@/store/tool/selectors';
|
13
12
|
|
14
13
|
import ArgsInput from './ArgsInput';
|
15
14
|
import CollapsibleSection from './CollapsibleSection';
|
@@ -40,6 +39,13 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
|
40
39
|
|
41
40
|
const pluginIds = useToolStore(pluginSelectors.storeAndInstallPluginsIdList);
|
42
41
|
const [isTesting, setIsTesting] = useState(false);
|
42
|
+
const testMcpConnection = useToolStore((s) => s.testMcpConnection);
|
43
|
+
|
44
|
+
// 使用 identifier 来跟踪测试状态(如果表单中有的话)
|
45
|
+
const formValues = form.getFieldsValue();
|
46
|
+
const identifier = formValues?.identifier || 'temp-test-id';
|
47
|
+
const testState = useToolStore(mcpStoreSelectors.getMCPConnectionTestState(identifier), isEqual);
|
48
|
+
|
43
49
|
const [connectionError, setConnectionError] = useState<string | null>(null);
|
44
50
|
|
45
51
|
const handleTestConnection = async () => {
|
@@ -80,43 +86,31 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
|
80
86
|
const description = values.customParams?.description;
|
81
87
|
const avatar = values.customParams?.avatar;
|
82
88
|
|
83
|
-
|
89
|
+
// 使用 mcpStore 的 testMcpConnection 方法
|
90
|
+
const result = await testMcpConnection({
|
91
|
+
connection: mcp,
|
92
|
+
identifier: id,
|
93
|
+
metadata: { avatar, description },
|
94
|
+
});
|
84
95
|
|
85
|
-
if (
|
86
|
-
if
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
96
|
+
if (result.success && result.manifest) {
|
97
|
+
// Optionally update form if manifest ID differs or to store the fetched manifest
|
98
|
+
// Be careful about overwriting user input if not desired
|
99
|
+
form.setFieldsValue({ manifest: result.manifest });
|
100
|
+
setConnectionError(null); // 清除本地错误状态
|
101
|
+
} else if (result.error) {
|
102
|
+
// Store 已经处理了错误状态,这里可以选择显示额外的用户友好提示
|
103
|
+
const errorMessage = t('error.testConnectionFailed', {
|
104
|
+
error: result.error,
|
93
105
|
});
|
94
|
-
|
95
|
-
if (!mcp.command) throw new Error(t('dev.mcp.command.required'));
|
96
|
-
if (!mcp.args) throw new Error(t('dev.mcp.args.required'));
|
97
|
-
data = await mcpService.getStdioMcpServerManifest(
|
98
|
-
{ ...mcp, name: id },
|
99
|
-
{ avatar, description },
|
100
|
-
);
|
101
|
-
} else {
|
102
|
-
throw new Error('Invalid MCP type'); // Internal error
|
106
|
+
setConnectionError(errorMessage);
|
103
107
|
}
|
104
|
-
|
105
|
-
// Optionally update form if manifest ID differs or to store the fetched manifest
|
106
|
-
// Be careful about overwriting user input if not desired
|
107
|
-
form.setFieldsValue({ manifest: data });
|
108
108
|
} catch (error) {
|
109
|
-
//
|
110
|
-
|
111
|
-
// Handle API call errors or other errors
|
112
|
-
const err = error as Error; // Assuming PluginInstallError or similar structure
|
113
|
-
// Use the error message directly if it's a simple string error, otherwise try translation
|
114
|
-
// highlight-start
|
109
|
+
// Handle unexpected errors
|
110
|
+
const err = error as Error;
|
115
111
|
const errorMessage = t('error.testConnectionFailed', {
|
116
|
-
error: err.
|
112
|
+
error: err.message || t('unknownError'),
|
117
113
|
});
|
118
|
-
// highlight-end
|
119
|
-
|
120
114
|
setConnectionError(errorMessage);
|
121
115
|
} finally {
|
122
116
|
setIsTesting(false);
|
@@ -274,10 +268,10 @@ const MCPManifestForm = ({ form, isEditMode }: MCPManifestFormProps) => {
|
|
274
268
|
</Button>
|
275
269
|
</Flexbox>
|
276
270
|
</FormItem>
|
277
|
-
{connectionError && (
|
271
|
+
{(connectionError || testState.error) && (
|
278
272
|
<Alert
|
279
273
|
closable
|
280
|
-
message={connectionError}
|
274
|
+
message={connectionError || testState.error}
|
281
275
|
onClose={() => setConnectionError(null)}
|
282
276
|
showIcon
|
283
277
|
style={{ marginBottom: 16 }}
|
@@ -7,8 +7,8 @@ import PluginAvatar from '@/components/Plugins/PluginAvatar';
|
|
7
7
|
import MCPInstallProgress from '@/features/MCP/MCPInstallProgress';
|
8
8
|
import { useToolStore } from '@/store/tool';
|
9
9
|
import { mcpStoreSelectors } from '@/store/tool/selectors';
|
10
|
-
import { MCPInstallStep } from '@/store/tool/slices/mcpStore/initialState';
|
11
10
|
import { DiscoverMcpItem } from '@/types/discover';
|
11
|
+
import { MCPInstallStep } from '@/types/plugins';
|
12
12
|
import { LobeToolType } from '@/types/tool/tool';
|
13
13
|
|
14
14
|
import Actions from './Action';
|
@@ -0,0 +1,211 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { McpInstallSchema } from '@lobechat/electron-client-ipc';
|
4
|
+
import { Block, Text } from '@lobehub/ui';
|
5
|
+
import { createStyles } from 'antd-style';
|
6
|
+
import { LinkIcon, Settings2Icon } from 'lucide-react';
|
7
|
+
import { memo, useState } from 'react';
|
8
|
+
import { useTranslation } from 'react-i18next';
|
9
|
+
import { Flexbox } from 'react-layout-kit';
|
10
|
+
|
11
|
+
import KeyValueEditor from '@/components/KeyValueEditor';
|
12
|
+
|
13
|
+
const useStyles = createStyles(({ css, token }) => ({
|
14
|
+
configEditor: css`
|
15
|
+
margin-block-start: ${token.marginSM}px;
|
16
|
+
`,
|
17
|
+
configSection: css`
|
18
|
+
margin-block-end: ${token.marginLG}px;
|
19
|
+
padding: ${token.paddingSM}px;
|
20
|
+
border-radius: ${token.borderRadius}px;
|
21
|
+
`,
|
22
|
+
configTitle: css`
|
23
|
+
display: flex;
|
24
|
+
gap: ${token.marginXS}px;
|
25
|
+
align-items: center;
|
26
|
+
|
27
|
+
height: 24px;
|
28
|
+
|
29
|
+
font-weight: 600;
|
30
|
+
color: ${token.colorTextHeading};
|
31
|
+
`,
|
32
|
+
|
33
|
+
previewContainer: css`
|
34
|
+
padding-inline: ${token.paddingXS}px;
|
35
|
+
`,
|
36
|
+
|
37
|
+
previewItem: css`
|
38
|
+
display: flex;
|
39
|
+
align-items: center;
|
40
|
+
justify-content: space-between;
|
41
|
+
|
42
|
+
padding-block: ${token.paddingXS}px;
|
43
|
+
padding-inline: 0;
|
44
|
+
|
45
|
+
&:not(:last-child) {
|
46
|
+
border-block-end: 1px solid ${token.colorBorderSecondary};
|
47
|
+
}
|
48
|
+
`,
|
49
|
+
|
50
|
+
previewLabel: css`
|
51
|
+
display: flex;
|
52
|
+
gap: ${token.marginXS}px;
|
53
|
+
align-items: center;
|
54
|
+
|
55
|
+
font-size: ${token.fontSizeSM}px;
|
56
|
+
font-weight: 500;
|
57
|
+
color: ${token.colorTextSecondary};
|
58
|
+
`,
|
59
|
+
|
60
|
+
previewValue: css`
|
61
|
+
padding-block: ${token.paddingXXS}px;
|
62
|
+
padding-inline: ${token.paddingXS}px;
|
63
|
+
border-radius: ${token.borderRadiusSM}px;
|
64
|
+
|
65
|
+
font-family: ${token.fontFamilyCode};
|
66
|
+
font-size: ${token.fontSizeSM}px;
|
67
|
+
font-weight: 600;
|
68
|
+
color: ${token.colorText};
|
69
|
+
|
70
|
+
background: ${token.colorFillQuaternary};
|
71
|
+
`,
|
72
|
+
|
73
|
+
typeValue: css`
|
74
|
+
display: flex;
|
75
|
+
gap: ${token.marginXS}px;
|
76
|
+
align-items: center;
|
77
|
+
`,
|
78
|
+
|
79
|
+
urlValue: css`
|
80
|
+
max-width: 300px;
|
81
|
+
padding-block: ${token.paddingXS}px;
|
82
|
+
padding-inline: ${token.paddingSM}px;
|
83
|
+
border: 1px solid ${token.colorBorder};
|
84
|
+
border-radius: ${token.borderRadius}px;
|
85
|
+
|
86
|
+
font-family: ${token.fontFamilyCode};
|
87
|
+
font-size: ${token.fontSizeSM}px;
|
88
|
+
font-weight: 500;
|
89
|
+
word-break: auto-phrase;
|
90
|
+
|
91
|
+
background: ${token.colorBgElevated};
|
92
|
+
`,
|
93
|
+
}));
|
94
|
+
|
95
|
+
interface ConfigDisplayProps {
|
96
|
+
onConfigUpdate?: (updatedConfig: {
|
97
|
+
env?: Record<string, string>;
|
98
|
+
headers?: Record<string, string>;
|
99
|
+
}) => void;
|
100
|
+
schema: McpInstallSchema;
|
101
|
+
}
|
102
|
+
|
103
|
+
const ConfigDisplay = memo<ConfigDisplayProps>(({ schema, onConfigUpdate }) => {
|
104
|
+
const { t } = useTranslation('plugin');
|
105
|
+
const { styles } = useStyles();
|
106
|
+
|
107
|
+
// 本地状态管理配置数据
|
108
|
+
const [currentEnv, setCurrentEnv] = useState<Record<string, string>>(schema.config.env || {});
|
109
|
+
const [currentHeaders, setCurrentHeaders] = useState<Record<string, string>>(
|
110
|
+
schema.config.headers || {},
|
111
|
+
);
|
112
|
+
|
113
|
+
// 处理环境变量更新
|
114
|
+
const handleEnvUpdate = (newEnv: Record<string, string>) => {
|
115
|
+
setCurrentEnv(newEnv);
|
116
|
+
onConfigUpdate?.({ env: newEnv, headers: currentHeaders });
|
117
|
+
};
|
118
|
+
|
119
|
+
// 处理 Headers 更新
|
120
|
+
const handleHeadersUpdate = (newHeaders: Record<string, string>) => {
|
121
|
+
setCurrentHeaders(newHeaders);
|
122
|
+
onConfigUpdate?.({ env: currentEnv, headers: newHeaders });
|
123
|
+
};
|
124
|
+
|
125
|
+
return (
|
126
|
+
<Flexbox gap={16}>
|
127
|
+
{/* 安装信息 */}
|
128
|
+
<Block className={styles.configSection} variant={'outlined'}>
|
129
|
+
<div className={styles.configTitle}>
|
130
|
+
<LinkIcon size={14} />
|
131
|
+
{t('protocolInstall.install.title', { defaultValue: '安装信息' })}
|
132
|
+
</div>
|
133
|
+
|
134
|
+
<div className={styles.previewContainer}>
|
135
|
+
{/* 连接类型 */}
|
136
|
+
<div className={styles.previewItem}>
|
137
|
+
<span className={styles.previewLabel}>{t('protocolInstall.config.type.label')}</span>
|
138
|
+
<div className={styles.typeValue}>
|
139
|
+
<Text className={styles.previewValue}>
|
140
|
+
{schema.config.type === 'stdio' ? 'STDIO' : 'HTTP'}
|
141
|
+
</Text>
|
142
|
+
</div>
|
143
|
+
</div>
|
144
|
+
|
145
|
+
{/* HTTP 类型显示 URL */}
|
146
|
+
{schema.config.type === 'http' && schema.config.url && (
|
147
|
+
<div className={styles.previewItem}>
|
148
|
+
<span className={styles.previewLabel}>{t('protocolInstall.config.url')}</span>
|
149
|
+
<div className={styles.urlValue}>{schema.config.url}</div>
|
150
|
+
</div>
|
151
|
+
)}
|
152
|
+
|
153
|
+
{/* STDIO 类型显示命令和参数 */}
|
154
|
+
{schema.config.type === 'stdio' && (
|
155
|
+
<>
|
156
|
+
{schema.config.command && (
|
157
|
+
<div className={styles.previewItem}>
|
158
|
+
<span className={styles.previewLabel}>{t('protocolInstall.config.command')}</span>
|
159
|
+
<span className={styles.previewValue}>{schema.config.command}</span>
|
160
|
+
</div>
|
161
|
+
)}
|
162
|
+
|
163
|
+
{schema.config.args && schema.config.args.length > 0 && (
|
164
|
+
<div className={styles.previewItem}>
|
165
|
+
<span className={styles.previewLabel}>{t('protocolInstall.config.args')}</span>
|
166
|
+
<span className={styles.previewValue}>{schema.config.args.join(' ')}</span>
|
167
|
+
</div>
|
168
|
+
)}
|
169
|
+
</>
|
170
|
+
)}
|
171
|
+
</div>
|
172
|
+
</Block>
|
173
|
+
|
174
|
+
{/* 配置信息 - 直接使用 KeyValueEditor */}
|
175
|
+
<Block className={styles.configSection} variant={'outlined'}>
|
176
|
+
<div className={styles.configTitle}>
|
177
|
+
<Settings2Icon size={14} />
|
178
|
+
{schema.config.type === 'stdio'
|
179
|
+
? t('protocolInstall.config.env', { defaultValue: '环境变量' })
|
180
|
+
: t('protocolInstall.config.headers', { defaultValue: '请求头' })}
|
181
|
+
</div>
|
182
|
+
|
183
|
+
<div className={styles.configEditor}>
|
184
|
+
{/* HTTP 类型显示 Headers */}
|
185
|
+
{schema.config.type === 'http' && (
|
186
|
+
<KeyValueEditor
|
187
|
+
addButtonText={t('protocolInstall.config.addHeaders', { defaultValue: '添加请求头' })}
|
188
|
+
onChange={handleHeadersUpdate}
|
189
|
+
style={{ border: 'none' }}
|
190
|
+
value={currentHeaders}
|
191
|
+
/>
|
192
|
+
)}
|
193
|
+
|
194
|
+
{/* STDIO 类型显示环境变量 */}
|
195
|
+
{schema.config.type === 'stdio' && (
|
196
|
+
<KeyValueEditor
|
197
|
+
addButtonText={t('protocolInstall.config.addEnv', { defaultValue: '添加环境变量' })}
|
198
|
+
onChange={handleEnvUpdate}
|
199
|
+
style={{ border: 'none' }}
|
200
|
+
value={currentEnv}
|
201
|
+
/>
|
202
|
+
)}
|
203
|
+
</div>
|
204
|
+
</Block>
|
205
|
+
</Flexbox>
|
206
|
+
);
|
207
|
+
});
|
208
|
+
|
209
|
+
ConfigDisplay.displayName = 'ConfigDisplay';
|
210
|
+
|
211
|
+
export default ConfigDisplay;
|