Skip to content

Commit ce3299a

Browse files
author
purpleroc
committed
Merge commit '0b18c5a10f2a2cf6a0aac7c7a8def24592db24ad'
2 parents 593cef1 + 0b18c5a commit ce3299a

File tree

13 files changed

+1277
-254
lines changed

13 files changed

+1277
-254
lines changed

package-lock.json

Lines changed: 13 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
"@ant-design/icons": "^5.2.6",
1818
"@reduxjs/toolkit": "^1.9.7",
1919
"antd": "^5.12.5",
20-
"react": "^18.2.0",
21-
"react-dom": "^18.2.0",
20+
"react": "^18.3.1",
21+
"react-dom": "^18.3.1",
2222
"react-markdown": "^10.1.0",
2323
"react-redux": "^8.1.3",
2424
"uuid": "^9.0.1"
2525
},
2626
"devDependencies": {
2727
"@types/chrome": "^0.0.254",
28+
"@types/node": "^24.2.1",
2829
"@types/react": "^18.2.37",
2930
"@types/react-dom": "^18.2.15",
3031
"@types/uuid": "^9.0.7",

src/components/ConfigPanel.tsx

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ApiOutlined, SaveOutlined, DisconnectOutlined } from '@ant-design/icons
44
import { useDispatch, useSelector } from 'react-redux';
55
import { connectToServer, disconnectFromServer } from '../store/mcpSlice';
66
import { RootState } from '../store';
7-
import { MCPServerConfig } from '../types/mcp';
7+
import { MCPServerConfig, MCPTransportMode } from '../types/mcp';
88
import { storage } from '../utils/storage';
99
import AuthConfigComponent from './AuthConfig';
1010
import { useI18n } from '../hooks/useI18n';
@@ -29,6 +29,7 @@ const ConfigPanel: React.FC<ConfigPanelProps> = ({ onConfigLoad, selectedConfig,
2929
form.setFieldsValue({
3030
name: selectedConfig.name,
3131
host: selectedConfig.host,
32+
transport: selectedConfig.transport,
3233
ssePath: selectedConfig.ssePath,
3334
sessionId: selectedConfig.sessionId
3435
});
@@ -42,9 +43,9 @@ const ConfigPanel: React.FC<ConfigPanelProps> = ({ onConfigLoad, selectedConfig,
4243
const serverConfig: MCPServerConfig = {
4344
name: values.name || 'MCP Server', // 如果没有填写名称,使用默认值
4445
host: values.host,
45-
ssePath: values.ssePath,
46+
ssePath: values.ssePath, // 两种模式都需要路径
4647
messagePath: '', // 现在从SSE自动获取,不需要配置
47-
transport: 'sse', // 固定为SSE传输方式
48+
transport: values.transport as MCPTransportMode, // 使用选择的传输方式
4849
sessionId: values.sessionId || undefined,
4950
headers: values.headers ? JSON.parse(values.headers || '{}') : undefined,
5051
auth: authConfig // 添加认证配置
@@ -106,9 +107,9 @@ const ConfigPanel: React.FC<ConfigPanelProps> = ({ onConfigLoad, selectedConfig,
106107
const serverConfig: MCPServerConfig = {
107108
name: values.name || 'MCP Server',
108109
host: values.host,
109-
ssePath: values.ssePath,
110+
ssePath: values.ssePath, // 两种模式都需要路径
110111
messagePath: '',
111-
transport: 'sse',
112+
transport: values.transport as MCPTransportMode,
112113
sessionId: values.sessionId || undefined,
113114
headers: values.headers ? JSON.parse(values.headers || '{}') : undefined,
114115
auth: authConfig
@@ -152,6 +153,18 @@ const ConfigPanel: React.FC<ConfigPanelProps> = ({ onConfigLoad, selectedConfig,
152153
<Input placeholder={t.config.serverNamePlaceholder} />
153154
</Form.Item>
154155

156+
<Form.Item
157+
name="transport"
158+
label={t.config.transportMode}
159+
rules={[{ required: true, message: t.config.messages.transportModeRequired }]}
160+
tooltip={t.config.transportModeTooltip}
161+
>
162+
<Select placeholder={t.config.transportModePlaceholder}>
163+
<Select.Option value="sse">{t.config.transportModes.sse}</Select.Option>
164+
<Select.Option value="streamable">{t.config.transportModes.streamable}</Select.Option>
165+
</Select>
166+
</Form.Item>
167+
155168
<Form.Item
156169
name="host"
157170
label={t.config.serverHost}
@@ -168,12 +181,37 @@ const ConfigPanel: React.FC<ConfigPanelProps> = ({ onConfigLoad, selectedConfig,
168181
</Form.Item>
169182

170183
<Form.Item
171-
name="ssePath"
172-
label={t.config.ssePath}
173-
rules={[{ required: true, message: t.config.messages.ssePathRequired }]}
174-
tooltip={t.config.ssePathPlaceholder}
184+
noStyle
185+
shouldUpdate={(prevValues, currentValues) => prevValues.transport !== currentValues.transport}
175186
>
176-
<Input placeholder={t.config.ssePathPlaceholder} />
187+
{({ getFieldValue, setFieldsValue }) => {
188+
const transport = getFieldValue('transport');
189+
190+
// 当传输模式改变时,自动更新路径默认值(移除useEffect,直接在渲染时检查)
191+
const currentPath = getFieldValue('ssePath');
192+
if (!currentPath || currentPath === '/sse' || currentPath === '/mcp') {
193+
const newPath = transport === 'sse' ? '/sse' : '/mcp';
194+
if (currentPath !== newPath) {
195+
// 使用 setTimeout 避免在渲染过程中更新状态
196+
setTimeout(() => {
197+
setFieldsValue({
198+
ssePath: newPath
199+
});
200+
}, 0);
201+
}
202+
}
203+
204+
return (
205+
<Form.Item
206+
name="ssePath"
207+
label={transport === 'sse' ? t.config.ssePath : t.config.mcpPath}
208+
rules={[{ required: true, message: transport === 'sse' ? t.config.messages.ssePathRequired : t.config.messages.mcpPathRequired }]}
209+
tooltip={transport === 'sse' ? t.config.ssePathPlaceholder : t.config.mcpPathPlaceholder}
210+
>
211+
<Input placeholder={transport === 'sse' ? t.config.ssePathPlaceholder : t.config.mcpPathPlaceholder} />
212+
</Form.Item>
213+
);
214+
}}
177215
</Form.Item>
178216

179217
<Form.Item label={t.config.authentication}>
@@ -223,6 +261,11 @@ const ConfigPanel: React.FC<ConfigPanelProps> = ({ onConfigLoad, selectedConfig,
223261
<div style={{ marginBottom: 16, padding: 12, backgroundColor: '#f6ffed', border: '1px solid #b7eb8f', borderRadius: 4 }}>
224262
<p><strong>{t.config.connectionStatus.connected}:</strong> {serverConfig?.name}</p>
225263
<p><strong>{t.config.serverHost}:</strong> {serverConfig?.host}</p>
264+
<p><strong>{t.config.transportMode}:</strong> {
265+
serverConfig?.transport === 'streamable'
266+
? t.config.transportModes.streamable
267+
: t.config.transportModes.sse
268+
}</p>
226269
<p><strong>{t.config.authType}:</strong> {
227270
(() => {
228271
const authType = serverConfig?.auth?.type;

src/hooks/useI18n.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { useState, useEffect } from 'react';
22
import { i18n, Language, TranslationKey } from '../i18n';
33

44
export const useI18n = () => {
5-
const [language, setLanguageState] = useState<Language>(i18n.getCurrentLanguage());
6-
const [translations, setTranslations] = useState<TranslationKey>(i18n.t());
5+
const [language, setLanguageState] = useState<Language>(() => i18n.getCurrentLanguage());
6+
const [translations, setTranslations] = useState<TranslationKey>(() => i18n.t());
77

88
useEffect(() => {
99
const handleLanguageChange = (newLanguage: Language) => {

src/i18n/en-US.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,17 @@ export const enUS: TranslationKey = {
4747
serverNamePlaceholder: 'Enter server name',
4848
serverHost: 'Server Host',
4949
serverHostPlaceholder: 'e.g., http://localhost:8000',
50+
transportMode: 'Transport Mode',
51+
transportModePlaceholder: 'Select transport mode',
52+
transportModeTooltip: 'Choose between SSE (Server-Sent Events) and Streamable HTTP transport modes',
53+
transportModes: {
54+
sse: 'SSE (Server-Sent Events)',
55+
streamable: 'Streamable HTTP',
56+
},
5057
ssePath: 'SSE Path',
5158
ssePathPlaceholder: 'e.g., /sse',
59+
mcpPath: 'MCP Path',
60+
mcpPathPlaceholder: 'e.g., /mcp',
5261
messagePath: 'Message Path',
5362
messagePathPlaceholder: 'e.g., /message',
5463
authentication: 'Authentication',
@@ -75,7 +84,9 @@ export const enUS: TranslationKey = {
7584
serverNameRequired: 'Please enter server name',
7685
serverHostRequired: 'Please enter server host',
7786
serverHostFormat: 'Please enter correct host format, e.g., http://127.0.0.1:8020 (do not include path)',
87+
transportModeRequired: 'Please select transport mode',
7888
ssePathRequired: 'Please enter SSE path',
89+
mcpPathRequired: 'Please enter MCP path',
7990
connectSuccess: 'Connected successfully',
8091
connectFailed: 'Connection failed',
8192
disconnectSuccess: 'Disconnected',

src/i18n/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,17 @@ export interface TranslationKey {
5454
serverNamePlaceholder: string;
5555
serverHost: string;
5656
serverHostPlaceholder: string;
57+
transportMode: string;
58+
transportModePlaceholder: string;
59+
transportModeTooltip: string;
60+
transportModes: {
61+
sse: string;
62+
streamable: string;
63+
};
5764
ssePath: string;
5865
ssePathPlaceholder: string;
66+
mcpPath: string;
67+
mcpPathPlaceholder: string;
5968
messagePath: string;
6069
messagePathPlaceholder: string;
6170
authentication: string;
@@ -82,7 +91,9 @@ export interface TranslationKey {
8291
serverNameRequired: string;
8392
serverHostRequired: string;
8493
serverHostFormat: string;
94+
transportModeRequired: string;
8595
ssePathRequired: string;
96+
mcpPathRequired: string;
8697
connectSuccess: string;
8798
connectFailed: string;
8899
disconnectSuccess: string;

src/i18n/zh-CN.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,17 @@ export const zhCN: TranslationKey = {
4747
serverNamePlaceholder: '请输入服务器名称',
4848
serverHost: '服务器地址',
4949
serverHostPlaceholder: '例如: http://localhost:8000',
50+
transportMode: '传输模式',
51+
transportModePlaceholder: '选择传输模式',
52+
transportModeTooltip: '选择SSE(服务器推送事件)或Streamable HTTP传输模式',
53+
transportModes: {
54+
sse: 'SSE(服务器推送事件)',
55+
streamable: 'Streamable HTTP',
56+
},
5057
ssePath: 'SSE路径',
5158
ssePathPlaceholder: '例如: /sse',
59+
mcpPath: 'MCP路径',
60+
mcpPathPlaceholder: '例如: /mcp',
5261
messagePath: '消息路径',
5362
messagePathPlaceholder: '例如: /message',
5463
authentication: '认证配置',
@@ -75,7 +84,9 @@ export const zhCN: TranslationKey = {
7584
serverNameRequired: '请输入服务器名称',
7685
serverHostRequired: '请输入主机地址',
7786
serverHostFormat: '请输入正确的主机地址格式,例如: http://127.0.0.1:8020 (不要包含路径)',
87+
transportModeRequired: '请选择传输模式',
7888
ssePathRequired: '请输入SSE路径',
89+
mcpPathRequired: '请输入MCP路径',
7990
connectSuccess: '连接成功',
8091
connectFailed: '连接失败',
8192
disconnectSuccess: '已断开连接',

src/main.tsx

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,69 @@ import './index.css';
88
import { i18n, Language } from './i18n';
99

1010
const AppWithI18n: React.FC = () => {
11-
const [language, setLanguage] = useState<Language>(i18n.getCurrentLanguage());
11+
const [language, setLanguage] = useState<Language>(() => i18n.getCurrentLanguage());
1212
const [antdLocale, setAntdLocale] = useState<any>(null);
13+
const [isLoaded, setIsLoaded] = useState(false);
1314

1415
useEffect(() => {
16+
let isMounted = true;
17+
1518
// 动态加载初始语言包
1619
const loadInitialLocale = async () => {
17-
const localeModule = await i18n.getAntdLocale();
18-
setAntdLocale(localeModule.default);
20+
try {
21+
const localeModule = await i18n.getAntdLocale();
22+
if (isMounted) {
23+
setAntdLocale(localeModule.default);
24+
setIsLoaded(true);
25+
}
26+
} catch (error) {
27+
console.error('Failed to load locale:', error);
28+
if (isMounted) {
29+
setIsLoaded(true); // 即使失败也要设置为已加载,使用默认值
30+
}
31+
}
1932
};
2033

2134
loadInitialLocale();
2235

2336
const handleLanguageChange = async (newLanguage: Language) => {
24-
setLanguage(newLanguage);
25-
const localeModule = await i18n.getAntdLocale();
26-
setAntdLocale(localeModule.default);
37+
if (isMounted) {
38+
setLanguage(newLanguage);
39+
try {
40+
const localeModule = await i18n.getAntdLocale();
41+
if (isMounted) {
42+
setAntdLocale(localeModule.default);
43+
}
44+
} catch (error) {
45+
console.error('Failed to load locale:', error);
46+
}
47+
}
2748
};
2849

2950
i18n.addLanguageChangeListener(handleLanguageChange);
3051

3152
return () => {
53+
isMounted = false;
3254
i18n.removeLanguageChangeListener(handleLanguageChange);
3355
};
3456
}, []);
3557

58+
// 在语言包加载完成前显示加载状态
59+
if (!isLoaded) {
60+
return (
61+
<div style={{
62+
display: 'flex',
63+
justifyContent: 'center',
64+
alignItems: 'center',
65+
height: '100vh',
66+
fontSize: '16px',
67+
color: '#666'
68+
}}>
69+
Loading...
70+
</div>
71+
);
72+
}
73+
3674
return (
3775
<Provider store={store}>
3876
<ConfigProvider locale={antdLocale}>

0 commit comments

Comments
 (0)