feat(frontend): per-platform Streamable HTTP input for MCP Hub import

This commit is contained in:
QihuAI Design committed 2026-07-16 17:36:58 +08:00
1 parent e856f186be
commit c9b5f964be
2 files changed
+107 -5

No files matched your search

+81 -5
View File
@@ -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>