/** * @file Ersetzt eine Antwort während der Neugenerierung in ihrer Nachrichtenkarte und stellt sie bei Fehlern wieder her. */ import {stream} from "./api.js"; import {updateReasoning, finishReasoning} from "./reasoning.js"; import {appendSources} from "./webSearch.js"; // Keep the original nodes (including action handlers) until a committed answer arrives. export async function regenerateInline({card, scroller, endpoint, payload, renderContent, complete}) { const original = [...card.childNodes]; const status = document.createElement("div"); status.className = "loading-shell"; status.setAttribute("role", "status"); status.textContent = "Generating..."; const body = document.createElement("div"); body.className = "msg-content streaming-content"; let committed = false; const update = operation => { const bottom = card.getBoundingClientRect().bottom; const viewport = scroller.getBoundingClientRect(); const follow = bottom >= viewport.top && Math.abs(bottom - viewport.bottom) < 100; operation(); if (follow) scroller.scrollTop += card.getBoundingClientRect().bottom - bottom; }; // Anchor to this response, including when a long old answer collapses. update(() => card.replaceChildren(original[0].cloneNode(true), status, body)); card.setAttribute("aria-busy", "true"); try { await stream(endpoint, payload, { onStatus: data => update(() => { status.textContent = data.status === "searching" ? "Searching the web..." : data.status === "analyzing" ? "Analyzing attachments..." : "Generating..."; if ("sources" in data) { card.querySelector(".msg-sources")?.remove(); appendSources(card, data.sources); } }), onThinking: ({fullThinking}) => update(() => updateReasoning(card, fullThinking, true)), onChunk: ({fullReply}) => update(() => { status.hidden = Boolean(fullReply); if (fullReply) finishReasoning(card); renderContent(body, fullReply); }), onError: ({error}) => { throw new Error(error || "Unable to regenerate message."); }, onDone: ({message}) => { if (!message) throw new Error("Missing regenerated response."); update(() => complete(message)); committed = true; } }); } finally { // Bei Fehler oder Abbruch die ursprünglichen Knoten samt Ereignishandlern wieder einsetzen. if (!committed) update(() => card.replaceChildren(...original)); card.removeAttribute("aria-busy"); } }