const state = {
currentSection: "overview",
health: null,
agents: [],
tasks: [],
metrics: {},
modelConfig: { value: {}, version: 1 },
siteScan: {
status: "idle",
host: "",
ports: "80,443,3000,5173,8000-8010,8080,8081,9000",
sites: [],
message: "",
},
systemTest: {
targetUrl: "",
assignedAgentId: "",
maxDepth: 2,
status: "idle",
message: "",
},
discovery: {
status: "idle",
targetUrl: "",
planName: "智能探索 P0",
candidates: [],
summary: null,
message: "",
},
toolConfig: { tools: [], config: {}, version: 1 },
report: null,
selectedTaskId: "",
selectedToolAgent: "",
selectedToolPlan: "",
};
const titles = {
overview: "总览",
plans: "系统测试",
console: "运行控制台",
devices: "设备监控",
models: "模型配置",
tools: "Tool 配置",
metrics: "指标看板",
reports: "报告中心",
};
const $ = (selector, root = document) => root.querySelector(selector);
const $$ = (selector, root = document) => Array.from(root.querySelectorAll(selector));
function icon(name) {
return ``;
}
function escapeHtml(value) {
return String(value ?? "")
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function formatDate(value) {
if (!value) return "-";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return escapeHtml(value);
return date.toLocaleString();
}
function percent(value) {
const n = Number(value || 0);
return `${n.toFixed(n % 1 ? 1 : 0)}%`;
}
function duration(value) {
if (value == null || value === "") return "-";
const n = Number(value);
if (Number.isNaN(n)) return "-";
return n >= 60 ? `${(n / 60).toFixed(1)} min` : `${n.toFixed(1)} s`;
}
function statusBadge(status) {
const normalized = String(status || "-");
const ok = ["online", "idle", "succeeded", "healthy"].includes(normalized);
const warn = ["ready", "routed", "running", "registered", "claimed"].includes(normalized);
const fail = ["failed", "cancelled", "offline"].includes(normalized);
const cls = ok ? "ok" : fail ? "fail" : warn ? "warn" : "info";
return `${escapeHtml(normalized)}`;
}
function showToast(message) {
const toast = $("#toast");
toast.textContent = message;
toast.hidden = false;
clearTimeout(showToast.timer);
showToast.timer = setTimeout(() => {
toast.hidden = true;
}, 3200);
}
async function fetchJson(url, options = {}) {
const response = await fetch(url, {
...options,
headers: {
Accept: "application/json",
...(options.body ? { "Content-Type": "application/json; charset=utf-8" } : {}),
...(options.headers || {}),
},
});
const data = await response.json().catch(() => ({}));
if (!response.ok || data.ok === false) {
throw new Error(data.error || `${response.status} ${response.statusText}`);
}
return data;
}
async function postJson(url, payload = {}) {
return fetchJson(url, { method: "POST", body: JSON.stringify(payload) });
}
async function refreshAll() {
$("#refreshButton").disabled = true;
try {
const [health, dashboard, report] = await Promise.all([
fetchJson("/health"),
fetchJson("/api/dashboard"),
fetchJson("/api/reports/summary"),
]);
state.health = health;
state.agents = dashboard.agents || [];
state.tasks = dashboard.tasks || [];
state.metrics = dashboard.metrics || {};
state.modelConfig = dashboard.model_config || { value: {}, version: 1 };
state.toolConfig = dashboard.tool_config || { tools: [], config: {}, version: 1 };
state.report = report.report;
renderAll();
} catch (error) {
renderHealthError(error);
showToast(error.message);
} finally {
$("#refreshButton").disabled = false;
}
}
function renderHealthError(error) {
const pill = $("#healthPill");
pill.classList.remove("ok");
pill.classList.add("fail");
pill.textContent = "offline";
$("#serviceMeta").textContent = error.message;
}
function renderChrome() {
const health = state.health;
const pill = $("#healthPill");
pill.classList.toggle("ok", Boolean(health?.ok));
pill.classList.toggle("fail", !health?.ok);
pill.textContent = health?.ok ? "healthy" : "attention";
$("#sectionTitle").textContent = titles[state.currentSection];
$("#serviceMeta").textContent = health
? `${health.name} · ${health.public_base_url} · ${health.deployment_mode}`
: "Loading...";
}
function renderAll() {
renderChrome();
renderCurrentView();
}
function renderCurrentView() {
if (state.currentSection === "overview") renderOverview();
if (state.currentSection === "plans") renderPlans();
if (state.currentSection === "console") renderConsole();
if (state.currentSection === "devices") renderDevices();
if (state.currentSection === "models") renderModels();
if (state.currentSection === "tools") renderTools();
if (state.currentSection === "metrics") renderMetrics();
if (state.currentSection === "reports") renderReports();
}
function kpi(label, value, hint) {
return `
${escapeHtml(value)}${escapeHtml(hint)}
`;
}
function renderOverview() {
const m = state.metrics;
const health = state.health;
$("#view-overview").innerHTML = `
${kpi("在线被控端", `${m.agent_active || 0}/${m.agent_total || 0}`, "盒子物理机 / 虚拟机")}
${kpi("待执行任务", m.ready_tasks || 0, "ready + routed")}
${kpi("运行中", m.running_tasks || 0, "Agent 占用")}
${kpi("通过率", percent(m.pass_rate), `${m.succeeded_tasks || 0} passed / ${m.failed_tasks || 0} failed`)}
- 数据库
- ${health?.checks?.database?.ok ? "OK" : "FAIL"}
- 任务数
- ${escapeHtml(health?.checks?.database?.task_count ?? 0)}
- 端口
- ${escapeHtml(`${health?.checks?.port?.host || "-"}:${health?.checks?.port?.port || "-"}`)}
- 静态面板
- ${health?.checks?.static_panel?.ok ? "OK" : "MISSING"}
- 启动时间
- ${formatDate(health?.started_at)}
${renderTaskTable(state.tasks.slice(0, 8), { actions: false })}
`;
}
function renderAgentMiniList() {
if (!state.agents.length) return `暂无被控端接入
`;
return `
${state.agents.slice(0, 4).map((agent) => `
- IP
- ${escapeHtml(agent.reported_ip || agent.reported_host || "-")}
- 日志端口
- ${escapeHtml(agent.reported_log_web_port || "-")}
- 心跳
- ${formatDate(agent.last_seen_at)}
`).join("")}
`;
}
function renderDiscoveryPanel() {
const discovery = state.discovery;
const cfg = state.modelConfig.value || {};
const targetUrl = discovery.targetUrl || cfg.target_system_url || "";
const selectedCount = (discovery.candidates || []).filter((candidate) => candidate.selected !== false).length;
return `
${renderDiscoveryResult()}
`;
}
function renderDiscoveryResult() {
const discovery = state.discovery;
if (discovery.status === "idle") {
return `输入待测系统地址后点击“发现任务”,Agent 会先基于首页结构拆出 P0 子任务。
`;
}
if (discovery.status === "running") {
return `正在读取页面结构并生成候选任务...
`;
}
if (discovery.status === "error") {
return `${escapeHtml(discovery.message || "发现任务失败")}
`;
}
const candidates = discovery.candidates || [];
if (!candidates.length) {
return `未发现可入队任务。
`;
}
const summary = discovery.summary || {};
const rows = candidates.map((candidate) => {
const depth = Math.max(0, Number(candidate.depth || 0));
const risk = candidate.risk_level || "low";
const selected = candidate.selected !== false;
const preconditions = Array.isArray(candidate.preconditions) && candidate.preconditions.length
? `前置:${escapeHtml(candidate.preconditions.join("、"))}`
: "";
return `
`;
}).join("");
return `
页面:${escapeHtml(summary.page_title || summary.final_url || summary.target_url || "-")}
候选 ${escapeHtml(summary.candidate_count || candidates.length)} 个
链接 ${escapeHtml(summary.link_count || 0)} / 表单 ${escapeHtml(summary.form_count || 0)} / 入口 ${escapeHtml(summary.button_count || 0)}
${rows}
`;
}
function activeAgents() {
return state.agents.filter((agent) => ["online", "idle", "running", "registered"].includes(String(agent.status || "")));
}
function systemTestTargetUrl() {
const cfg = state.modelConfig.value || {};
return state.systemTest.targetUrl || cfg.target_system_url || "";
}
function renderSystemTestAgentOptions() {
const selected = String(state.systemTest.assignedAgentId || "");
return [``]
.concat(state.agents.map((agent) => {
const label = `${agent.display_name || agent.public_id || agent.id} ${agent.reported_ip || ""} ${agent.status || ""}`.trim();
return ``;
}))
.join("");
}
function renderSystemTestStatus() {
const activeCount = activeAgents().length;
const status = state.systemTest.status;
const message = state.systemTest.message || (activeCount ? `${activeCount} 个被控端可接收任务` : "暂无在线被控端,任务会先进入队列");
const cls = status === "error" ? "fail" : status === "starting" ? "warn" : activeCount ? "ok" : "info";
return `${escapeHtml(status)}${escapeHtml(message)}
`;
}
function readSystemTestForm() {
const targetUrl = $("#systemTestTargetUrl")?.value.trim() || "";
const assignedAgentId = $("#systemTestAgent")?.value || "";
const maxDepth = Math.max(1, Math.min(5, Number($("#systemTestMaxDepth")?.value || 2)));
state.systemTest = {
...state.systemTest,
targetUrl,
assignedAgentId,
maxDepth,
};
return { target_url: targetUrl, assigned_agent_id: assignedAgentId || null, max_depth: maxDepth };
}
function renderPlans() {
const targetUrl = systemTestTargetUrl();
$("#view-plans").innerHTML = `
${renderTaskTable(state.tasks, { actions: false })}
${renderTaskDetail()}
`;
}
function renderConsole() {
$("#view-console").innerHTML = `
${kpi("队列", state.metrics.ready_tasks || 0, "ready / routed")}
${kpi("运行", state.metrics.running_tasks || 0, "running")}
${kpi("完成", state.metrics.completed_tasks || 0, "succeeded / failed / cancelled")}
${kpi("并发利用", percent(state.metrics.concurrency_utilization), "running / online agents")}
${renderTaskTable(state.tasks, { actions: true })}
${renderTaskDetail()}
`;
}
function renderTaskMeta(task) {
const level = task.task_level || task.payload?.level || "";
const risk = task.risk_level || task.payload?.risk_level || "";
const depth = Number(task.depth || task.payload?.depth || 0);
const parts = [];
if (level) parts.push(`层级 ${depth} · ${level}`);
if (risk) parts.push(`风险 ${risk}`);
if (task.parent_external_id) parts.push(`父任务 ${task.parent_external_id}`);
return parts.length ? `${escapeHtml(parts.join(" / "))}` : "";
}
function renderTaskTable(tasks, options = { actions: true }) {
if (!tasks.length) return ``;
const showActions = options.actions !== false;
const showDetails = options.details !== false;
const agentOptions = [``]
.concat(state.agents.map((a) => ``))
.join("");
return `
| Priority | Task | Plan | Status | Agent | Duration | ${showDetails || showActions ? "Actions | " : ""}
${tasks.map((task) => `
| ${escapeHtml(task.priority)} |
${escapeHtml(task.title || task.external_id || task.id)}
${escapeHtml(task.external_id || `#${task.id}`)}
${renderTaskMeta(task)}
|
${escapeHtml(task.plan_name || "-")} |
${statusBadge(task.status)} |
${escapeHtml(task.assigned_agent_name || task.assigned_agent_id || "-")} |
${duration(task.duration_seconds)} |
${showDetails || showActions ? `
${showDetails ? `` : ""}
${showActions ? `
` : ""}
| ` : ""}
`).join("")}
`;
}
function selectedTask() {
const selectedId = String(state.selectedTaskId || "");
if (selectedId) {
const task = state.tasks.find((item) => String(item.id) === selectedId);
if (task) return task;
}
return state.tasks.find((item) => item.result) || state.tasks[0] || null;
}
function taskAgent(task) {
if (!task?.assigned_agent_id) return null;
return state.agents.find((agent) => String(agent.id) === String(task.assigned_agent_id)) || null;
}
function screenshotHref(task, imagePath) {
const agent = taskAgent(task);
const host = agent?.reported_ip || agent?.reported_host || "";
const port = agent?.reported_log_web_port || "";
if (!host || !port || !imagePath) return "";
return `http://${host}:${port}/agent/screenshot?path=${encodeURIComponent(imagePath)}`;
}
function renderTaskDetail() {
const task = selectedTask();
if (!task) {
return ``;
}
if (!task.result) {
const payload = task.payload || {};
const agent = taskAgent(task);
const agentStatus = String(agent?.status || "").toLowerCase();
const likelyInterrupted = task.status === "running" && ["idle", "offline", "registered"].includes(agentStatus);
const progressText = task.status === "running"
? likelyInterrupted
? "被控端已恢复为空闲但任务没有结果回传,可能执行中断;请重试该任务。"
: "被控端已领取任务,正在等待执行结果回传。"
: task.status === "ready"
? "任务已进入队列,等待空闲被控端领取。"
: task.status === "cancelled"
? "任务已取消,未产生执行结果。"
: "任务尚未产生执行结果。";
return `
${escapeHtml(task.title || task.external_id || task.id)}
${escapeHtml(task.external_id || `#${task.id}`)}
- 目标 URL
- ${escapeHtml(payload.target_url || payload.url || "-")}
- 被控端
- ${escapeHtml(agent?.display_name || task.assigned_agent_name || "-")}
- 执行状态
- ${escapeHtml(progressText)}
- 创建时间
- ${formatDate(task.created_at)}
- 领取时间
- ${formatDate(task.claimed_at)}
- 更新时间
- ${formatDate(task.updated_at)}
- 执行结论
- ${escapeHtml(task.last_error || "-")}
模型分析
任务结果尚未回传,暂时没有模型调用记录。
`;
}
const result = task.result || {};
const observation = result.observation || {};
const browserOpen = result.browser_open || {};
const modelAnalysis = result.model_analysis || {};
const modelUsed = Boolean(modelAnalysis.model_used || result.host_decision?.model_used || result.app_decision?.model_used);
const modelErrors = [...new Set([
...(Array.isArray(result.model_errors) ? result.model_errors : []),
modelAnalysis.error,
result.host_decision?.model_error,
result.app_decision?.model_error,
].filter(Boolean).map((item) => String(item)))];
const modelCallAttempted = Boolean(
modelUsed
|| modelErrors.length
|| modelAnalysis.model_called
|| modelAnalysis.model_duration_seconds
|| result.host_decision?.model_duration_seconds
|| result.app_decision?.model_duration_seconds
);
const modelState = !result.model_available
? "未配置"
: modelUsed
? "已调用"
: modelErrors.length
? "调用失败"
: modelCallAttempted
? "已调用但无有效结果"
: "未调用";
const discoverySource = result.discovery_source === "llm" ? "大模型生成" : result.discovery_source === "rule" ? "规则回退" : "-";
const screenshots = Array.isArray(result.screenshots) ? result.screenshots : [];
const submitted = Array.isArray(result.submitted_child_tasks) ? result.submitted_child_tasks : [];
const discovered = Array.isArray(result.discovered_child_tasks) ? result.discovered_child_tasks : [];
const logs = Array.isArray(result.logs) ? result.logs : [];
const needsUserInput = result.needs_user_input || task.status === "failed";
return `
${escapeHtml(task.title || task.external_id || task.id)}
${escapeHtml(task.external_id || `#${task.id}`)}
- 目标 URL
- ${escapeHtml(result.target_url || observation.url || (task.payload || {}).target_url || "-")}
- 最终 URL
- ${escapeHtml(result.final_url || observation.final_url || "-")}
- 页面标题
- ${escapeHtml(result.page_title || observation.page_title || "-")}
- HTTP 状态
- ${escapeHtml(result.http_status || observation.http_status || "-")}
- 浏览器打开
- ${browserOpen.opened ? "已打开" : result.browser_open ? "未打开" : "-"}
- 大模型
- ${escapeHtml(modelState)}${result.model_duration_seconds ? ` · ${duration(result.model_duration_seconds)}` : ""}
- 任务来源
- ${escapeHtml(discoverySource)}
- 耗时
- ${duration(result.duration_seconds || task.duration_seconds)}
- 人工介入
- ${needsUserInput ? '需要确认' : '无需'}
- 执行结论
- ${escapeHtml(result.message || task.last_error || "-")}
模型分析
${modelUsed ? `
- ${escapeHtml(modelAnalysis.summary || "模型已参与分析,但未返回摘要。")}
${(Array.isArray(modelAnalysis.coverage_notes) ? modelAnalysis.coverage_notes : []).slice(0, 5).map((note) => `- ${escapeHtml(note)}
`).join("")}
${modelAnalysis.user_input_reason ? `- 需要用户补充:${escapeHtml(modelAnalysis.user_input_reason)}
` : ""}
` : modelErrors.length ? `
${modelErrors.map((error) => `- 调用失败:${escapeHtml(error)}
`).join("")}
本轮已回退到规则探索。
` : `
${result.model_available ? "本轮未使用模型生成有效分析,已回退到规则探索。" : "未配置可用模型,本轮使用规则探索。"}
`}
页面发现
- 链接:${escapeHtml(observation.link_count ?? "-")}
- 表单:${escapeHtml(observation.form_count ?? "-")}
- 按钮:${escapeHtml(observation.button_count ?? "-")}
- 登录表单:${observation.has_login_form ? "是" : "否"}
证据截图
${screenshots.length ? `
${screenshots.map((shot) => {
const imagePath = shot.image_path || shot.path || "";
const href = screenshotHref(task, imagePath);
const label = imagePath || JSON.stringify(shot);
return `- ${href ? `${escapeHtml(label)}` : escapeHtml(label)}
`;
}).join("")}
` : `
暂无截图
`}
已加入队列的子任务
${submitted.length ? `
${submitted.map((child) => `- ${escapeHtml(child.title || child.external_id)}层级 ${escapeHtml(child.depth ?? "-")} · ${escapeHtml(child.task_level || "-")} · ${escapeHtml(child.source || discoverySource || "-")}${child.llm_reason ? `${escapeHtml(child.llm_reason)}` : ""}
`).join("")}
` : `
暂无子任务
`}
发现候选
${discovered.length ? `
${discovered.slice(0, 8).map((child) => `- ${escapeHtml(child.title || child.external_id)}${escapeHtml(child.target_url || "-")}
`).join("")}
` : `
暂无候选
`}
日志
${logs.length ? `
${logs.slice(-8).map((line) => `- ${escapeHtml(line)}
`).join("")}
` : `
暂无日志
`}
`;
}
function renderDevices() {
$("#view-devices").innerHTML = `
${state.agents.length ? `
${state.agents.map((agent) => `
- 部署形态
- ${escapeHtml(agent.deployment_type || "-")}
- Agent 版本
- ${escapeHtml(agent.agent_version || "-")}
- Host
- ${escapeHtml(agent.reported_host || "-")}
- IP
- ${escapeHtml(agent.reported_ip || "-")}
- 配置版本
- v${escapeHtml(agent.config_revision || 0)}${agent.config_applied_at ? ` · ${formatDate(agent.config_applied_at)}` : ""}
- 日志页
- ${agent.reported_ip && agent.reported_log_web_port ? `打开 ${escapeHtml(agent.reported_log_web_port)}` : "-"}
- 最后心跳
- ${formatDate(agent.last_seen_at)}
`).join("")}
` : `
暂无被控端接入
`}
`;
}
function renderModels() {
const cfg = state.modelConfig.value || {};
const targetVersion = Number(state.modelConfig.version || 1);
const syncedAgents = state.agents.filter((agent) => Number(agent.config_revision || 0) >= targetVersion).length;
const syncRows = state.agents.length
? state.agents.map((agent) => {
const revision = Number(agent.config_revision || 0);
const isSynced = revision >= targetVersion;
return `
|
${escapeHtml(agent.display_name || agent.public_id)}
${escapeHtml(agent.reported_ip || agent.public_id)}
|
v${escapeHtml(revision)} |
${isSynced ? 'synced' : 'pending'} |
${formatDate(agent.config_applied_at)} |
`;
}).join("")
: `| 暂无被控端接入 |
`;
$("#view-models").innerHTML = `
${modelInput("target_system_url", "待测系统 URL", cfg.target_system_url, "url", { placeholder: "http://host:port" })}
${renderSiteScanner()}
${modelInput("vllm_base_url", "vLLM / OpenAI Base URL", cfg.vllm_base_url, "url", { placeholder: "http://host:8000/v1" })}
${modelInput("vllm_model", "模型名称", cfg.vllm_model, "text", { placeholder: "qwen2.5-vl / local-model" })}
${modelInput("grounding_url", "Grounding URL", cfg.grounding_url, "url", { placeholder: "http://host:port" })}
${modelInput("grounding_model", "Grounding 模型", cfg.grounding_model, "text", { placeholder: "grounding-model" })}
${modelSelect("ground_coordinate_mode", "坐标模式", cfg.ground_coordinate_mode || "absolute", [["absolute", "absolute"], ["normalized", "normalized"]])}
${modelInput("resolution", "分辨率", cfg.resolution, "text", { placeholder: "1920x1080" })}
${modelInput("screenshot_max_width", "截图最大宽", cfg.screenshot_max_width, "number", { min: 1, max: 7680 })}
${modelInput("screenshot_max_height", "截图最大高", cfg.screenshot_max_height, "number", { min: 1, max: 7680 })}
${modelInput("max_steps", "最大步数", cfg.max_steps, "number", { min: 1, max: 500 })}
${modelInput("request_timeout_seconds", "请求超时秒", cfg.request_timeout_seconds, "number", { min: 1, max: 300 })}
${modelInput("temperature", "Temperature", cfg.temperature, "number", { min: 0, max: 2, step: "0.1" })}
保存后版本递增,并通过被控端心跳下发;被控端应用新配置后会重启运行时。
| Agent | 版本 | 状态 | 应用时间 |
${syncRows}
${renderModelTestResult(null)}
`;
}
function renderSiteScanner() {
const scan = state.siteScan;
return `
${renderSiteScanResult()}
`;
}
function renderSiteScanResult() {
const scan = state.siteScan;
if (scan.status === "idle") {
return `尚未扫描
`;
}
if (scan.status === "running") {
return `正在扫描可访问网站...
`;
}
if (scan.status === "error") {
return `${escapeHtml(scan.message || "扫描失败")}
`;
}
const sites = scan.sites || [];
if (!sites.length) {
return `未发现可访问网站
`;
}
const rows = sites.map((site) => {
const address = site.address || `${site.ip || site.host}:${site.port}`;
const statusText = site.status ? `HTTP ${site.status}` : "HTTP";
const elapsed = site.elapsed_seconds != null ? `${Number(site.elapsed_seconds).toFixed(2)}s` : "-";
return `
${escapeHtml(site.name || address)}
${escapeHtml(site.url || "")}
${escapeHtml(address)} · ${escapeHtml(statusText)} · ${escapeHtml(elapsed)}
`;
}).join("");
return `
发现 ${sites.length} 个可访问网站
${rows}
`;
}
function renderModelTestResult(test) {
if (!test) {
return `
${icon("activity")}
尚未测试
点击“连通性测试”后,这里会显示模型服务是否可访问、响应耗时和发现的模型。
`;
}
if (test.status === "running") {
return `
`;
}
if (!test.ok) {
return `
${icon("x")}
连接失败
${escapeHtml(test.message || "模型服务暂时不可访问。")}
${test.url ? `
- 测试地址
- ${escapeHtml(test.url)}
` : ""}
- 确认模型服务已启动,并且主控主机可以访问该地址。
- 检查 Base URL 是否包含正确端口,例如 http://host:8000/v1。
- 如果服务需要鉴权,请确认 API Key 已保存且未过期。
`;
}
const models = normalizeModelList(test.models);
const modelList = models.length
? `${models.map((name) => `${escapeHtml(name)}`).join("")}
`
: `接口可访问,但没有返回可展示的模型名称。
`;
return `
${icon("check")}
连接成功
模型服务已响应,可以用于后续测试执行。
- 测试地址
- ${escapeHtml(test.url || "-")}
- 响应耗时
- ${duration(test.elapsed_seconds)}
发现的模型
${modelList}
`;
}
function normalizeModelList(models) {
if (!Array.isArray(models)) return [];
return models
.map((item) => {
if (typeof item === "string") return item;
if (!item || typeof item !== "object") return "";
return item.id || item.name || item.model || item.object || "";
})
.filter(Boolean)
.slice(0, 12);
}
function modelInput(id, label, value, type = "text", attrs = {}) {
const attrText = Object.entries(attrs)
.filter(([key]) => key !== "full")
.map(([key, val]) => `${key}="${escapeHtml(val)}"`)
.join(" ");
return ``;
}
function modelSelect(id, label, value, options) {
return `
`;
}
function readModelForm() {
const fields = [
"target_system_url",
"vllm_base_url",
"vllm_model",
"api_key",
"grounding_url",
"grounding_model",
"ground_coordinate_mode",
"resolution",
"screenshot_max_width",
"screenshot_max_height",
"max_steps",
"request_timeout_seconds",
"temperature",
];
const payload = {};
fields.forEach((field) => {
const node = $(`#${field}`);
payload[field] = node.type === "number" ? Number(node.value || 0) : node.value.trim();
});
payload.clear_api_key = Boolean($("#clear_api_key")?.checked);
return payload;
}
function readSiteScanForm() {
const host = $("#site_scan_host")?.value.trim() || "";
const ports = $("#site_scan_ports")?.value.trim() || "";
state.siteScan.host = host;
state.siteScan.ports = ports || state.siteScan.ports;
return { host, ports, timeout_seconds: 0.8 };
}
function readDiscoveryForm() {
const targetUrl = $("#discoveryTargetUrl")?.value.trim() || "";
const planName = $("#discoveryPlanName")?.value.trim() || "智能探索 P0";
state.discovery.targetUrl = targetUrl;
state.discovery.planName = planName;
return { target_url: targetUrl, plan_name: planName, timeout_seconds: 8 };
}
function selectedDiscoveryCandidates() {
const checked = new Set($$(".discovery-checkbox:checked").map((node) => node.dataset.taskId));
state.discovery.candidates = (state.discovery.candidates || []).map((candidate) => ({
...candidate,
selected: checked.has(String(candidate.id)),
}));
return state.discovery.candidates.filter((candidate) => candidate.selected);
}
function renderTools() {
const tools = state.toolConfig.tools || [];
const config = state.toolConfig.config || {};
const globalEnabled = config.global_enabled || {};
const agentOptions = [``]
.concat(state.agents.map((a) => ``))
.join("");
const agentOverride = (config.agent_overrides || {})[state.selectedToolAgent] || {};
const planOverride = (config.plan_overrides || {})[state.selectedToolPlan] || {};
$("#view-tools").innerHTML = `
`;
}
function renderMetrics() {
const m = state.metrics;
const rows = [
["通过率", m.pass_rate],
["失败率", m.fail_rate],
["覆盖率", m.coverage_rate],
["并发利用率", m.concurrency_utilization],
["人工介入率", m.manual_intervention_rate],
["异常可恢复率", m.recovery_rate],
["Tool 配置一致率", m.tool_consistency_rate],
];
$("#view-metrics").innerHTML = `
${kpi("任务总数", m.total_tasks || 0, "all plans")}
${kpi("平均耗时", duration(m.avg_duration_seconds), "completed tasks")}
${kpi("通过", m.succeeded_tasks || 0, "succeeded")}
${kpi("失败", m.failed_tasks || 0, "failed")}
${rows.map(([label, value]) => `
${escapeHtml(label)}
${percent(value)}
`).join("")}
`;
}
function renderReports() {
const report = state.report || { metrics: {}, plans: [], failed_tasks: [] };
$("#view-reports").innerHTML = `
- 生成时间
- ${formatDate(report.generated_at)}
- 总任务
- ${escapeHtml(report.metrics?.total_tasks || 0)}
- 通过率
- ${percent(report.metrics?.pass_rate)}
- 覆盖率
- ${percent(report.metrics?.coverage_rate)}
| Task | Plan | Reason |
${(report.failed_tasks || []).length ? report.failed_tasks.map((task) => `
| ${escapeHtml(task.title || task.external_id || task.id)} | ${escapeHtml(task.plan_name || "-")} | ${escapeHtml(task.reason || "-")} |
`).join("") : `| 暂无失败任务 |
`}
`;
}
function estimateTasks(raw) {
if (Array.isArray(raw)) return raw.reduce((sum, item) => sum + (item && Array.isArray(item.tasks) ? item.tasks.length : 1), 0);
if (raw && Array.isArray(raw.plans)) return raw.plans.reduce((sum, plan) => sum + (Array.isArray(plan.tasks) ? plan.tasks.length : 0), 0);
if (raw && Array.isArray(raw.tasks)) return raw.tasks.length;
if (raw && raw.tasks && typeof raw.tasks === "object") return Object.keys(raw.tasks).length;
return raw && typeof raw === "object" ? 1 : 0;
}
function samplePlan() {
return JSON.stringify(
{
plan_name: "ERP Smoke Plan",
tasks: [
{
task_id: "ERP-LOGIN-001",
title: "登录系统",
priority: 10,
tags: ["login", "smoke"],
instruction: "打开待测系统,使用测试账号登录,确认首页可访问。",
timeout: 300,
task_failure_policy: "terminate",
},
{
task_id: "ERP-QUERY-001",
title: "查询采购单",
priority: 20,
tags: ["purchase"],
instruction: "进入采购模块,按单号查询并确认列表展示结果。",
timeout: 300,
task_failure_policy: "skip",
},
],
},
null,
2
);
}
function downloadJson(filename, value) {
const blob = new Blob([JSON.stringify(value, null, 2)], { type: "application/json" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
}
document.addEventListener("click", async (event) => {
const nav = event.target.closest(".nav-item");
if (nav) {
setSection(nav.dataset.section);
return;
}
const jump = event.target.closest("[data-section-jump]");
if (jump) {
setSection(jump.dataset.sectionJump);
return;
}
const button = event.target.closest("[data-action]");
if (!button) return;
const action = button.dataset.action;
try {
if (action === "start-system-test") {
const form = readSystemTestForm();
if (!form.target_url) {
state.systemTest.status = "error";
state.systemTest.message = "请先选择或填写待测系统 URL";
renderPlans();
return;
}
state.systemTest.status = "starting";
state.systemTest.message = "正在创建根任务";
renderPlans();
const result = await postJson("/api/system-tests/start", form);
state.systemTest.status = "started";
state.systemTest.message = `已创建根任务:${result.task?.external_id || result.task?.id || "-"}`;
showToast("系统测试已启动,等待被控端领取任务");
setSection("console");
await refreshAll();
} else if (action === "sample-plan") {
$("#planJson").value = samplePlan();
$("#planValidation").textContent = "已填入样例";
} else if (action === "validate-plan") {
const raw = JSON.parse($("#planJson").value || "{}");
$("#planValidation").textContent = `JSON 有效,可导入 ${estimateTasks(raw)} 条任务`;
} else if (action === "import-plan") {
const content = $("#planJson").value;
const result = await postJson("/api/tasks/import", { content });
showToast(`已导入 ${result.count} 条任务`);
await refreshAll();
} else if (action === "discover-site") {
const form = readDiscoveryForm();
const resultContainer = $("#discoveryResult");
state.discovery.status = "running";
state.discovery.message = "";
state.discovery.candidates = [];
state.discovery.summary = null;
if (resultContainer) resultContainer.innerHTML = renderDiscoveryResult();
button.disabled = true;
try {
const result = await postJson("/api/discovery/preview", form);
state.discovery.status = "done";
state.discovery.summary = result.summary || null;
state.discovery.candidates = result.candidates || [];
state.discovery.message = "";
renderPlans();
showToast(`发现 ${state.discovery.candidates.length} 个候选任务`);
} catch (error) {
state.discovery.status = "error";
state.discovery.message = error.message;
if (resultContainer) resultContainer.innerHTML = renderDiscoveryResult();
} finally {
button.disabled = false;
}
} else if (action === "toggle-discovery-all") {
const boxes = $$(".discovery-checkbox");
const shouldCheck = boxes.some((box) => !box.checked);
boxes.forEach((box) => {
box.checked = shouldCheck;
});
} else if (action === "import-discovery-tasks") {
const form = readDiscoveryForm();
const tasks = selectedDiscoveryCandidates();
if (!tasks.length) {
showToast("请先选择要入队的任务");
return;
}
const result = await postJson("/api/discovery/import", {
target_url: form.target_url,
plan_name: form.plan_name,
tasks,
});
showToast(`已入队 ${result.count} 个智能探索任务`);
await refreshAll();
} else if (action === "export-tasks") {
downloadJson("system-test-tasks.json", state.tasks);
} else if (action === "create-task") {
await postJson("/api/tasks", {
task_id: $("#manualTaskId").value.trim(),
title: $("#manualTitle").value.trim(),
plan_name: $("#manualPlan").value.trim(),
priority: Number($("#manualPriority").value || 100),
failure_policy: $("#manualPolicy").value,
instruction: $("#manualInstruction").value.trim(),
});
showToast("任务已保存");
await refreshAll();
} else if (action === "reload-tasks") {
const status = $("#taskStatusFilter")?.value;
const data = await fetchJson(status ? `/api/tasks?status=${encodeURIComponent(status)}` : "/api/tasks");
state.tasks = data.tasks || [];
renderConsole();
} else if (action === "show-task-detail") {
state.selectedTaskId = button.dataset.taskId || "";
if (state.currentSection === "console") {
renderConsole();
} else {
renderPlans();
}
} else if (action === "assign-task") {
const taskId = button.dataset.taskId;
const agentId = $(`#assign-${taskId}`).value;
await postJson(`/api/tasks/${taskId}/assign`, { agent_id: agentId || null, reason: "manual dispatch from console" });
showToast("分派已保存");
await refreshAll();
} else if (action === "retry-task") {
await postJson(`/api/tasks/${button.dataset.taskId}/retry`, { reason: "retry from console" });
showToast("任务已回到队列");
await refreshAll();
} else if (action === "abort-task") {
await postJson(`/api/tasks/${button.dataset.taskId}/abort`, { reason: "aborted from console" });
showToast("任务已中止");
await refreshAll();
} else if (action === "save-model") {
const saved = await postJson("/api/config/model", readModelForm());
state.modelConfig = saved;
showToast(`模型配置已保存 v${saved.version}`);
await refreshAll();
} else if (action === "test-model") {
const container = $("#modelTestResult");
container.innerHTML = renderModelTestResult({ status: "running" });
try {
const result = await postJson("/api/models/test", readModelForm());
container.innerHTML = renderModelTestResult(result.test);
} catch (error) {
container.innerHTML = renderModelTestResult({ ok: false, message: error.message });
}
} else if (action === "scan-sites") {
const resultContainer = $("#siteScanResult");
button.disabled = true;
state.siteScan.status = "running";
state.siteScan.message = "";
state.siteScan.sites = [];
resultContainer.innerHTML = renderSiteScanResult();
try {
const result = await postJson("/api/sites/scan", readSiteScanForm());
state.siteScan.status = "done";
state.siteScan.sites = result.sites || [];
state.siteScan.message = "";
resultContainer.innerHTML = renderSiteScanResult();
showToast(`发现 ${state.siteScan.sites.length} 个可访问网站`);
} catch (error) {
state.siteScan.status = "error";
state.siteScan.message = error.message;
resultContainer.innerHTML = renderSiteScanResult();
} finally {
button.disabled = false;
}
} else if (action === "select-site") {
const url = button.dataset.siteUrl || "";
state.systemTest.targetUrl = url;
const systemTestInput = $("#systemTestTargetUrl");
if (systemTestInput && url) {
systemTestInput.value = url;
}
const targetInput = $("#target_system_url");
if (targetInput && url) {
targetInput.value = url;
}
if (url) showToast("已填入待测系统 URL");
} else if (action === "save-tools") {
await saveTools();
showToast("Tool 策略已保存");
await refreshAll();
} else if (action === "download-report") {
downloadJson("system-test-report-summary.json", state.report || {});
}
} catch (error) {
showToast(error.message);
}
});
document.addEventListener("change", (event) => {
if (event.target.id === "toolAgentSelect") {
state.selectedToolAgent = event.target.value;
renderTools();
}
});
document.addEventListener("input", (event) => {
if (event.target.id === "toolPlanName") {
state.selectedToolPlan = event.target.value.trim();
}
});
$("#refreshButton").addEventListener("click", refreshAll);
function setSection(section) {
state.currentSection = section;
$$(".nav-item").forEach((item) => item.classList.toggle("is-active", item.dataset.section === section));
$$(".view").forEach((view) => view.classList.toggle("is-active", view.id === `view-${section}`));
renderChrome();
renderCurrentView();
}
async function saveTools() {
const current = state.toolConfig.config || {};
const config = {
global_enabled: { ...(current.global_enabled || {}) },
agent_overrides: { ...(current.agent_overrides || {}) },
plan_overrides: { ...(current.plan_overrides || {}) },
};
$$("[data-tool-global]").forEach((node) => {
config.global_enabled[node.dataset.toolGlobal] = node.checked;
});
if (state.selectedToolAgent) {
config.agent_overrides[state.selectedToolAgent] = {};
$$("[data-tool-agent]").forEach((node) => {
config.agent_overrides[state.selectedToolAgent][node.dataset.toolAgent] = node.checked;
});
}
const planName = $("#toolPlanName")?.value.trim();
if (planName) {
config.plan_overrides[planName] = {};
$$("[data-tool-plan]").forEach((node) => {
config.plan_overrides[planName][node.dataset.toolPlan] = node.checked;
});
state.selectedToolPlan = planName;
}
const saved = await postJson("/api/tools", { tools: state.toolConfig.tools, config });
state.toolConfig = saved;
}
refreshAll();
setInterval(() => {
if (!["plans", "models", "tools"].includes(state.currentSection)) {
refreshAll();
}
}, 5000);