Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Chat Streaming on Obsidian, Docker Image Version and First-Run, Chat Error Messages in Clients #589

Merged
merged 9 commits into from
Dec 22, 2023
10 changes: 5 additions & 5 deletions src/interface/desktop/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@
return referenceButton;
}

function renderMessage(message, by, dt=null, annotations=null) {
function renderMessage(message, by, dt=null, annotations=null, raw=false) {
let message_time = formatDate(dt ?? new Date());
let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You";
let formattedMessage = formatHTMLMessage(message);
let formattedMessage = formatHTMLMessage(message, raw);
let chatBody = document.getElementById("chat-body");

// Create a new div for the chat message
Expand Down Expand Up @@ -248,7 +248,7 @@
renderMessage(message, by, dt, references);
}

function formatHTMLMessage(htmlMessage) {
function formatHTMLMessage(htmlMessage, raw=false) {
var md = window.markdownit();
let newHTML = htmlMessage;

Expand All @@ -267,7 +267,7 @@
};

// Render markdown
newHTML = md.render(newHTML);
newHTML = raw ? newHTML : md.render(newHTML);
// Get any elements with a class that starts with "language"
let element = document.createElement('div');
element.innerHTML = newHTML;
Expand Down Expand Up @@ -574,7 +574,7 @@
.trim()
.replace(/(\r\n|\n|\r)/gm, "");

renderMessage(first_run_message, "khoj");
renderMessage(first_run_message, "khoj", null, null, true);

// Disable chat input field and update placeholder text
document.getElementById("chat-input").setAttribute("disabled", "disabled");
Expand Down
93 changes: 56 additions & 37 deletions src/interface/obsidian/src/chat_modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,21 @@ export class KhojChatModal extends Modal {
let chatBodyEl = contentEl.createDiv({ attr: { id: "khoj-chat-body", class: "khoj-chat-body" } });

// Get chat history from Khoj backend
await this.getChatHistory(chatBodyEl);
let getChatHistorySucessfully = await this.getChatHistory(chatBodyEl);
let placeholderText = getChatHistorySucessfully ? "Chat with Khoj [Hit Enter to send message]" : "Configure Khoj to enable chat";

// Add chat input field
let inputRow = contentEl.createDiv("khoj-input-row");
const chatInput = inputRow.createEl("input",
{
attr: {
type: "text",
id: "khoj-chat-input",
autofocus: "autofocus",
placeholder: "Chat with Khoj [Hit Enter to send message]",
class: "khoj-chat-input option"
}
})
let chatInput = inputRow.createEl("input", {
attr: {
type: "text",
id: "khoj-chat-input",
autofocus: "autofocus",
placeholder: placeholderText,
class: "khoj-chat-input option",
disabled: !getChatHistorySucessfully ? "disabled" : null
},
})

let transcribe = inputRow.createEl("button", {
text: "Transcribe",
Expand Down Expand Up @@ -162,7 +163,7 @@ export class KhojChatModal extends Modal {
referenceExpandButton.innerHTML = expandButtonText;
}

renderMessage(chatEl: Element, message: string, sender: string, dt?: Date): Element {
renderMessage(chatEl: Element, message: string, sender: string, dt?: Date, raw: boolean=false): Element {
let message_time = this.formatDate(dt ?? new Date());
let emojified_sender = sender == "khoj" ? "🏮 Khoj" : "🤔 You";

Expand All @@ -177,8 +178,12 @@ export class KhojChatModal extends Modal {
let chat_message_body_el = chatMessageEl.createDiv();
chat_message_body_el.addClasses(["khoj-chat-message-text", sender]);
let chat_message_body_text_el = chat_message_body_el.createDiv();
// @ts-ignore
MarkdownRenderer.renderMarkdown(message, chat_message_body_text_el, null, null);
if (raw) {
chat_message_body_text_el.innerHTML = message;
} else {
// @ts-ignore
MarkdownRenderer.renderMarkdown(message, chat_message_body_text_el, null, null);
}

// Remove user-select: none property to make text selectable
chatMessageEl.style.userSelect = "text";
Expand Down Expand Up @@ -212,11 +217,11 @@ export class KhojChatModal extends Modal {
return chat_message_el
}

renderIncrementalMessage(htmlElement: HTMLDivElement, additionalMessage: string) {
async renderIncrementalMessage(htmlElement: HTMLDivElement, additionalMessage: string) {
this.result += additionalMessage;
htmlElement.innerHTML = "";
// @ts-ignore
MarkdownRenderer.renderMarkdown(this.result, htmlElement, null, null);
await MarkdownRenderer.renderMarkdown(this.result, htmlElement, null, null);
// Scroll to bottom of modal, till the send message input box
this.modalEl.scrollTop = this.modalEl.scrollHeight;
}
Expand All @@ -228,15 +233,33 @@ export class KhojChatModal extends Modal {
return `${time_string}, ${date_string}`;
}

async getChatHistory(chatBodyEl: Element): Promise<void> {
async getChatHistory(chatBodyEl: Element): Promise<boolean> {
// Get chat history from Khoj backend
let chatUrl = `${this.setting.khojUrl}/api/chat/history?client=obsidian`;
let headers = { "Authorization": `Bearer ${this.setting.khojApiKey}` };
let response = await request({ url: chatUrl, headers: headers });
let chatLogs = JSON.parse(response).response;
chatLogs.forEach((chatLog: any) => {
this.renderMessageWithReferences(chatBodyEl, chatLog.message, chatLog.by, chatLog.context, new Date(chatLog.created), chatLog.intent?.type);
});

try {
let response = await fetch(chatUrl, { method: "GET", headers: headers });
let responseJson: any = await response.json();

if (responseJson.detail) {
// If the server returns error details in response, render a setup hint.
let setupMsg = "Hi 👋🏾, to start chatting add available chat models options via <a class='inline-chat-link' href='/server/admin'>the Django Admin panel</a> on the Server";
this.renderMessage(chatBodyEl, setupMsg, "khoj", undefined, true);

return false;
} else if (responseJson.response) {
let chatLogs = responseJson.response;
chatLogs.forEach((chatLog: any) => {
this.renderMessageWithReferences(chatBodyEl, chatLog.message, chatLog.by, chatLog.context, new Date(chatLog.created), chatLog.intent?.type);
});
}
} catch (err) {
let errorMsg = "Unable to get response from Khoj server ❤️‍🩹. Ensure server is running or contact developers for help at <a href='mailto:[email protected]'>[email protected]</a> or on <a href='https://discord.gg/BDgyabRM6e'>Discord</a>";
this.renderMessage(chatBodyEl, errorMsg, "khoj", undefined, true);
return false;
}
return true;
}

async getChatResponse(query: string | undefined | null): Promise<void> {
Expand All @@ -254,7 +277,7 @@ export class KhojChatModal extends Modal {

// Temporary status message to indicate that Khoj is thinking
this.result = "";
this.renderIncrementalMessage(responseElement, "🤔");
await this.renderIncrementalMessage(responseElement, "🤔");

let response = await fetch(chatUrl, {
method: "GET",
Expand Down Expand Up @@ -289,17 +312,17 @@ export class KhojChatModal extends Modal {
// If the chunk is not a JSON object, just display it as is
responseText = response.body.read().toString()
} finally {
this.renderIncrementalMessage(responseElement, responseText);
await this.renderIncrementalMessage(responseElement, responseText);
}
}

for await (const chunk of response.body) {
let responseText = chunk.toString();
if (responseText.includes("### compiled references:")) {
const additionalResponse = responseText.split("### compiled references:")[0];
this.renderIncrementalMessage(responseElement, additionalResponse);
const [additionalResponse, rawReference] = responseText.split("### compiled references:", 2);
await this.renderIncrementalMessage(responseElement, additionalResponse);
console.log(`Raw: ${responseText}\nResponse: ${additionalResponse}\nReferences: ${rawReference}`);

const rawReference = responseText.split("### compiled references:")[1];
const rawReferenceAsJson = JSON.parse(rawReference);
let references = responseElement.createDiv();
references.classList.add("references");
Expand Down Expand Up @@ -337,17 +360,12 @@ export class KhojChatModal extends Modal {
referenceExpandButton.innerHTML = expandButtonText;
references.appendChild(referenceSection);
} else {
if (responseText.startsWith("{") && responseText.endsWith("}")) {
} else {
// If the chunk is not a JSON object, just display it as is
continue;
}

this.renderIncrementalMessage(responseElement, responseText);
await this.renderIncrementalMessage(responseElement, responseText);
}
}
} catch (err) {
this.renderIncrementalMessage(responseElement, "Sorry, unable to get response from Khoj backend ❤️‍🩹. Contact developer for help at [email protected] or <a href='https://discord.gg/BDgyabRM6e'>in Discord</a>")
let errorMsg = "<p>Sorry, unable to get response from Khoj backend ❤️‍🩹. Contact developer for help at [email protected] or <a href='https://discord.gg/BDgyabRM6e'>in Discord</a></p>";
responseElement.innerHTML = errorMsg
}
}

Expand Down Expand Up @@ -379,8 +397,9 @@ export class KhojChatModal extends Modal {
} else {
// If conversation history is cleared successfully, clear chat logs from modal
chatBody.innerHTML = "";
await this.getChatHistory(chatBody);
this.flashStatusInChatInput(result.message);
let getChatHistoryStatus = await this.getChatHistory(chatBody);
let statusMsg = getChatHistoryStatus ? result.message : "Failed to clear conversation history";
this.flashStatusInChatInput(statusMsg);
}
} catch (err) {
this.flashStatusInChatInput("Failed to clear conversation history");
Expand Down
17 changes: 9 additions & 8 deletions src/khoj/interface/web/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@
return referenceButton;
}

function renderMessage(message, by, dt=null, annotations=null) {
function renderMessage(message, by, dt=null, annotations=null, raw=false) {
let message_time = formatDate(dt ?? new Date());
let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You";
let formattedMessage = formatHTMLMessage(message);
let formattedMessage = formatHTMLMessage(message, raw);
let chatBody = document.getElementById("chat-body");

// Create a new div for the chat message
Expand Down Expand Up @@ -257,7 +257,7 @@
renderMessage(message, by, dt, references);
}

function formatHTMLMessage(htmlMessage) {
function formatHTMLMessage(htmlMessage, raw=false) {
var md = window.markdownit();
let newHTML = htmlMessage;

Expand All @@ -276,7 +276,7 @@
};

// Render markdown
newHTML = md.render(newHTML);
newHTML = raw ? newHTML : md.render(newHTML);
// Get any elements with a class that starts with "language"
let element = document.createElement('div');
element.innerHTML = newHTML;
Expand Down Expand Up @@ -435,9 +435,9 @@
numReferences = rawReferenceAsJson.length;

rawReferenceAsJson.forEach((reference, index) => {
let polishedReference = generateReference(reference, index);
referenceSection.appendChild(polishedReference);
});
let polishedReference = generateReference(reference, index);
referenceSection.appendChild(polishedReference);
});
} else {
numReferences += processOnlineReferences(referenceSection, rawReferenceAsJson);
}
Expand Down Expand Up @@ -539,7 +539,8 @@
.then(data => {
if (data.detail) {
// If the server returns a 500 error with detail, render a setup hint.
renderMessage("Hi 👋🏾, to start chatting add available chat models options via <a class='inline-chat-link' href='/server/admin'>the Django Admin panel</a> on the Server", "khoj");
let setupMsg = "Hi 👋🏾, to start chatting add available chat models options via <a class='inline-chat-link' href='/server/admin'>the Django Admin panel</a> on the Server";
renderMessage(setupMsg, "khoj", null, null, true);

// Disable chat input field and update placeholder text
document.getElementById("chat-input").setAttribute("disabled", "disabled");
Expand Down
Loading