Fix rendering image on chat response in web, desktop client

This commit is contained in:
Debanjum Singh Solanky
2023-12-05 01:03:52 -05:00
parent d124266923
commit 8f2f053968
2 changed files with 211 additions and 174 deletions

View File

@@ -345,114 +345,142 @@
let chatInput = document.getElementById("chat-input"); let chatInput = document.getElementById("chat-input");
chatInput.classList.remove("option-enabled"); chatInput.classList.remove("option-enabled");
// Call specified Khoj API which returns a streamed response of type text/plain // Call specified Khoj API
let response = await fetch(url, { headers }); let response = await fetch(url, { headers });
const reader = response.body.getReader();
const decoder = new TextDecoder();
let rawResponse = ""; let rawResponse = "";
let references = null; const contentType = response.headers.get("content-type");
function readStream() { if (contentType === "application/json") {
reader.read().then(({ done, value }) => { // Handle JSON response
if (done) { try {
// Append any references after all the data has been streamed const responseAsJson = await response.json();
if (references != null) { if (responseAsJson.image) {
newResponseText.appendChild(references); // If response has image field, response is a generated image.
} rawResponse += `![${query}](data:image/png;base64,${responseAsJson.image})`;
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
document.getElementById("chat-input").removeAttribute("disabled");
return;
} }
if (responseAsJson.detail) {
// If response has detail field, response is an error message.
rawResponse += responseAsJson.detail;
}
} catch (error) {
// If the chunk is not a JSON object, just display it as is
rawResponse += chunk;
} finally {
newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse));
// Decode message chunk from stream document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
const chunk = decoder.decode(value, { stream: true }); document.getElementById("chat-input").removeAttribute("disabled");
}
} else {
// Handle streamed response of type text/event-stream or text/plain
const reader = response.body.getReader();
const decoder = new TextDecoder();
let references = null;
if (chunk.includes("### compiled references:")) { readStream();
const additionalResponse = chunk.split("### compiled references:")[0];
rawResponse += additionalResponse;
newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse));
const rawReference = chunk.split("### compiled references:")[1]; function readStream() {
const rawReferenceAsJson = JSON.parse(rawReference); reader.read().then(({ done, value }) => {
references = document.createElement('div'); if (done) {
references.classList.add("references"); // Append any references after all the data has been streamed
if (references != null) {
let referenceExpandButton = document.createElement('button'); newResponseText.appendChild(references);
referenceExpandButton.classList.add("reference-expand-button"); }
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
let referenceSection = document.createElement('div'); document.getElementById("chat-input").removeAttribute("disabled");
referenceSection.classList.add("reference-section"); return;
referenceSection.classList.add("collapsed");
let numReferences = 0;
// If rawReferenceAsJson is a list, then count the length
if (Array.isArray(rawReferenceAsJson)) {
numReferences = rawReferenceAsJson.length;
rawReferenceAsJson.forEach((reference, index) => {
let polishedReference = generateReference(reference, index);
referenceSection.appendChild(polishedReference);
});
} else {
numReferences += processOnlineReferences(referenceSection, rawReferenceAsJson);
} }
references.appendChild(referenceExpandButton); // Decode message chunk from stream
const chunk = decoder.decode(value, { stream: true });
referenceExpandButton.addEventListener('click', function() { if (chunk.includes("### compiled references:")) {
if (referenceSection.classList.contains("collapsed")) { const additionalResponse = chunk.split("### compiled references:")[0];
referenceSection.classList.remove("collapsed"); rawResponse += additionalResponse;
referenceSection.classList.add("expanded");
} else {
referenceSection.classList.add("collapsed");
referenceSection.classList.remove("expanded");
}
});
let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`;
referenceExpandButton.innerHTML = expandButtonText;
references.appendChild(referenceSection);
readStream();
} else {
// Display response from Khoj
if (newResponseText.getElementsByClassName("spinner").length > 0) {
newResponseText.removeChild(loadingSpinner);
}
// Try to parse the chunk as a JSON object. It will be a JSON object if there is an error.
if (chunk.startsWith("{") && chunk.endsWith("}")) {
try {
const responseAsJson = JSON.parse(chunk);
if (responseAsJson.image) {
rawResponse += `![${query}](data:image/png;base64,${responseAsJson.image})`;
}
if (responseAsJson.detail) {
rawResponse += responseAsJson.detail;
}
} catch (error) {
// If the chunk is not a JSON object, just display it as is
rawResponse += chunk;
} finally {
newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse));
}
} else {
// If the chunk is not a JSON object, just display it as is
rawResponse += chunk;
newResponseText.innerHTML = ""; newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse)); newResponseText.appendChild(formatHTMLMessage(rawResponse));
readStream(); const rawReference = chunk.split("### compiled references:")[1];
} const rawReferenceAsJson = JSON.parse(rawReference);
} references = document.createElement('div');
references.classList.add("references");
// Scroll to bottom of chat window as chat response is streamed let referenceExpandButton = document.createElement('button');
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight; referenceExpandButton.classList.add("reference-expand-button");
});
let referenceSection = document.createElement('div');
referenceSection.classList.add("reference-section");
referenceSection.classList.add("collapsed");
let numReferences = 0;
// If rawReferenceAsJson is a list, then count the length
if (Array.isArray(rawReferenceAsJson)) {
numReferences = rawReferenceAsJson.length;
rawReferenceAsJson.forEach((reference, index) => {
let polishedReference = generateReference(reference, index);
referenceSection.appendChild(polishedReference);
});
} else {
numReferences += processOnlineReferences(referenceSection, rawReferenceAsJson);
}
references.appendChild(referenceExpandButton);
referenceExpandButton.addEventListener('click', function() {
if (referenceSection.classList.contains("collapsed")) {
referenceSection.classList.remove("collapsed");
referenceSection.classList.add("expanded");
} else {
referenceSection.classList.add("collapsed");
referenceSection.classList.remove("expanded");
}
});
let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`;
referenceExpandButton.innerHTML = expandButtonText;
references.appendChild(referenceSection);
readStream();
} else {
// Display response from Khoj
if (newResponseText.getElementsByClassName("spinner").length > 0) {
newResponseText.removeChild(loadingSpinner);
}
// Try to parse the chunk as a JSON object. It will be a JSON object if there is an error.
if (chunk.startsWith("{") && chunk.endsWith("}")) {
try {
const responseAsJson = JSON.parse(chunk);
if (responseAsJson.image) {
rawResponse += `![${query}](data:image/png;base64,${responseAsJson.image})`;
}
if (responseAsJson.detail) {
rawResponse += responseAsJson.detail;
}
} catch (error) {
// If the chunk is not a JSON object, just display it as is
rawResponse += chunk;
} finally {
newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse));
}
} else {
// If the chunk is not a JSON object, just display it as is
rawResponse += chunk;
newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse));
readStream();
}
}
// Scroll to bottom of chat window as chat response is streamed
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
});
}
} }
readStream();
} }
function incrementalChat(event) { function incrementalChat(event) {

View File

@@ -350,113 +350,122 @@ To get started, just start typing below. You can also type / to see a list of co
let chatInput = document.getElementById("chat-input"); let chatInput = document.getElementById("chat-input");
chatInput.classList.remove("option-enabled"); chatInput.classList.remove("option-enabled");
// Call specified Khoj API which returns a streamed response of type text/plain // Call specified Khoj API
let response = await fetch(url); let response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let rawResponse = ""; let rawResponse = "";
let references = null; const contentType = response.headers.get("content-type");
function readStream() { if (contentType === "application/json") {
reader.read().then(({ done, value }) => { // Handle JSON response
if (done) { try {
// Append any references after all the data has been streamed const responseAsJson = await response.json();
if (references != null) { if (responseAsJson.image) {
newResponseText.appendChild(references); // If response has image field, response is a generated image.
} rawResponse += `![${query}](data:image/png;base64,${responseAsJson.image})`;
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
document.getElementById("chat-input").removeAttribute("disabled");
return;
} }
if (responseAsJson.detail) {
// If response has detail field, response is an error message.
rawResponse += responseAsJson.detail;
}
} catch (error) {
// If the chunk is not a JSON object, just display it as is
rawResponse += chunk;
} finally {
newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse));
// Decode message chunk from stream document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
const chunk = decoder.decode(value, { stream: true }); document.getElementById("chat-input").removeAttribute("disabled");
}
} else {
// Handle streamed response of type text/event-stream or text/plain
const reader = response.body.getReader();
const decoder = new TextDecoder();
let references = null;
if (chunk.includes("### compiled references:")) { readStream();
const additionalResponse = chunk.split("### compiled references:")[0];
rawResponse += additionalResponse;
newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse));
const rawReference = chunk.split("### compiled references:")[1]; function readStream() {
const rawReferenceAsJson = JSON.parse(rawReference); reader.read().then(({ done, value }) => {
references = document.createElement('div'); if (done) {
references.classList.add("references"); // Append any references after all the data has been streamed
if (references != null) {
let referenceExpandButton = document.createElement('button'); newResponseText.appendChild(references);
referenceExpandButton.classList.add("reference-expand-button"); }
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
let referenceSection = document.createElement('div'); document.getElementById("chat-input").removeAttribute("disabled");
referenceSection.classList.add("reference-section"); return;
referenceSection.classList.add("collapsed");
let numReferences = 0;
// If rawReferenceAsJson is a list, then count the length
if (Array.isArray(rawReferenceAsJson)) {
numReferences = rawReferenceAsJson.length;
rawReferenceAsJson.forEach((reference, index) => {
let polishedReference = generateReference(reference, index);
referenceSection.appendChild(polishedReference);
});
} else {
numReferences += processOnlineReferences(referenceSection, rawReferenceAsJson);
} }
references.appendChild(referenceExpandButton); // Decode message chunk from stream
const chunk = decoder.decode(value, { stream: true });
referenceExpandButton.addEventListener('click', function() { if (chunk.includes("### compiled references:")) {
if (referenceSection.classList.contains("collapsed")) { const additionalResponse = chunk.split("### compiled references:")[0];
referenceSection.classList.remove("collapsed"); rawResponse += additionalResponse;
referenceSection.classList.add("expanded"); newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse));
const rawReference = chunk.split("### compiled references:")[1];
const rawReferenceAsJson = JSON.parse(rawReference);
references = document.createElement('div');
references.classList.add("references");
let referenceExpandButton = document.createElement('button');
referenceExpandButton.classList.add("reference-expand-button");
let referenceSection = document.createElement('div');
referenceSection.classList.add("reference-section");
referenceSection.classList.add("collapsed");
let numReferences = 0;
// If rawReferenceAsJson is a list, then count the length
if (Array.isArray(rawReferenceAsJson)) {
numReferences = rawReferenceAsJson.length;
rawReferenceAsJson.forEach((reference, index) => {
let polishedReference = generateReference(reference, index);
referenceSection.appendChild(polishedReference);
});
} else { } else {
referenceSection.classList.add("collapsed"); numReferences += processOnlineReferences(referenceSection, rawReferenceAsJson);
referenceSection.classList.remove("expanded");
} }
});
let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`; references.appendChild(referenceExpandButton);
referenceExpandButton.innerHTML = expandButtonText;
references.appendChild(referenceSection);
readStream();
} else {
// Display response from Khoj
if (newResponseText.getElementsByClassName("spinner").length > 0) {
newResponseText.removeChild(loadingSpinner);
}
// Try to parse the chunk as a JSON object. It will be a JSON object if there is an error. referenceExpandButton.addEventListener('click', function() {
if (chunk.startsWith("{") && chunk.endsWith("}")) { if (referenceSection.classList.contains("collapsed")) {
try { referenceSection.classList.remove("collapsed");
const responseAsJson = JSON.parse(chunk); referenceSection.classList.add("expanded");
if (responseAsJson.image) { } else {
rawResponse += `![${query}](data:image/png;base64,${responseAsJson.image})`; referenceSection.classList.add("collapsed");
referenceSection.classList.remove("expanded");
} }
if (responseAsJson.detail) { });
rawResponse += responseAsJson.detail;
} let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`;
} catch (error) { referenceExpandButton.innerHTML = expandButtonText;
// If the chunk is not a JSON object, just display it as is references.appendChild(referenceSection);
rawResponse += chunk; readStream();
} finally {
newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse));
}
} else { } else {
// Display response from Khoj
if (newResponseText.getElementsByClassName("spinner").length > 0) {
newResponseText.removeChild(loadingSpinner);
}
// If the chunk is not a JSON object, just display it as is // If the chunk is not a JSON object, just display it as is
rawResponse += chunk; rawResponse += chunk;
newResponseText.innerHTML = ""; newResponseText.innerHTML = "";
newResponseText.appendChild(formatHTMLMessage(rawResponse)); newResponseText.appendChild(formatHTMLMessage(rawResponse));
readStream(); readStream();
} }
} });
// Scroll to bottom of chat window as chat response is streamed // Scroll to bottom of chat window as chat response is streamed
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight; document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
}); };
} }
readStream();
}; };
function incrementalChat(event) { function incrementalChat(event) {