(payload, answerTarget, message)
| 4524 | } |
| 4525 | |
| 4526 | async function streamWikiAsk(payload, answerTarget, message) { |
| 4527 | const response = await fetch("/api/wiki/ask/stream", { |
| 4528 | method: "POST", |
| 4529 | headers: { "Content-Type": "application/json" }, |
| 4530 | body: JSON.stringify(payload) |
| 4531 | }); |
| 4532 | if (!response.ok || !response.body) throw new Error(ui().chat.streamUnavailable); |
| 4533 | |
| 4534 | const decoder = new TextDecoder(); |
| 4535 | const reader = response.body.getReader(); |
| 4536 | let buffer = ""; |
| 4537 | let text = ""; |
| 4538 | let result = {}; |
| 4539 | let citations = []; |
| 4540 | let doneReceived = false; |
| 4541 | |
| 4542 | const processFrame = (frame) => { |
| 4543 | if (!frame.trim()) return; |
| 4544 | const parsed = parseSseFrame(frame); |
| 4545 | if (!parsed) return; |
| 4546 | if (parsed.event === "error") throw new Error(parsed.data.error || ui().chat.streamFailed); |
| 4547 | if (parsed.event === "status" && !text) { |
| 4548 | answerTarget.innerHTML = `<p>${escapeHtml(parsed.data.text || ui().chat.generating)}</p>`; |
| 4549 | } |
| 4550 | if (parsed.event === "meta") { |
| 4551 | result = { ...result, ...parsed.data }; |
| 4552 | citations = normalizeCitations(parsed.data); |
| 4553 | const metaTarget = message.querySelector(".answer-meta"); |
| 4554 | if (metaTarget) metaTarget.outerHTML = renderAnswerMeta(parsed.data, citations); |
| 4555 | renderChatSources(citations); |
| 4556 | } |
| 4557 | if (parsed.event === "chunk") { |
| 4558 | text += parsed.data.text || ""; |
| 4559 | answerTarget.classList.remove("loading"); |
| 4560 | answerTarget.innerHTML = renderAnswerWithCitations(text, citations); |
| 4561 | $("chatThread").scrollTop = $("chatThread").scrollHeight; |
| 4562 | } |
| 4563 | if (parsed.event === "done") { |
| 4564 | doneReceived = true; |
| 4565 | result = { ...result, ...parsed.data }; |
| 4566 | text = parsed.data.text || text; |
| 4567 | citations = normalizeCitations(parsed.data); |
| 4568 | const metaTarget = message.querySelector(".answer-meta"); |
| 4569 | if (metaTarget) metaTarget.outerHTML = renderAnswerMeta(parsed.data, citations); |
| 4570 | answerTarget.classList.remove("loading"); |
| 4571 | answerTarget.innerHTML = renderAnswerWithCitations(text, citations); |
| 4572 | renderChatSources(citations); |
| 4573 | } |
| 4574 | }; |
| 4575 | |
| 4576 | while (true) { |
| 4577 | const { value, done } = await reader.read(); |
| 4578 | if (done) { |
| 4579 | buffer += decoder.decode(); |
| 4580 | break; |
| 4581 | } |
| 4582 | buffer += decoder.decode(value, { stream: true }); |
| 4583 | const frames = buffer.split(/\n\n/); |
no test coverage detected