迭代18: QMT Bridge MCP 能力内建(R-020)—— 动态挂载 dsh-mcp-client + 状态出口(设置页/会话头部灯)

This commit is contained in:
2026-09-09 08:58:49 +08:00
parent 9382ace3a0
commit c700f2f760
16 changed files with 1402 additions and 9 deletions
+46 -1
View File
@@ -520,6 +520,8 @@ function QmtConnectionsSettings() {
const [editingId, setEditingId] = useState(null);
const [formName, setFormName] = useState('');
const [formUrl, setFormUrl] = useState('');
const [mcpStatus, setMcpStatus] = useState(null); // R-020/迭代18MCP 状态
const [mcpChecking, setMcpChecking] = useState(false);
const call = useRpc();
const toast = useToast();
@@ -533,7 +535,14 @@ function QmtConnectionsSettings() {
}
}, [call]);
useEffect(() => { load(); }, [load]);
const loadMcp = useCallback(async (refresh) => {
try {
const res = await call('one-divine-lot/qmt-connections/mcp-status', { args: { refresh: !!refresh } });
if (res && res.ok) setMcpStatus(res.value);
} catch { /* 静默 */ }
}, [call]);
useEffect(() => { load(); loadMcp(false); }, [load, loadMcp]);
const openAdd = () => { setEditingId(null); setFormName(''); setFormUrl(''); setFormOpen(true); setTestResult(null); };
const openEdit = (c) => { setEditingId(c.id); setFormName(c.name); setFormUrl(c.baseUrl); setFormOpen(true); setTestResult(null); };
@@ -576,6 +585,7 @@ function QmtConnectionsSettings() {
const res = await call('one-divine-lot/qmt-connections/activate', { args: { id: c.id } });
if (res && res.ok) {
setState(res.value);
loadMcp(false); // R-020/迭代18:激活切换后刷新 MCP 状态
toast.success('已激活:' + c.name + '(立即生效)');
} else {
toast.error((res && res.error && res.error.message) || '激活失败');
@@ -679,6 +689,41 @@ function QmtConnectionsSettings() {
</button>
</div>
{/* R-020/迭代18:MCP 连接状态条(随激活连接;绿=已连接+N工具 / 黄=连接中 / 红=断开 / 灰=未启用) */}
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, minWidth: 0, fontSize: 12 }}>
<span style={{ flex: 'none', fontWeight: 600 }}>MCP 连接</span>
<span style={{
flex: 'none', width: 10, height: 10, borderRadius: '50%',
background: mcpStatus?.state === 'connected'
? 'var(--dsw-alias-state-success-primary, #2e7d32)'
: mcpStatus?.state === 'connecting'
? 'var(--dsw-alias-state-warn-primary, #f9a825)'
: mcpStatus?.state === 'error'
? 'var(--dsw-alias-state-error-primary, #c62828)'
: 'var(--dsw-alias-label-tertiary, #bbb)',
}} />
<EllipsisText
text={mcpStatus?.state === 'connected'
? '已连接' + (mcpStatus.toolCount != null ? '' + mcpStatus.toolCount + ' 个工具)' : '') + ' · ' + (mcpStatus.url || '')
: mcpStatus?.state === 'connecting' ? '连接中…'
: mcpStatus?.state === 'error' ? '断开 · ' + String(mcpStatus.error || '').slice(0, 80)
: mcpStatus ? '未启用(无激活连接)' : '检测中…'}
maxWidth={520}
title={mcpStatus?.error ? String(mcpStatus.error) : (mcpStatus?.url || '')}
/>
<button
onClick={async () => {
if (mcpChecking) return;
setMcpChecking(true);
try { await loadMcp(true); } finally { setMcpChecking(false); }
}}
disabled={mcpChecking}
style={{ flex: 'none', padding: '2px 10px', cursor: 'pointer', border: '1px solid var(--dsw-alias-border-l2, #ccc)', borderRadius: 4, background: 'var(--dsw-alias-bg-layer-1, #fff)', fontSize: 12 }}
>
{mcpChecking ? '检测中…' : '检测 MCP'}
</button>
</div>
{error && <div style={{ color: 'var(--dsw-alias-state-error-primary, #c62828)', marginBottom: 12 }}><EllipsisText text={error} maxWidth={560} /></div>}
{!state && !error && <div style={{ color: 'var(--dsw-alias-label-secondary, #666)' }}>加载中...</div>}