mirror of
https://github.com/Jeuners/astra-vision.git
synced 2026-09-09 15:02:35 +02:00
feat: show RSS headlines as clickable links in the transcript
read_news bisher zeigte nur Klartext-Titel in der Karte an — man konnte im Chat selbst nicht auf den Artikel klicken (nur das Modell kannte den Link, für ein mögliches read_article danach). Jetzt trägt der tool_result-Event zusätzlich strukturierte 'items' (source, title, link), das Frontend rendert daraus eine Liste mit echten <a>-Links statt reinem Text. read_article bleibt unverändert bei reinem Text (items bleibt leer, kein Link zum Verlinken vorhanden). Verifiziert: die Rendering-Logik direkt per simuliertem Datenkanal- Event getestet (unabhängig von der Modell-Zuverlässigkeit, die bei read_news aktuell bei ~15-20% liegt) — zwei Links korrekt mit href, target=_blank, rel=noopener und Quelle gerendert. NICHT gepusht — weiterhin nur lokal, wie angewiesen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
This commit is contained in:
parent
0d2e826dd3
commit
b0282900d2
4 changed files with 46 additions and 9 deletions
|
|
@ -47,8 +47,15 @@ def build_tools(config: Settings, notify: Callable[[dict], None], media_store: d
|
||||||
notify({"type": "tool_error", "text": str(exc)})
|
notify({"type": "tool_error", "text": str(exc)})
|
||||||
await params.result_callback({"error": str(exc)})
|
await params.result_callback({"error": str(exc)})
|
||||||
return
|
return
|
||||||
headlines = "\n".join(f"- ({entry.source}) {entry.title}" for entry in entries)
|
notify(
|
||||||
notify({"type": "tool_result", "text": headlines})
|
{
|
||||||
|
"type": "tool_result",
|
||||||
|
"text": "\n".join(f"- ({entry.source}) {entry.title}" for entry in entries),
|
||||||
|
"items": [
|
||||||
|
{"source": e.source, "title": e.title, "link": e.link} for e in entries
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
await params.result_callback(
|
await params.result_callback(
|
||||||
{
|
{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,10 @@ async def test_read_news_tool_reports_headlines(monkeypatch):
|
||||||
assert [n["type"] for n in notifications] == ["activity", "tool_start", "tool_result"]
|
assert [n["type"] for n in notifications] == ["activity", "tool_start", "tool_result"]
|
||||||
assert "KI-Durchbruch" in notifications[2]["text"]
|
assert "KI-Durchbruch" in notifications[2]["text"]
|
||||||
assert "Neuer Chip" in notifications[2]["text"]
|
assert "Neuer Chip" in notifications[2]["text"]
|
||||||
|
assert notifications[2]["items"] == [
|
||||||
|
{"source": "heise online", "title": "KI-Durchbruch", "link": "https://x"},
|
||||||
|
{"source": "Golem.de", "title": "Neuer Chip", "link": "https://y"},
|
||||||
|
]
|
||||||
assert params.results[0]["status"] == "ok"
|
assert params.results[0]["status"] == "ok"
|
||||||
assert len(params.results[0]["headlines"]) == 2
|
assert len(params.results[0]["headlines"]) == 2
|
||||||
|
|
||||||
|
|
|
||||||
38
web/app.js
38
web/app.js
|
|
@ -62,7 +62,7 @@ function addToolError(text) {
|
||||||
}
|
}
|
||||||
showError(text);
|
showError(text);
|
||||||
}
|
}
|
||||||
function addToolResult(text) {
|
function addToolResult(text, items) {
|
||||||
const pending = document.getElementById("pending-tool");
|
const pending = document.getElementById("pending-tool");
|
||||||
const article = pending || document.createElement("article");
|
const article = pending || document.createElement("article");
|
||||||
if (pending) {
|
if (pending) {
|
||||||
|
|
@ -76,10 +76,36 @@ function addToolResult(text) {
|
||||||
const speaker = document.createElement("span");
|
const speaker = document.createElement("span");
|
||||||
speaker.className = "speaker";
|
speaker.className = "speaker";
|
||||||
speaker.textContent = "Astra";
|
speaker.textContent = "Astra";
|
||||||
const body = document.createElement("p");
|
article.append(speaker);
|
||||||
body.className = "tool-result";
|
if (items?.length) {
|
||||||
body.textContent = text;
|
const list = document.createElement("ul");
|
||||||
article.append(speaker, body);
|
list.className = "tool-result-list";
|
||||||
|
for (const item of items) {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
if (item.link) {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = item.link;
|
||||||
|
link.target = "_blank";
|
||||||
|
link.rel = "noopener";
|
||||||
|
link.textContent = item.title;
|
||||||
|
li.append(link);
|
||||||
|
} else {
|
||||||
|
li.textContent = item.title;
|
||||||
|
}
|
||||||
|
if (item.source) {
|
||||||
|
const source = document.createElement("small");
|
||||||
|
source.textContent = ` (${item.source})`;
|
||||||
|
li.append(source);
|
||||||
|
}
|
||||||
|
list.append(li);
|
||||||
|
}
|
||||||
|
article.append(list);
|
||||||
|
} else {
|
||||||
|
const body = document.createElement("p");
|
||||||
|
body.className = "tool-result";
|
||||||
|
body.textContent = text;
|
||||||
|
article.append(body);
|
||||||
|
}
|
||||||
if (!pending) $("messages").append(article);
|
if (!pending) $("messages").append(article);
|
||||||
$("messages").scrollTop = $("messages").scrollHeight;
|
$("messages").scrollTop = $("messages").scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +174,7 @@ function receive(event) {
|
||||||
if (message.type === "activity" && !muted) $("status").textContent = message.text;
|
if (message.type === "activity" && !muted) $("status").textContent = message.text;
|
||||||
if (message.type === "tool_start") addToolStart(message.text);
|
if (message.type === "tool_start") addToolStart(message.text);
|
||||||
if (message.type === "tool_error") addToolError(message.text);
|
if (message.type === "tool_error") addToolError(message.text);
|
||||||
if (message.type === "tool_result") addToolResult(message.text);
|
if (message.type === "tool_result") addToolResult(message.text, message.items);
|
||||||
if (message.type === "partial") $("partial").textContent = message.text;
|
if (message.type === "partial") $("partial").textContent = message.text;
|
||||||
if (message.type === "transcript") addMessage(message);
|
if (message.type === "transcript") addMessage(message);
|
||||||
if (message.type === "image") addImage(message.url);
|
if (message.type === "image") addImage(message.url);
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue