/* KA Guardian — poste de commande (/commander) */
const $ = (s) => document.querySelector(s);
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
const STATUS_FR = { ok: "ok", degraded: "dégradé", broken: "cassé", stale: "endormi" };
const STATE_FR = { open: "en file", dispatched: "envoi…", fixing: "en cours", watching: "surveillance",
cooldown: "attente", resolved: "livré", self_healed: "auto-guéri", abandoned: "terminé" };
const KIND_FR = { effort_new: "nouveau connecteur", effort_enrich: "enrichissement", effort_degrade: "inspection dégradés" };
const HINTS = {
effort_new: "L'agent cartographie l'existant, découvre des sources québécoises non couvertes (Serper), choisit la meilleure et construit le connecteur complet — testé en volume réel.",
effort_enrich: "L'agent maximise la valeur du connecteur choisi : pagination complète, champs des fiches détail, robustesse, détection des retraits — mesuré avant/après.",
effort_degrade: "L'agent inspecte tous les connecteurs dégradés de la plateforme (volume < 50 % de la médiane), trie baisse réelle / légitime / blocage, et répare un par un — un commit par connecteur.",
};
let STATE = null;
async function load() {
const r = await fetch("/api/state");
STATE = await r.json();
const id = STATE.identity, num = STATE.agent.replace("ka", "");
const rs = document.documentElement.style;
rs.setProperty("--agent", id.accent); rs.setProperty("--lime", id.accent);
if (id.accent_soft) rs.setProperty("--lime-soft", id.accent_soft);
if (id.accent2) rs.setProperty("--accent-deep", id.accent2);
document.title = `Commander — ${STATE.agent.toUpperCase()} Guardian`;
$("#wm-name").textContent = "ka·" + num;
$("#foot-name").textContent = "ka·" + num;
$("#hero-label").textContent = `${STATE.agent} — poste de commande · ${id.tagline}`;
$("#siblings").innerHTML = Object.entries(STATE.siblings).map(([a, v]) =>
`${a}·G`).join(" ");
const svcSel = $("#ef-service");
if (!svcSel.options.length) {
svcSel.innerHTML = Object.entries(STATE.services)
.map(([k, s]) => ``).join("");
}
renderEfforts();
}
function renderEfforts() {
const efforts = STATE.incidents.filter((i) => (i.status_detected || "").startsWith("effort"));
$("#efforts-list").innerHTML = efforts.slice(0, 20).map((i) => {
const d = JSON.parse(i.detail || "{}");
return `
${esc(KIND_FR[i.status_detected] || i.status_detected)} — ${esc((STATE.services[i.service] || {}).app || i.service)}
${esc(i.source)}${d.note ? " · « " + esc(d.note.slice(0, 80)) + " »" : ""}${d.max_minutes ? " · ≤" + d.max_minutes + " min" : ""}${d.max_cost_usd ? " · ≤" + d.max_cost_usd + " $" : ""}
${STATE_FR[i.state] || esc(i.state)} `;
}).join("") || `Aucun effort commandé encore — sois le premier à lui donner du travail.
`;
}
function syncKind() {
const kind = $("#ef-kind").value;
$("#ef-source-wrap").hidden = kind !== "effort_enrich";
$("#kind-hint").textContent = HINTS[kind] || "";
if (kind === "effort_enrich") fillSources();
}
async function fillSources() {
const r = await fetch("/api/connectors/" + $("#ef-service").value);
const d = await r.json();
$("#ef-source").innerHTML = d.connectors
.map((c) => ``).join("")
|| ``;
}
async function submitEffort(e) {
e.preventDefault();
const msg = $("#ef-msg"), btn = e.target.querySelector("button");
const token = $("#ef-token").value.trim();
if (!token) { msg.className = "cmd-msg err"; msg.textContent = "jeton d'opérateur requis"; return; }
localStorage.setItem("ka_token", token);
btn.disabled = true; msg.className = "cmd-msg"; msg.textContent = "lancement…";
try {
const r = await fetch("/api/admin/effort", {
method: "POST", headers: { "Content-Type": "application/json", "X-KA-Token": token },
body: JSON.stringify({
kind: $("#ef-kind").value, service: $("#ef-service").value,
source: $("#ef-source").value || "", note: $("#ef-note").value,
max_minutes: parseInt($("#ef-minutes").value, 10),
max_cost_usd: parseFloat($("#ef-cost").value),
}),
});
const d = await r.json();
if (r.ok && d.ok) {
msg.className = "cmd-msg ok";
msg.textContent = `effort accepté (${d.incident_id}) — la session démarre d'ici ~5 min. Suis-la sur le flux ⚡`;
$("#ef-note").value = "";
load();
} else {
msg.className = "cmd-msg err";
msg.textContent = "refusé: " + (d.detail || r.status);
}
} catch (err) {
msg.className = "cmd-msg err"; msg.textContent = "erreur: " + err;
} finally { btn.disabled = false; }
}
$("#ef-kind").addEventListener("change", syncKind);
$("#ef-service").addEventListener("change", () => { if ($("#ef-kind").value === "effort_enrich") fillSources(); });
$("#effort-form").addEventListener("submit", submitEffort);
$("#ef-token").value = localStorage.getItem("ka_token") || "";
load().then(syncKind);
setInterval(load, 30000);