feat(citations): Web-Such-Quellen unter der Antwort-Bubble anzeigen
Sonar liefert Citation-URLs; bisher verworfen, jetzt bis in die UI gereicht. - ToolCallingLLM: _run_tool/_inject_forced_search geben Citations zurueck; stream() sammelt sie ueber einen on_citations-Callback. - Orchestrator: chat_stream reicht on_citations durch (via _stream_supports) und legt die gesammelten URLs in PipelineTrace.citations. - schemas: PipelineTrace.citations. - ws.py: Citations im semantic-Event. - Frontend (app.js): renderCitations() zeigt bis zu 4 Quellen-Links (Hostname, neuer Tab) unter der Antwort-Bubble. Nur ueber den Streaming-Pfad (= Web-UI). Live verifiziert: "Wer ist Bundeskanzler?" -> 15 Quellen gesammelt (Bundeskanzler.de, Wikipedia, ...). Tests: 312 gruen; JS-Syntax geprueft. Doc §8. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fdf65e8623
commit
e753788a23
6 changed files with 65 additions and 15 deletions
|
|
@ -513,6 +513,32 @@ function addMessage(role, text, meta = {}) {
|
|||
return div;
|
||||
}
|
||||
|
||||
// Quellen-Links der Web-Suche unter die Antwort-Bubble (max. 4, idempotent).
|
||||
function renderCitations(bubble, urls) {
|
||||
if (!bubble || !Array.isArray(urls) || !urls.length) return;
|
||||
let box = bubble._citationsEl;
|
||||
if (!box) {
|
||||
box = document.createElement("div");
|
||||
box.className = "citations mt-1 text-xs text-slate-500 dark:text-slate-400 "
|
||||
+ "flex flex-wrap gap-x-2 gap-y-0.5";
|
||||
bubble._citationsEl = box;
|
||||
bubble.appendChild(box);
|
||||
}
|
||||
box.innerHTML = "";
|
||||
const label = document.createElement("span");
|
||||
label.textContent = "Quellen:";
|
||||
box.appendChild(label);
|
||||
urls.slice(0, 4).forEach((u) => {
|
||||
let host;
|
||||
try { host = new URL(u).hostname.replace(/^www\./, ""); } catch { host = u; }
|
||||
const a = document.createElement("a");
|
||||
a.href = u; a.target = "_blank"; a.rel = "noopener noreferrer";
|
||||
a.textContent = host;
|
||||
a.className = "underline hover:text-slate-700 dark:hover:text-white";
|
||||
box.appendChild(a);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Vorlesen / Replay ----------
|
||||
function setReplayPlaying(btn, on) {
|
||||
if (!btn) return;
|
||||
|
|
@ -704,6 +730,7 @@ function runTurn(path, onopen) {
|
|||
case "semantic":
|
||||
if (!answerEl) answerEl = addMessage("assistant", "", { lang: turnLang() });
|
||||
if (msg.text) answerEl._textEl.textContent = msg.text;
|
||||
if (msg.citations) renderCitations(answerEl, msg.citations);
|
||||
// Sprache final festhalten (für Replay) und im Geräte-Modus lokal vorlesen.
|
||||
// Für TTS das bereinigte "spoken" (ohne Markdown/Emojis) bevorzugen, anzeigen
|
||||
// bleibt der Originaltext.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue