jecio/ui/assets/app.js

216 lines
7.3 KiB
JavaScript
Raw Normal View History

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();
}
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, "&quot;");
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("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();