import { useCallback, useEffect, useMemo, useState } from 'react'; import { createSystemTestAccount, deleteSystemTestAccount, listSkillCatalog, listSystemTestAccounts, runSystemSkillValidation, } from '../../api/client'; import type { AdminSystemTestAccount, AdminSystemTestReport, SkillDefinition } from '../../types'; import { formatTime } from '../utils/format'; const DEFAULT_SKILL = 'service-integration-smoke'; function statusLabel(status: 'passed' | 'warning' | 'failed') { if (status === 'passed') return '通过'; if (status === 'warning') return '待处理'; return '失败'; } function AddAccountModal({ onClose, onSave, }: { onClose: () => void; onSave: (payload: { label: string; username: string; password: string }) => Promise; }) { const [label, setLabel] = useState(''); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); const normalizedUsername = username.trim(); const normalizedLabel = label.trim() || normalizedUsername; if (!normalizedUsername || !password) return; setSaving(true); setError(null); try { await onSave({ label: normalizedLabel, username: normalizedUsername, password, }); } catch (err) { setError(err instanceof Error ? err.message : '保存测试账号失败'); } finally { setSaving(false); } }; return (
event.target === event.currentTarget && onClose()}>

添加测试账号

保存到后台配置,后续管理员都可以直接复用。

{error &&

{error}

}
); } export function SystemTestsPage() { const [catalog, setCatalog] = useState([]); const [skillName, setSkillName] = useState(DEFAULT_SKILL); const [accounts, setAccounts] = useState([]); const [selectedAccountId, setSelectedAccountId] = useState(''); const [loading, setLoading] = useState(true); const [running, setRunning] = useState(false); const [accountsLoading, setAccountsLoading] = useState(true); const [accountBusyId, setAccountBusyId] = useState(null); const [showAddAccount, setShowAddAccount] = useState(false); const [error, setError] = useState(null); const [report, setReport] = useState(null); const loadCatalog = useCallback(async () => { setLoading(true); setError(null); try { const nextCatalog = await listSkillCatalog(); setCatalog(nextCatalog); if (nextCatalog.some((item) => item.name === DEFAULT_SKILL)) { setSkillName(DEFAULT_SKILL); } else if (nextCatalog[0]?.name) { setSkillName(nextCatalog[0].name); } } catch (err) { setError(err instanceof Error ? err.message : '加载 skill 列表失败'); } finally { setLoading(false); } }, []); useEffect(() => { void loadCatalog(); }, [loadCatalog]); const loadAccounts = useCallback(async () => { setAccountsLoading(true); try { const nextAccounts = await listSystemTestAccounts(); setAccounts(nextAccounts); setSelectedAccountId((current) => nextAccounts.some((item) => item.id === current) ? current : (nextAccounts[0]?.id ?? ''), ); } catch (err) { setError(err instanceof Error ? err.message : '加载测试账号失败'); } finally { setAccountsLoading(false); } }, []); useEffect(() => { void loadAccounts(); }, [loadAccounts]); const selectedSkill = useMemo( () => catalog.find((item) => item.name === skillName) ?? null, [catalog, skillName], ); const selectedAccount = useMemo( () => accounts.find((item) => item.id === selectedAccountId) ?? null, [accounts, selectedAccountId], ); const handleSaveAccount = async (payload: { label: string; username: string; password: string }) => { const account = await createSystemTestAccount(payload); setAccounts((current) => [account, ...current]); setSelectedAccountId(account.id); setShowAddAccount(false); }; const handleRemoveAccount = async () => { if (!selectedAccount) return; setAccountBusyId(selectedAccount.id); setError(null); try { await deleteSystemTestAccount(selectedAccount.id); const nextAccounts = accounts.filter((item) => item.id !== selectedAccount.id); setAccounts(nextAccounts); setSelectedAccountId(nextAccounts[0]?.id ?? ''); } catch (err) { setError(err instanceof Error ? err.message : '删除测试账号失败'); } finally { setAccountBusyId(null); } }; const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); if (!selectedAccount) { setError('请先选择一个测试账号,或先添加账号'); return; } setRunning(true); setError(null); try { const nextReport = await runSystemSkillValidation({ accountId: selectedAccount.id, username: '', password: '', skillName, }); setReport(nextReport); } catch (err) { setError(err instanceof Error ? err.message : '系统测试执行失败'); } finally { setRunning(false); } }; return (

系统测试验证

选择测试账号后,按选定 skill 执行整条服务联调,并汇总异常反馈。

{error &&

{error}

}

执行参数

默认推荐使用 service-integration-smoke,对真实账号跑登录、直连聊天、记忆读取和 skill 输出验证。

{loading ? (

加载 skill 列表中…

) : (
{selectedAccount ? (
当前使用: {selectedAccount.label} · @{selectedAccount.username} · 密码 {selectedAccount.passwordMasked}
) : (
还没有可用测试账号,请先添加账号和密码。
)}
{selectedSkill && (

当前 skill: {selectedSkill.label} · {selectedSkill.description}

)}
)}
{showAddAccount && ( setShowAddAccount(false)} onSave={handleSaveAccount} /> )} {report && ( <>

验证摘要

账号 @{report.account.username} · skill {report.selectedSkill} · Portal {report.portalBaseUrl}

总体结果
{report.ok ? '通过' : '有问题'}
开始 {formatTime(report.startedAt)} / 完成 {formatTime(report.finishedAt)}
通过
{report.summary.passed}
待处理
{report.summary.warnings}
失败
{report.summary.failed}

步骤结果

{report.steps.map((step) => ( ))}
步骤 状态 结果
{step.label} {statusLabel(step.status)}
{step.message}
{step.details && (
查看详情
                              {JSON.stringify(step.details, null, 2)}
                            
)}

问题反馈

{report.issues.length === 0 ? (

本轮未发现额外问题。

) : (
{report.issues.map((issue, index) => ( ))}
级别 步骤 问题
{issue.severity === 'error' ? '错误' : '提醒'} {issue.stepKey}
{issue.title}
{issue.detail}
)}
)}
); }