function getConfig() { return { apiKey: document.getElementById("apiKey").value.trim(), releaseName: document.getElementById("releaseName").value.trim(), }; } function saveConfig() { const cfg = getConfig(); cfg.chatSessionId = document.getElementById("chatSessionId").value.trim(); localStorage.setItem("assistant_ui_cfg", JSON.stringify(cfg)); } function loadConfig() { try { const raw = localStorage.getItem("assistant_ui_cfg"); if (!raw) return; const cfg = JSON.parse(raw); document.getElementById("apiKey").value = cfg.apiKey || ""; document.getElementById("releaseName").value = cfg.releaseName || ""; document.getElementById("chatSessionId").value = cfg.chatSessionId || "main"; } catch (_) {} } async function apiGet(path, params) { const cfg = getConfig(); const url = new URL(path, window.location.origin); Object.entries(params || {}).forEach(([k, v]) => { if (v !== null && v !== undefined && String(v).length > 0) url.searchParams.set(k, String(v)); }); const r = await fetch(url, { headers: { "X-Admin-Api-Key": cfg.apiKey }, }); if (!r.ok) throw new Error(await r.text()); return r.json(); } async function apiPost(path, payload) { const cfg = getConfig(); const r = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json", "X-Admin-Api-Key": cfg.apiKey, }, body: JSON.stringify(payload), }); if (!r.ok) throw new Error(await r.text()); return r.json(); } async function loadMeta() { const status = document.getElementById("metaStatus"); status.textContent = "backend: checking..."; try { const data = await apiGet("/meta", {}); status.textContent = `backend: ${data.version} @ ${new Date(data.started_at_utc).toLocaleTimeString()}`; } catch (e) { status.textContent = "backend: unreachable/old"; } } function renderRows(target, rows, formatter) { target.innerHTML = ""; if (!rows || rows.length === 0) { target.innerHTML = '
No rows.
'; return; } rows.forEach((row) => { const el = document.createElement("div"); el.className = "row"; el.innerHTML = formatter(row); target.appendChild(el); }); } async function loadInbox() { const cfg = getConfig(); const q = document.getElementById("inboxQuery").value.trim(); const out = document.getElementById("inboxResults"); out.innerHTML = '
Loading...
'; try { const data = await apiGet("/assistant/inbox", { release_name: cfg.releaseName, q, limit: 20 }); renderRows(out, data.rows || [], (r) => { const text = (r.text || r.summary || r.description || "").slice(0, 280); return `
${r.display_name || r.concept_id || "message"}
${text || "(no text)"}
${r.source_pk || ""} | ${r.release_name || ""}
`; }); } catch (e) { out.innerHTML = `
Error: ${String(e)}
`; } } async function loadTasks() { const cfg = getConfig(); const onlyPending = document.getElementById("onlyPending").checked; const out = document.getElementById("taskResults"); out.innerHTML = '
Loading...
'; try { const data = await apiGet("/assistant/tasks", { release_name: cfg.releaseName, only_pending: onlyPending, limit: 30, }); renderRows(out, data.rows || [], (r) => { const safeTodo = (r.todo || "").replace(/"/g, """); return `
${r.todo || "(empty task)"}
status=${r.status} | due=${r.due_hint || "-"} | who=${r.who || "-"}
source=${r.source_pk || ""} | release=${r.release_name || ""}
`; }); document.querySelectorAll(".use-goal").forEach((btn) => { btn.addEventListener("click", () => { const goal = btn.getAttribute("data-goal") || ""; document.getElementById("goalText").value = goal; }); }); } catch (e) { out.innerHTML = `
Error: ${String(e)}
`; } } async function makeDraft() { const cfg = getConfig(); const goal = document.getElementById("goalText").value.trim(); const recipient = document.getElementById("recipient").value.trim(); const out = document.getElementById("draftOutput"); if (!goal) { out.textContent = "Provide goal text first."; return; } out.textContent = "Generating..."; try { const data = await apiPost("/assistant/draft", { task_type: "message", goal, recipient: recipient || null, tone: "friendly-professional", constraints: ["keep it concise"], release_name: cfg.releaseName || null, max_sources: 5, }); const sourceLine = (data.sources || []).map((s) => s.concept_id).filter(Boolean).slice(0, 5).join(", "); out.textContent = `${data.draft || ""}\n\nconfidence=${data.confidence}\nneeds_review=${data.needs_review}\nsources=${sourceLine}`; } catch (e) { out.textContent = `Error: ${String(e)}`; } } async function saveLearn() { const cfg = getConfig(); const title = document.getElementById("learnTitle").value.trim(); const tags = document.getElementById("learnTags").value .split(",") .map((x) => x.trim()) .filter(Boolean); const text = document.getElementById("learnText").value.trim(); const out = document.getElementById("learnOutput"); if (!text) { out.textContent = "Provide note text first."; return; } out.textContent = "Saving..."; try { const data = await apiPost("/assistant/learn", { text, title: title || null, tags, release_name: cfg.releaseName || null, }); out.textContent = `saved=${data.stored}\nconcept_id=${data.concept_id}\ntitle=${data.title}`; document.getElementById("learnText").value = ""; } catch (e) { out.textContent = `Error: ${String(e)}`; } } function appendChat(role, text, meta) { const target = document.getElementById("chatTranscript"); const el = document.createElement("div"); el.className = "row"; el.innerHTML = `
${role}
${(text || "").replace(/\n/g, "
")}
${meta ? `
${meta}
` : ""} `; target.prepend(el); } async function sendChat() { const cfg = getConfig(); const sessionInput = document.getElementById("chatSessionId"); const session_id = (sessionInput.value || "main").trim(); sessionInput.value = session_id; const messageEl = document.getElementById("chatMessage"); const message = messageEl.value.trim(); if (!message) return; appendChat("user", message, `session=${session_id}`); messageEl.value = ""; try { const data = await apiPost("/assistant/chat", { session_id, message, release_name: cfg.releaseName || null, max_sources: 6, }); const sourceLine = (data.sources || []).map((s) => s.concept_id).filter(Boolean).slice(0, 4).join(", "); appendChat("assistant", data.answer || "", `confidence=${data.confidence} | sources=${sourceLine || "-"}`); } catch (e) { appendChat("assistant", `Error: ${String(e)}`, ""); } } async function runSelfImprove() { const cfg = getConfig(); const objective = document.getElementById("improveObjective").value.trim() || "Improve assistant quality and reliability"; const maxRaw = Number(document.getElementById("improveMax").value || 5); const maxProposals = Math.max(1, Math.min(20, Number.isFinite(maxRaw) ? maxRaw : 5)); const summary = document.getElementById("improveSummary"); const list = document.getElementById("improveProposals"); summary.textContent = "Generating proposals..."; list.innerHTML = ""; try { const data = await apiPost("/assistant/self-improve", { objective, release_name: cfg.releaseName || null, max_proposals: maxProposals, feedback_limit: 50, action_limit: 50, include_edited_feedback: true, include_rejected_feedback: true, include_blocked_actions: true, apply: false, }); window.selfImproveLast = data.proposals || []; summary.textContent = `${data.summary || ""}\n\nsignals: feedback=${data.signals?.feedback_rows ?? 0}, blocked_actions=${data.signals?.blocked_action_rows ?? 0}`; renderRows(list, data.proposals || [], (p, idx) => `
${p.proposal_id}: ${p.title}
${p.problem}
risk=${p.risk} | auto_apply_safe=${p.auto_apply_safe}
files=${(p.files || []).join(", ") || "-"}
tests=${(p.tests || []).join(" | ") || "-"}
${p.change || ""}
`); document.querySelectorAll(".apply-proposal").forEach((btn) => { btn.addEventListener("click", async () => { const proposalId = btn.getAttribute("data-proposal-id"); const found = (window.selfImproveLast || []).find((x) => x.proposal_id === proposalId); if (!found) return; const dryRun = document.getElementById("improveDryRun").checked; summary.textContent = `Applying ${proposalId}...`; try { const applied = await apiPost("/assistant/self-improve/apply", { objective, release_name: cfg.releaseName || null, proposal: found, dry_run: dryRun, }); summary.textContent = `apply result: applied=${applied.applied} dry_run=${applied.dry_run}\n` + `branch=${applied.branch_name}\n` + `proposal_file=${applied.proposal_file}\n` + `commit=${applied.commit || "-"}\n` + `${applied.detail || ""}`; } catch (e) { summary.textContent = `Apply error: ${String(e)}`; } }); }); } catch (e) { summary.textContent = `Error: ${String(e)}`; } } document.getElementById("saveConfig").addEventListener("click", saveConfig); document.getElementById("refreshMeta").addEventListener("click", loadMeta); document.getElementById("loadInbox").addEventListener("click", loadInbox); document.getElementById("loadTasks").addEventListener("click", loadTasks); document.getElementById("makeDraft").addEventListener("click", makeDraft); document.getElementById("saveLearn").addEventListener("click", saveLearn); document.getElementById("sendChat").addEventListener("click", sendChat); document.getElementById("runImprove").addEventListener("click", runSelfImprove); loadConfig(); loadMeta();