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\n` +
`confidence=${data.confidence}\n` +
`needs_review=${data.needs_review}\n` +
`provider=${data.provider_used || "local"}\n` +
`escalated=${Boolean(data.escalated)}\n` +
`sources=${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)}`;
}
}
async function saveLearnAndWriteCode() {
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 codeObjective = document.getElementById("learnCodeObjective").value.trim();
const codeFiles = document.getElementById("learnCodeFiles").value
.split(",")
.map((x) => x.trim())
.filter(Boolean);
const codeDryRun = document.getElementById("learnCodeDryRun").checked;
const codeCommit = document.getElementById("learnCodeCommit").checked;
const out = document.getElementById("learnOutput");
if (!text) {
out.textContent = "Provide note text first.";
return;
}
if (codeFiles.length === 0) {
out.textContent = "Provide at least one code file path.";
return;
}
out.textContent = "Saving note and writing code...";
try {
const data = await apiPost("/assistant/learn", {
text,
title: title || null,
tags,
release_name: cfg.releaseName || null,
write_code: true,
code_objective: codeObjective || null,
code_files: codeFiles,
code_dry_run: codeDryRun,
code_commit: codeCommit,
});
const cw = data.code_write || {};
out.textContent =
`saved=${data.stored}\nconcept_id=${data.concept_id}\ntitle=${data.title}\n\n` +
`write_code.attempted=${cw.attempted}\n` +
`dry_run=${cw.dry_run}\n` +
`branch=${cw.branch_name || "-"}\n` +
`changed_files=${(cw.changed_files || []).join(", ") || "-"}\n` +
`skipped_files=${(cw.skipped_files || []).map((x) => `${x.file}:${x.reason}`).join(" | ") || "-"}\n` +
`commit=${cw.commit || "-"}`;
} 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} | provider=${data.provider_used || "local"} | escalated=${Boolean(data.escalated)} | 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,
});
summary.textContent =
`${data.summary || ""}\n\nproposal_set_id=${data.proposal_set_id}\n` +
`signals: feedback=${data.signals?.feedback_rows ?? 0}, blocked_actions=${data.signals?.blocked_action_rows ?? 0}`;
renderProposalSets(
[{ proposal_set_id: data.proposal_set_id, created_at_utc: data.created_at_utc, objective, release_name: cfg.releaseName || "", summary: data.summary, signals: data.signals || {}, proposals: data.proposals || [] }],
list,
summary,
cfg.releaseName || null
);
} catch (e) {
summary.textContent = `Error: ${String(e)}`;
}
}
function renderProposalSets(sets, listEl, summaryEl, releaseName) {
listEl.innerHTML = "";
if (!sets || sets.length === 0) {
listEl.innerHTML = 'No proposal sets.
';
return;
}
sets.forEach((setObj) => {
const wrap = document.createElement("div");
wrap.className = "row";
const proposals = setObj.proposals || [];
const proposalsHtml = proposals.map((p) => `
${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 || ""}
`).join("");
wrap.innerHTML = `
Set ${setObj.proposal_set_id}
${setObj.created_at_utc || ""} | release=${setObj.release_name || ""}
${setObj.summary || ""}
${proposalsHtml}
`;
listEl.appendChild(wrap);
});
document.querySelectorAll(".apply-proposal").forEach((btn) => {
btn.addEventListener("click", async () => {
const proposalSetId = btn.getAttribute("data-proposal-set-id");
const proposalId = btn.getAttribute("data-proposal-id");
if (!proposalSetId || !proposalId) return;
const dryRun = document.getElementById("improveDryRun").checked;
summaryEl.textContent = `Applying ${proposalId} from set ${proposalSetId}...`;
try {
const applied = await apiPost("/assistant/self-improve/apply", {
objective: document.getElementById("improveObjective").value.trim() || null,
release_name: releaseName || null,
proposal_set_id: proposalSetId,
proposal_id: proposalId,
dry_run: dryRun,
});
summaryEl.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) {
summaryEl.textContent = `Apply error: ${String(e)}`;
}
});
});
}
async function loadImproveHistory() {
const cfg = getConfig();
const summary = document.getElementById("improveSummary");
const list = document.getElementById("improveProposals");
summary.textContent = "Loading proposal history...";
try {
const data = await apiGet("/assistant/self-improve/history", {
release_name: cfg.releaseName || null,
limit: 200,
});
summary.textContent = `history sets=${data.count || 0}`;
renderProposalSets(data.rows || [], list, summary, cfg.releaseName || null);
} 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("saveLearnWrite").addEventListener("click", saveLearnAndWriteCode);
document.getElementById("sendChat").addEventListener("click", sendChat);
document.getElementById("runImprove").addEventListener("click", runSelfImprove);
document.getElementById("loadImproveHistory").addEventListener("click", loadImproveHistory);
loadConfig();
loadMeta();
loadImproveHistory();