feat(frontend): per-platform Streamable HTTP input for MCP Hub import
This commit is contained in:
1 parent
e856f186be
commit
c9b5f964be
2 files changed
+107
-5
No files matched your search
@@ -1091,6 +1091,22 @@
|
||||
border-color: var(--color-accent-light);
|
||||
}
|
||||
|
||||
.hub-platform-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.hub-platform-code-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-md);
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.hub-platform-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1713,3 +1729,13 @@
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.hub-platform-code-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-md);
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
@@ -881,9 +881,14 @@ const HubImportModal: React.FC<HubImportModalProps> = ({ appType, platforms, onC
|
||||
const [statusMsg, setStatusMsg] = useState('');
|
||||
|
||||
// MCP code editor state for hub import
|
||||
// shared: used by the standalone custom-import button
|
||||
const [hubCode, setHubCode] = useState('');
|
||||
const [hubCodeVerifyResult, setHubCodeVerifyResult] = useState<'idle' | 'checking' | 'pass' | 'fail'>('idle');
|
||||
const [hubCodeVerifyMsg, setHubCodeVerifyMsg] = useState('');
|
||||
// per-platform: each MCP platform has its own Streamable HTTP code input
|
||||
const [hubCodeMap, setHubCodeMap] = useState<Record<string, string>>({});
|
||||
const [hubCodeVerifyResultMap, setHubCodeVerifyResultMap] = useState<Record<string, 'idle' | 'checking' | 'pass' | 'fail'>>({});
|
||||
const [hubCodeVerifyMsgMap, setHubCodeVerifyMsgMap] = useState<Record<string, string>>({});
|
||||
const [hubName, setHubName] = useState('');
|
||||
const [hubDescription, setHubDescription] = useState('');
|
||||
|
||||
@@ -908,12 +913,39 @@ const HubImportModal: React.FC<HubImportModalProps> = ({ appType, platforms, onC
|
||||
}, 1200);
|
||||
};
|
||||
|
||||
const handleHubMcpVerifyForPlatform = (platformId: string) => {
|
||||
const code = hubCodeMap[platformId] || '';
|
||||
if (!code.trim()) return;
|
||||
setHubCodeVerifyResultMap(prev => ({ ...prev, [platformId]: 'checking' }));
|
||||
setHubCodeVerifyMsgMap(prev => ({ ...prev, [platformId]: '' }));
|
||||
setTimeout(() => {
|
||||
const trimmed = code.trim();
|
||||
const hasMinLength = trimmed.length > 20;
|
||||
const hasKeywords = /(transport|endpoint|mcp|server|client|connection|url)/i.test(trimmed);
|
||||
if (hasMinLength && hasKeywords) {
|
||||
setHubCodeVerifyResultMap(prev => ({ ...prev, [platformId]: 'pass' }));
|
||||
setHubCodeVerifyMsgMap(prev => ({ ...prev, [platformId]: 'AI验证通过:代码包含必要的传输协议和端点配置,可以导入' }));
|
||||
} else {
|
||||
setHubCodeVerifyResultMap(prev => ({ ...prev, [platformId]: 'fail' }));
|
||||
setHubCodeVerifyMsgMap(prev => ({ ...prev, [platformId]: 'AI验证失败:代码中缺少必要的关键词(transport/endpoint),请检查代码完整性' }));
|
||||
}
|
||||
}, 1200);
|
||||
};
|
||||
|
||||
const handleImport = (platformId: string) => {
|
||||
const url = importUrl[platformId];
|
||||
const isCustomImport = platformId === 'custom-import';
|
||||
const platformCode = isCustomImport ? hubCode : (hubCodeMap[platformId] || '');
|
||||
// For Skills: require URL; for MCP: require code verification pass + name
|
||||
if (appType === 'skill' && !url?.trim()) return;
|
||||
if (appType === 'mcp' && hubCodeVerifyResult !== 'pass') return;
|
||||
if (appType === 'mcp' && !hubName.trim()) return;
|
||||
if (appType === 'mcp') {
|
||||
if (isCustomImport) {
|
||||
if (hubCodeVerifyResult !== 'pass') return;
|
||||
} else {
|
||||
if (hubCodeVerifyResultMap[platformId] !== 'pass') return;
|
||||
}
|
||||
if (!hubName.trim()) return;
|
||||
}
|
||||
setImportingId(platformId);
|
||||
setImportStatus('verifying');
|
||||
setStatusMsg(appType === 'mcp' ? '正在验证代码...' : '正在验证URL链接...');
|
||||
@@ -934,13 +966,17 @@ const HubImportModal: React.FC<HubImportModalProps> = ({ appType, platforms, onC
|
||||
if (step.status === 'published') {
|
||||
setTimeout(() => {
|
||||
const name = appType === 'skill' ? (hubName || extractSkillName(url || '', platformId)) : hubName;
|
||||
onImport(platformId, appType === 'mcp' ? hubCode : (url || ''), name, importPrice, hubDescription);
|
||||
const importPayload = appType === 'mcp' ? platformCode : (url || '');
|
||||
onImport(platformId, importPayload, name, importPrice, hubDescription);
|
||||
setImportingId(null);
|
||||
setImportStatus('idle');
|
||||
setImportUrl(prev => ({ ...prev, [platformId]: '' }));
|
||||
setHubCode('');
|
||||
setHubCodeVerifyResult('idle');
|
||||
setHubCodeVerifyMsg('');
|
||||
setHubCodeMap(prev => ({ ...prev, [platformId]: '' }));
|
||||
setHubCodeVerifyResultMap(prev => ({ ...prev, [platformId]: 'idle' }));
|
||||
setHubCodeVerifyMsgMap(prev => ({ ...prev, [platformId]: '' }));
|
||||
setHubName('');
|
||||
setHubDescription('');
|
||||
}, 1000);
|
||||
@@ -962,7 +998,7 @@ const HubImportModal: React.FC<HubImportModalProps> = ({ appType, platforms, onC
|
||||
<div className="hub-step-arrow"/>
|
||||
<div className="hub-step"><span className="hub-step-num">2</span> {appType === 'skill' ? '在平台找到目标链接并复制' : '在平台获取MCP客户端代码'}</div>
|
||||
<div className="hub-step-arrow"/>
|
||||
<div className="hub-step"><span className="hub-step-num">3</span> {appType === 'skill' ? '粘贴链接至输入框并点击导入' : '粘贴代码到编辑器,通过AI验证后点击导入'}</div>
|
||||
<div className="hub-step"><span className="hub-step-num">3</span> {appType === 'skill' ? '粘贴链接至输入框并点击导入' : '粘贴代码到对应平台输入框,通过AI验证后点击导入'}</div>
|
||||
</div>
|
||||
<div className="hub-price-section">
|
||||
<label className="form-label">定价(元)</label>
|
||||
@@ -1125,7 +1161,7 @@ const mcpClient = new MCPClient({
|
||||
<button
|
||||
className="hub-import-btn"
|
||||
onClick={() => handleImport(p.id)}
|
||||
disabled={hubCodeVerifyResult !== 'pass' || importingId !== null}
|
||||
disabled={hubCodeVerifyResultMap[p.id] !== 'pass' || importingId !== null}
|
||||
>
|
||||
{importingId === p.id ? (
|
||||
<span className="hub-importing-text">
|
||||
@@ -1137,6 +1173,46 @@ const mcpClient = new MCPClient({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{appType === 'mcp' && (
|
||||
<div className="hub-platform-code-section">
|
||||
<label className="form-label">Streamable HTTP 传输代码 <span className="required">*</span></label>
|
||||
<textarea
|
||||
className="mcp-code-editor"
|
||||
placeholder={`粘贴 ${p.name} 的 MCP 客户端代码(需通过AI验证后才能导入),例如:
|
||||
const mcpClient = new MCPClient({
|
||||
transport: { type: "streamable-http", endpoint: "https://..." }
|
||||
});`}
|
||||
value={hubCodeMap[p.id] || ''}
|
||||
onChange={e => {
|
||||
setHubCodeMap(prev => ({ ...prev, [p.id]: e.target.value }));
|
||||
setHubCodeVerifyResultMap(prev => ({ ...prev, [p.id]: 'idle' }));
|
||||
}}
|
||||
rows={6}
|
||||
/>
|
||||
<div className="skills-upload-row">
|
||||
<span className="mcp-code-hint">请粘贴从 {p.name} 获取的 MCP 客户端代码,通过AI验证后方可导入</span>
|
||||
<button
|
||||
className="format-check-btn"
|
||||
onClick={() => handleHubMcpVerifyForPlatform(p.id)}
|
||||
disabled={!(hubCodeMap[p.id] || '').trim()}
|
||||
>
|
||||
{hubCodeVerifyResultMap[p.id] === 'checking' ? '验证中...' : 'AI验证'}
|
||||
</button>
|
||||
</div>
|
||||
{hubCodeVerifyResultMap[p.id] === 'checking' && (
|
||||
<div className="format-check-status checking">正在进行AI代码验证...</div>
|
||||
)}
|
||||
{hubCodeVerifyResultMap[p.id] === 'pass' && (
|
||||
<div className="format-check-status pass">
|
||||
<CheckCircleIcon />
|
||||
{hubCodeVerifyMsgMap[p.id]}
|
||||
</div>
|
||||
)}
|
||||
{hubCodeVerifyResultMap[p.id] === 'fail' && (
|
||||
<div className="format-check-status fail">{hubCodeVerifyMsgMap[p.id]}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user