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`)}

主控健康

${statusBadge(health?.ok ? "healthy" : "attention")}
数据库
${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)}

最近被控端

${state.agents.length} devices
${renderAgentMiniList()}

最近任务

${renderTaskTable(state.tasks.slice(0, 8), { actions: false })}
`; } function renderAgentMiniList() { if (!state.agents.length) return `

暂无被控端接入

`; return `
${state.agents.slice(0, 4).map((agent) => `
${escapeHtml(agent.display_name || agent.public_id)} ${escapeHtml(agent.deployment_type || "-")}
${statusBadge(agent.status)}
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 `

智能探索 P0

${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 = `

系统测试

${renderSystemTestStatus()}

网站扫描

IP + Port
${renderSiteScanner()}

被控端

${state.agents.length} devices
${renderAgentMiniList()}

任务队列

${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 ` ${showDetails || showActions ? "" : ""} ${tasks.map((task) => ` ${showDetails || showActions ? ` ` : ""} `).join("")}
PriorityTaskPlanStatusAgentDurationActions
${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 ? ` ` : ""}
`; } 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 `

任务详情

${statusBadge(task.status)}
${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 `

任务详情

${statusBadge(task.status)}
${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} agents
${state.agents.length ? `
${state.agents.map((agent) => `
${escapeHtml(agent.display_name || agent.public_id)} ${escapeHtml(agent.public_id)}
${statusBadge(agent.status)}
部署形态
${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 = `

模型与执行参数

v${escapeHtml(state.modelConfig.version || 1)}
${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" })}

保存后版本递增,并通过被控端心跳下发;被控端应用新配置后会重启运行时。

配置同步

${syncedAgents}/${state.agents.length} agents
${syncRows}
Agent版本状态应用时间

测试结果

${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 `
${icon("activity")}

正在测试模型服务

正在访问模型服务,请稍候。

`; } if (!test.ok) { return `
${icon("x")}

连接失败

${escapeHtml(test.message || "模型服务暂时不可访问。")}

${test.url ? `
测试地址
${escapeHtml(test.url)}
` : ""}
`; } 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 = `

全局默认

v${escapeHtml(state.toolConfig.version || 1)}
${tools.map((tool) => ` `).join("")}
EnabledToolTypeSourceScenario
${escapeHtml(tool.name)}
${escapeHtml(tool.capability)}
${escapeHtml(tool.type)} ${escapeHtml(tool.source)} · ${escapeHtml(tool.version)} ${escapeHtml(tool.scenario)}

覆盖策略

${tools.map((tool) => ``).join("")}
${tools.map((tool) => ``).join("")}
`; } 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")}

核心指标

${(m.plans || []).join(", ") || "No plans"}
${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)}

失败清单

${(report.failed_tasks || []).length ? report.failed_tasks.map((task) => ` `).join("") : ``}
TaskPlanReason
${escapeHtml(task.title || task.external_id || task.id)}${escapeHtml(task.plan_name || "-")}${escapeHtml(task.reason || "-")}
暂无失败任务
`; } 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);