fix: fix chat webui response parsing (#2515)

fix: fix chat webui

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
This commit is contained in:
Sertaç Özercan 2024-06-07 18:20:31 +03:00 committed by GitHub
parent d38e9090df
commit 0d62594099
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 39 deletions

View file

@ -38,7 +38,7 @@ function submitSystemPrompt(event) {
localStorage.setItem("system_prompt", document.getElementById("systemPrompt").value); localStorage.setItem("system_prompt", document.getElementById("systemPrompt").value);
document.getElementById("systemPrompt").blur(); document.getElementById("systemPrompt").blur();
} }
var image = ""; var image = "";
function submitPrompt(event) { function submitPrompt(event) {
@ -54,15 +54,15 @@ function submitPrompt(event) {
} }
function readInputImage() { function readInputImage() {
if (!this.files || !this.files[0]) return; if (!this.files || !this.files[0]) return;
const FR = new FileReader(); const FR = new FileReader();
FR.addEventListener("load", function(evt) { FR.addEventListener("load", function(evt) {
image = evt.target.result; image = evt.target.result;
}); });
FR.readAsDataURL(this.files[0]); FR.readAsDataURL(this.files[0]);
} }
@ -155,7 +155,7 @@ function readInputImage() {
stream: true, stream: true,
}), }),
}); });
if (!response.ok) { if (!response.ok) {
Alpine.store("chat").add( Alpine.store("chat").add(
"assistant", "assistant",
@ -163,11 +163,11 @@ function readInputImage() {
); );
return; return;
} }
const reader = response.body const reader = response.body
?.pipeThrough(new TextDecoderStream()) ?.pipeThrough(new TextDecoderStream())
.getReader(); .getReader();
if (!reader) { if (!reader) {
Alpine.store("chat").add( Alpine.store("chat").add(
"assistant", "assistant",
@ -175,30 +175,74 @@ function readInputImage() {
); );
return; return;
} }
while (true) { // Function to add content to the chat and handle DOM updates efficiently
const { value, done } = await reader.read(); const addToChat = (token) => {
if (done) break; const chatStore = Alpine.store("chat");
let dataDone = false; chatStore.add("assistant", token);
const arr = value.split("\n"); // Efficiently scroll into view without triggering multiple reflows
arr.forEach((data) => { const messages = document.getElementById('messages');
if (data.length === 0) return; messages.scrollTop = messages.scrollHeight;
if (data.startsWith(":")) return; };
if (data === "data: [DONE]") {
dataDone = true; let buffer = "";
return; let contentBuffer = [];
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;
let lines = buffer.split("\n");
buffer = lines.pop(); // Retain any incomplete line in the buffer
lines.forEach((line) => {
if (line.length === 0 || line.startsWith(":")) return;
if (line === "data: [DONE]") {
return;
}
if (line.startsWith("data: ")) {
try {
const jsonData = JSON.parse(line.substring(6));
const token = jsonData.choices[0].delta.content;
if (token) {
contentBuffer.push(token);
}
} catch (error) {
console.error("Failed to parse line:", line, error);
}
}
});
// Efficiently update the chat in batch
if (contentBuffer.length > 0) {
addToChat(contentBuffer.join(""));
contentBuffer = [];
} }
const token = JSON.parse(data.substring(6)).choices[0].delta.content; }
if (!token) {
return; // Final content flush if any data remains
} if (contentBuffer.length > 0) {
hljs.highlightAll(); addToChat(contentBuffer.join(""));
Alpine.store("chat").add("assistant", token); }
document.getElementById('messages').scrollIntoView(false)
}); // Highlight all code blocks once at the end
hljs.highlightAll(); hljs.highlightAll();
if (dataDone) break; } catch (error) {
console.error("An error occurred while reading the stream:", error);
Alpine.store("chat").add(
"assistant",
`<span class='error'>Error: Failed to process stream</span>`,
);
} finally {
// Perform any cleanup if necessary
reader.releaseLock();
} }
// Remove class "loader" from the element with "loader" id // Remove class "loader" from the element with "loader" id
//document.getElementById("loader").classList.remove("loader"); //document.getElementById("loader").classList.remove("loader");
document.getElementById("loader").style.display = "none"; document.getElementById("loader").style.display = "none";
@ -209,7 +253,7 @@ function readInputImage() {
// set focus to the input // set focus to the input
document.getElementById("input").focus(); document.getElementById("input").focus();
} }
document.getElementById("key").addEventListener("submit", submitKey); document.getElementById("key").addEventListener("submit", submitKey);
document.getElementById("system_prompt").addEventListener("submit", submitSystemPrompt); document.getElementById("system_prompt").addEventListener("submit", submitSystemPrompt);
@ -230,7 +274,7 @@ function readInputImage() {
} else { } else {
document.getElementById("systemPrompt").value = null; document.getElementById("systemPrompt").value = null;
} }
marked.setOptions({ marked.setOptions({
highlight: function (code) { highlight: function (code) {
return hljs.highlightAuto(code).value; return hljs.highlightAuto(code).value;

View file

@ -4,22 +4,22 @@
<body class="bg-gray-900 text-gray-200"> <body class="bg-gray-900 text-gray-200">
<div class="flex flex-col min-h-screen"> <div class="flex flex-col min-h-screen">
{{template "views/partials/navbar" .}} {{template "views/partials/navbar" .}}
<div class="container mx-auto px-4 flex-grow"> <div class="container mx-auto px-4 flex-grow">
<div class="header text-center py-12"> <div class="header text-center py-12">
<h1 class="text-5xl font-bold text-gray-100">Welcome to <i>your</i> LocalAI instance!</h1> <h1 class="text-5xl font-bold text-gray-100">Welcome to <i>your</i> LocalAI instance!</h1>
<p class="mt-4 text-lg">The FOSS alternative to OpenAI, Claude, ...</p> <p class="mt-4 text-lg">The FOSS alternative to OpenAI, Claude, ...</p>
<a href="https://localai.io" target="_blank" class="mt-4 inline-block bg-blue-500 text-white py-2 px-4 rounded-lg shadow transition duration-300 ease-in-out hover:bg-blue-700 hover:shadow-lg"> <a href="https://localai.io" target="_blank" class="mt-4 inline-block bg-blue-500 text-white py-2 px-4 rounded-lg shadow transition duration-300 ease-in-out hover:bg-blue-700 hover:shadow-lg">
<i class="fas fa-book-reader pr-2"></i>Documentation <i class="fas fa-book-reader pr-2"></i>Documentation
</a> </a>
</div> </div>
<div class="models mt-4"> <div class="models mt-4">
{{template "views/partials/inprogress" .}} {{template "views/partials/inprogress" .}}
{{ if eq (len .ModelsConfig) 0 }} {{ if eq (len .ModelsConfig) 0 }}
<h2 class="text-center text-3xl font-semibold text-gray-100"> <i class="text-yellow-200 ml-2 fa-solid fa-triangle-exclamation animate-pulse"></i> Ouch! seems you don't have any models installed!</h2> <h2 class="text-center text-3xl font-semibold text-gray-100"> <i class="text-yellow-200 ml-2 fa-solid fa-triangle-exclamation animate-pulse"></i> Ouch! seems you don't have any models installed!</h2>
<p class="text-center mt-4 text-xl">..install something from the <a class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded" href="/browse">🖼️ Gallery</a> or check the <a href="https://localai.io/basics/getting_started/" class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded"> <i class="fa-solid fa-book"></i> Getting started documentation </a></p> <p class="text-center mt-4 text-xl">..install something from the <a class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded" href="/browse">🖼️ Gallery</a> or check the <a href="https://localai.io/basics/getting_started/" class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded"> <i class="fa-solid fa-book"></i> Getting started documentation </a></p>
@ -70,9 +70,9 @@
{{ end }} {{ end }}
</td> </td>
<td class="px-4 py-3"> <td class="px-4 py-3">
<button <button
class="float-right inline-block rounded bg-red-800 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-red-accent-300 hover:shadow-red-2 focus:bg-red-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-red-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong" class="float-right inline-block rounded bg-red-800 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-red-accent-300 hover:shadow-red-2 focus:bg-red-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-red-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong"
data-twe-ripple-color="light" data-twe-ripple-init="" hx-confirm="Are you sure you wish to delete the model?" hx-post="/browse/delete/model/{{.Name}}" hx-swap="outerHTML"><i class="fa-solid fa-cancel pr-2"></i>Delete</button> data-twe-ripple-color="light" data-twe-ripple-init="" hx-confirm="Are you sure you wish to delete the model?" hx-post="/browse/delete/model/{{.Name}}" hx-swap="outerHTML"><i class="fa-solid fa-cancel pr-2"></i>Delete</button>
</td> </td>
{{ end }} {{ end }}