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);
|
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 {
|
.hub-platform-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1713,3 +1729,13 @@
|
|||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
border: 1px solid var(--color-border-light);
|
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('');
|
const [statusMsg, setStatusMsg] = useState('');
|
||||||
|
|
||||||
// MCP code editor state for hub import
|
// MCP code editor state for hub import
|
||||||
|
// shared: used by the standalone custom-import button
|
||||||
const [hubCode, setHubCode] = useState('');
|
const [hubCode, setHubCode] = useState('');
|
||||||
const [hubCodeVerifyResult, setHubCodeVerifyResult] = useState<'idle' | 'checking' | 'pass' | 'fail'>('idle');
|
const [hubCodeVerifyResult, setHubCodeVerifyResult] = useState<'idle' | 'checking' | 'pass' | 'fail'>('idle');
|
||||||
const [hubCodeVerifyMsg, setHubCodeVerifyMsg] = useState('');
|
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 [hubName, setHubName] = useState('');
|
||||||
const [hubDescription, setHubDescription] = useState('');
|
const [hubDescription, setHubDescription] = useState('');
|
||||||
|
|
||||||
@@ -908,12 +913,39 @@ const HubImportModal: React.FC<HubImportModalProps> = ({ appType, platforms, onC
|
|||||||
}, 1200);
|
}, 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 handleImport = (platformId: string) => {
|
||||||
const url = importUrl[platformId];
|
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
|
// For Skills: require URL; for MCP: require code verification pass + name
|
||||||
if (appType === 'skill' && !url?.trim()) return;
|
if (appType === 'skill' && !url?.trim()) return;
|
||||||
if (appType === 'mcp' && hubCodeVerifyResult !== 'pass') return;
|
if (appType === 'mcp') {
|
||||||
if (appType === 'mcp' && !hubName.trim()) return;
|
if (isCustomImport) {
|
||||||
|
if (hubCodeVerifyResult !== 'pass') return;
|
||||||
|
} else {
|
||||||
|
if (hubCodeVerifyResultMap[platformId] !== 'pass') return;
|
||||||
|
}
|
||||||
|
if (!hubName.trim()) return;
|
||||||
|
}
|
||||||
setImportingId(platformId);
|
setImportingId(platformId);
|
||||||
setImportStatus('verifying');
|
setImportStatus('verifying');
|
||||||
setStatusMsg(appType === 'mcp' ? '正在验证代码...' : '正在验证URL链接...');
|
setStatusMsg(appType === 'mcp' ? '正在验证代码...' : '正在验证URL链接...');
|
||||||
@@ -934,13 +966,17 @@ const HubImportModal: React.FC<HubImportModalProps> = ({ appType, platforms, onC
|
|||||||
if (step.status === 'published') {
|
if (step.status === 'published') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const name = appType === 'skill' ? (hubName || extractSkillName(url || '', platformId)) : hubName;
|
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);
|
setImportingId(null);
|
||||||
setImportStatus('idle');
|
setImportStatus('idle');
|
||||||
setImportUrl(prev => ({ ...prev, [platformId]: '' }));
|
setImportUrl(prev => ({ ...prev, [platformId]: '' }));
|
||||||
setHubCode('');
|
setHubCode('');
|
||||||
setHubCodeVerifyResult('idle');
|
setHubCodeVerifyResult('idle');
|
||||||
setHubCodeVerifyMsg('');
|
setHubCodeVerifyMsg('');
|
||||||
|
setHubCodeMap(prev => ({ ...prev, [platformId]: '' }));
|
||||||
|
setHubCodeVerifyResultMap(prev => ({ ...prev, [platformId]: 'idle' }));
|
||||||
|
setHubCodeVerifyMsgMap(prev => ({ ...prev, [platformId]: '' }));
|
||||||
setHubName('');
|
setHubName('');
|
||||||
setHubDescription('');
|
setHubDescription('');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -962,7 +998,7 @@ const HubImportModal: React.FC<HubImportModalProps> = ({ appType, platforms, onC
|
|||||||
<div className="hub-step-arrow"/>
|
<div className="hub-step-arrow"/>
|
||||||
<div className="hub-step"><span className="hub-step-num">2</span> {appType === 'skill' ? '在平台找到目标链接并复制' : '在平台获取MCP客户端代码'}</div>
|
<div className="hub-step"><span className="hub-step-num">2</span> {appType === 'skill' ? '在平台找到目标链接并复制' : '在平台获取MCP客户端代码'}</div>
|
||||||
<div className="hub-step-arrow"/>
|
<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>
|
||||||
<div className="hub-price-section">
|
<div className="hub-price-section">
|
||||||
<label className="form-label">定价(元)</label>
|
<label className="form-label">定价(元)</label>
|
||||||
@@ -1125,7 +1161,7 @@ const mcpClient = new MCPClient({
|
|||||||
<button
|
<button
|
||||||
className="hub-import-btn"
|
className="hub-import-btn"
|
||||||
onClick={() => handleImport(p.id)}
|
onClick={() => handleImport(p.id)}
|
||||||
disabled={hubCodeVerifyResult !== 'pass' || importingId !== null}
|
disabled={hubCodeVerifyResultMap[p.id] !== 'pass' || importingId !== null}
|
||||||
>
|
>
|
||||||
{importingId === p.id ? (
|
{importingId === p.id ? (
|
||||||
<span className="hub-importing-text">
|
<span className="hub-importing-text">
|
||||||
@@ -1137,6 +1173,46 @@ const mcpClient = new MCPClient({
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user