feat(agent): add index bootstrap and answer feedback

This commit is contained in:
2026-09-16 22:40:06 +02:00
parent fc656188cf
commit c005b9b306
19 changed files with 757 additions and 41 deletions
+64
View File
@@ -137,6 +137,67 @@ function followUpPanel(question) {
return box;
}
function ratingPanel(answerRequestId) {
const panel = node("div", "rating-panel");
panel.append(node("strong", "", "War diese Antwort hilfreich?"));
const actions = node("div", "rating-actions");
const up = node("button", "secondary rating-choice", "👍 Ja");
const down = node("button", "secondary rating-choice", "👎 Nein");
up.type = "button";
down.type = "button";
actions.append(up, down);
panel.append(actions);
const form = node("div", "rating-form");
form.hidden = true;
const feedback = node("textarea", "rating-feedback");
feedback.rows = 2;
feedback.maxLength = 1000;
feedback.placeholder = "Optional: Was war hilfreich oder was hat gefehlt?";
const send = node("button", "primary", "Bewertung senden");
send.type = "button";
const status = node("span", "rating-status");
form.append(feedback, send, status);
panel.append(form);
let selection = null;
const select = (value, selected, other) => {
selection = value;
selected.classList.add("is-selected");
other.classList.remove("is-selected");
form.hidden = false;
feedback.focus();
};
up.addEventListener("click", () => select("up", up, down));
down.addEventListener("click", () => select("down", down, up));
send.addEventListener("click", async () => {
if (!selection) return;
send.disabled = true;
status.textContent = "Wird gespeichert …";
try {
const response = await fetch("/v1/ratings", {
method: "POST",
headers: apiHeaders(),
body: JSON.stringify({
request_id: answerRequestId,
rating: selection,
feedback: feedback.value.trim() || null,
}),
});
if (!response.ok) throw new Error(await errorDetail(response));
status.textContent = "Danke, Bewertung gespeichert.";
up.disabled = true;
down.disabled = true;
feedback.disabled = true;
send.hidden = true;
} catch (error) {
status.textContent = error.message || "Bewertung konnte nicht gespeichert werden.";
send.disabled = false;
}
});
return panel;
}
function technicalPanel(data) {
const details = node("details", "technical-panel");
details.append(node("summary", "", "Technische Details"));
@@ -182,6 +243,9 @@ function addAgentMessage(data) {
if (Array.isArray(data.sources) && data.sources.length) {
body.append(sourcePanel(data.sources));
}
if (data.ratings_enabled && data.request_id) {
body.append(ratingPanel(data.request_id));
}
const seconds = typeof data.latency_ms === "number" ? `${(data.latency_ms / 1000).toFixed(1)} s` : "";
body.append(node("div", "answer-meta", `${data.model || "Modell"} · ${seconds} · ${data.citations?.length || 0} Zitate`));
+1 -1
View File
@@ -59,7 +59,7 @@
<div class="privacy-note">
<strong>Datenschutzgrenze</strong>
<span>Keine Namen, Personalnummern oder Lohndaten eingeben. Diese Testversion verarbeitet nur Fachfragen.</span>
<span>Keine Namen, Personalnummern oder Lohndaten eingeben. Fragen, Antworten und Bewertungen werden für die Qualitätsprüfung protokolliert.</span>
</div>
</div>
</aside>
+8
View File
@@ -122,6 +122,13 @@ details summary { color: var(--green-dark); font-size: 0.83rem; font-weight: 700
.follow-up { margin-top: 14px; padding: 12px; border-radius: 9px; background: var(--green-soft); }
.follow-up p { margin: 0 0 8px; font-size: 0.84rem; }
.query-list { margin: 8px 0 0; padding-left: 20px; color: var(--muted); font-size: 0.76rem; line-height: 1.5; }
.rating-panel { display: grid; gap: 9px; margin-top: 16px; padding: 13px; border: 1px solid var(--line); border-radius: 10px; background: #fafbf8; }
.rating-panel > strong { font-size: 0.8rem; }
.rating-actions { display: flex; gap: 8px; }
.rating-choice.is-selected { border-color: var(--green); color: var(--green-dark); background: var(--green-soft); }
.rating-form { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: end; }
.rating-feedback { width: 100%; min-height: 62px; resize: vertical; padding: 9px; border: 1px solid var(--line); border-radius: 8px; color: var(--ink); background: white; }
.rating-status { grid-column: 1 / -1; color: var(--muted); font-size: 0.72rem; }
.loading-row { display: flex; align-items: center; gap: 10px; color: var(--muted); }
.loader { width: 18px; height: 18px; border: 2px solid #cbd3cd; border-top-color: var(--green); border-radius: 50%; animation: spin 0.8s linear infinite; }
@@ -162,6 +169,7 @@ details summary { color: var(--green-dark); font-size: 0.83rem; font-weight: 700
.messages { padding: 22px 14px 6px; }
.message-user { width: 92%; }
.composer { margin: 18px 10px 0; }
.rating-form { grid-template-columns: 1fr; }
.footnote { margin-inline: 14px; }
}