feat: 智能Agent接入前连通性/鉴权校验

- 前端技能市场「接入单个Agent」新增「测试连接」按钮,接入前必须校验通过

- 后端 POST /market/apps/verify-agent:dify 走 chat-messages 鉴权探测(trust_env=False 直连),n8n/coze 走 HEAD 可达性探测

- upload_app 对 agent 类型做服务端守卫,密钥无效/不可达直接 400 拒绝

- MarketApp 新增 platform 列(dify/n8n/coze),009 迁移幂等加列
This commit is contained in:
QihuAI Design committed 2026-07-15 13:01:19 +08:00
1 parent b0bfba5c16
commit d374d464e8
6 files changed
+209 -5

No files matched your search

+2
View File
@@ -313,6 +313,8 @@ export const marketAPI = {
createApp: (data: any) => post<any>('/market/apps', data),
verifyAgent: (data: any) => post<any>('/market/apps/verify-agent', data),
purchaseApp: (id: string) => post<any>(`/market/apps/${id}/purchase`),
installApp: (id: string) => post<any>(`/market/apps/${id}/install`),
+45
View File
@@ -856,6 +856,51 @@
cursor: not-allowed;
}
/* Agent 接入:连通性 / 鉴权校验 */
.agent-verify-row {
display: flex;
align-items: center;
gap: var(--space-sm);
margin-bottom: var(--space-sm);
flex-wrap: wrap;
}
.form-test-btn {
padding: var(--space-sm) var(--space-lg);
background: transparent;
color: var(--color-accent);
border: 1px solid var(--color-accent);
border-radius: var(--radius-md);
font-size: var(--text-sm);
font-weight: 600;
cursor: pointer;
transition: all var(--transition-fast);
}
.form-test-btn:hover:not(:disabled) {
background: var(--color-accent);
color: #fff;
}
.form-test-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.agent-verify-msg {
font-size: var(--text-xs);
line-height: 1.4;
word-break: break-all;
}
.agent-verify-msg.ok {
color: #16a34a;
}
.agent-verify-msg.err {
color: #dc2626;
}
/* Audit Progress */
.audit-progress {
display: flex;
+60 -5
View File
@@ -1172,14 +1172,53 @@ const AgentConnectModal: React.FC<AgentConnectModalProps> = ({ onClose, onConnec
const [endpoint, setEndpoint] = useState('');
const [price, setPrice] = useState<number>(0);
const [connecting, setConnecting] = useState(false);
const [verifying, setVerifying] = useState(false);
const [verifyMsg, setVerifyMsg] = useState<string>('');
const [verifyOk, setVerifyOk] = useState<boolean | null>(null);
const handleConnect = () => {
const runVerify = async (): Promise<boolean | null> => {
if (!endpoint.trim() || !apiKey.trim()) {
setVerifyMsg('请先填写 API Endpoint 与 API Key');
setVerifyOk(false);
return false;
}
setVerifying(true);
setVerifyMsg('正在校验连通性与密钥...');
setVerifyOk(null);
try {
const res: any = await marketAPI.verifyAgent({
platform,
endpoint: endpoint.trim(),
api_key: apiKey.trim(),
});
if (res && res.code === 0 && res.data && res.data.ok) {
setVerifyOk(true);
setVerifyMsg('✓ ' + (res.data.detail || '校验通过'));
return true;
}
setVerifyOk(false);
setVerifyMsg('✗ ' + ((res && res.data && res.data.detail) || '校验未通过'));
return false;
} catch (e: any) {
setVerifyOk(false);
setVerifyMsg('✗ 校验请求失败:' + (e?.message || '网络错误'));
return false;
} finally {
setVerifying(false);
}
};
const handleConnect = async () => {
if (!name.trim() || !endpoint.trim() || !apiKey.trim()) return;
// 接入前必须先通过连通性 / 鉴权校验
const ok = await runVerify();
if (!ok) return;
setConnecting(true);
setTimeout(() => {
try {
await onConnect(platform, { apiKey: apiKey.trim(), endpoint: endpoint.trim() }, price, name.trim(), intro.trim());
} finally {
setConnecting(false);
onConnect(platform, { apiKey: apiKey.trim(), endpoint: endpoint.trim() }, price, name.trim(), intro.trim());
}, 800);
}
};
return (
@@ -1254,10 +1293,25 @@ const AgentConnectModal: React.FC<AgentConnectModalProps> = ({ onClose, onConnec
/>
<span className="hub-price-hint">用户对接的 Agent 是否收费由您决定;管理员导入默认免费且审核通过。</span>
</div>
<div className="form-section agent-verify-row">
<button
type="button"
className="form-test-btn"
onClick={runVerify}
disabled={!endpoint.trim() || !apiKey.trim() || verifying}
>
{verifying ? '校验中...' : '测试连接'}
</button>
{verifyMsg && (
<span className={`agent-verify-msg ${verifyOk === true ? 'ok' : verifyOk === false ? 'err' : ''}`}>
{verifyMsg}
</span>
)}
</div>
<button
className="form-submit-btn"
onClick={handleConnect}
disabled={!name.trim() || !endpoint.trim() || !apiKey.trim() || connecting}
disabled={!name.trim() || !endpoint.trim() || !apiKey.trim() || connecting || verifying}
>
{connecting ? '接入中...' : '接入 Agent'}
</button>
@@ -1393,6 +1447,7 @@ const MarketPage: React.FC = () => {
source: 'user',
agent_endpoint: config.endpoint || '',
api_key: config.apiKey || '',
platform: platformId || 'dify',
});
// 重新加载列表
loadApps();