229 lines
7.7 KiB
JavaScript
229 lines
7.7 KiB
JavaScript
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 = '<div class="row">No rows.</div>';
|
|
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 = '<div class="row">Loading...</div>';
|
|
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 `
|
|
<div><strong>${r.display_name || r.concept_id || "message"}</strong></div>
|
|
<div>${text || "(no text)"}</div>
|
|
<div class="meta">${r.source_pk || ""} | ${r.release_name || ""}</div>
|
|
`;
|
|
});
|
|
} catch (e) {
|
|
out.innerHTML = `<div class="row">Error: ${String(e)}</div>`;
|
|
}
|
|
}
|
|
|
|
async function loadTasks() {
|
|
const cfg = getConfig();
|
|
const onlyPending = document.getElementById("onlyPending").checked;
|
|
const out = document.getElementById("taskResults");
|
|
out.innerHTML = '<div class="row">Loading...</div>';
|
|
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 `
|
|
<div><strong>${r.todo || "(empty task)"}</strong></div>
|
|
<div class="meta">status=${r.status} | due=${r.due_hint || "-"} | who=${r.who || "-"}</div>
|
|
<div class="meta">source=${r.source_pk || ""} | release=${r.release_name || ""}</div>
|
|
<div style="margin-top:6px"><button data-goal="${safeTodo}" class="use-goal">Use as goal</button></div>
|
|
`;
|
|
});
|
|
document.querySelectorAll(".use-goal").forEach((btn) => {
|
|
btn.addEventListener("click", () => {
|
|
const goal = btn.getAttribute("data-goal") || "";
|
|
document.getElementById("goalText").value = goal;
|
|
});
|
|
});
|
|
} catch (e) {
|
|
out.innerHTML = `<div class="row">Error: ${String(e)}</div>`;
|
|
}
|
|
}
|
|
|
|
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 = `
|
|
<div><strong>${role}</strong></div>
|
|
<div>${(text || "").replace(/\n/g, "<br/>")}</div>
|
|
${meta ? `<div class="meta">${meta}</div>` : ""}
|
|
`;
|
|
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)}`, "");
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
loadConfig();
|
|
loadMeta();
|