mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-09 05:39:12 +00:00
Format the chat outputted message with code, bolding, or italics. Add a copy button for code. Closes #445.
This commit is contained in:
@@ -9,6 +9,16 @@
|
|||||||
<link rel="stylesheet" href="/static/assets/khoj.css">
|
<link rel="stylesheet" href="/static/assets/khoj.css">
|
||||||
</head>
|
</head>
|
||||||
<script>
|
<script>
|
||||||
|
function copyProgrammaticOutput(event) {
|
||||||
|
// Remove the first 4 characters which are the "Copy" button
|
||||||
|
const programmaticOutput = event.target.parentNode.textContent.trim().slice(4);
|
||||||
|
navigator.clipboard.writeText(programmaticOutput).then(() => {
|
||||||
|
console.log("Programmatic output copied to clipboard");
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error("Error copying programmatic output to clipboard:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function formatDate(date) {
|
function formatDate(date) {
|
||||||
// Format date in HH:MM, DD MMM YYYY format
|
// Format date in HH:MM, DD MMM YYYY format
|
||||||
let time_string = date.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', hour12: false });
|
let time_string = date.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit', hour12: false });
|
||||||
@@ -27,10 +37,11 @@
|
|||||||
function renderMessage(message, by, dt=null) {
|
function renderMessage(message, by, dt=null) {
|
||||||
let message_time = formatDate(dt ?? new Date());
|
let message_time = formatDate(dt ?? new Date());
|
||||||
let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You";
|
let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You";
|
||||||
|
let formattedMessage = formatHTMLMessage(message);
|
||||||
// Generate HTML for Chat Message and Append to Chat Body
|
// Generate HTML for Chat Message and Append to Chat Body
|
||||||
document.getElementById("chat-body").innerHTML += `
|
document.getElementById("chat-body").innerHTML += `
|
||||||
<div data-meta="${by_name} at ${message_time}" class="chat-message ${by}">
|
<div data-meta="${by_name} at ${message_time}" class="chat-message ${by}">
|
||||||
<div class="chat-message-text ${by}">${message}</div>
|
<div class="chat-message-text ${by}">${formattedMessage}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
// Scroll to bottom of chat-body element
|
// Scroll to bottom of chat-body element
|
||||||
@@ -48,10 +59,19 @@
|
|||||||
renderMessage(message+references, by, dt);
|
renderMessage(message+references, by, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatHTMLMessage(htmlMessage) {
|
||||||
|
// Replace any ``` with <div class="programmatic-output">
|
||||||
|
let newHTML = htmlMessage.replace(/```([\s\S]*?)```/g, '<div class="programmatic-output"><button class="copy-button" onclick="copyProgrammaticOutput(event)">Copy</button>$1</div>');
|
||||||
|
// Replace any ** with <b> and __ with <u>
|
||||||
|
newHTML = newHTML.replace(/\*\*([\s\S]*?)\*\*/g, '<b>$1</b>');
|
||||||
|
newHTML = newHTML.replace(/__([\s\S]*?)__/g, '<u>$1</u>');
|
||||||
|
return newHTML;
|
||||||
|
}
|
||||||
|
|
||||||
function chat() {
|
function chat() {
|
||||||
// Extract required fields for search from form
|
// Extract required fields for search from form
|
||||||
let query = document.getElementById("chat-input").value.trim();
|
let query = document.getElementById("chat-input").value.trim();
|
||||||
let results_count = localStorage.getItem("khojResultsCount") || 5;
|
let resultsCount = localStorage.getItem("khojResultsCount") || 5;
|
||||||
console.log(`Query: ${query}`);
|
console.log(`Query: ${query}`);
|
||||||
|
|
||||||
// Short circuit on empty query
|
// Short circuit on empty query
|
||||||
@@ -65,7 +85,7 @@
|
|||||||
document.getElementById("chat-input").setAttribute("disabled", "disabled");
|
document.getElementById("chat-input").setAttribute("disabled", "disabled");
|
||||||
|
|
||||||
// Generate backend API URL to execute query
|
// Generate backend API URL to execute query
|
||||||
let url = `/api/chat?q=${encodeURIComponent(query)}&n=${results_count}&client=web&stream=true`;
|
let url = `/api/chat?q=${encodeURIComponent(query)}&n=${resultsCount}&client=web&stream=true`;
|
||||||
|
|
||||||
let chat_body = document.getElementById("chat-body");
|
let chat_body = document.getElementById("chat-body");
|
||||||
let new_response = document.createElement("div");
|
let new_response = document.createElement("div");
|
||||||
@@ -73,14 +93,14 @@
|
|||||||
new_response.attributes["data-meta"] = "🏮 Khoj at " + formatDate(new Date());
|
new_response.attributes["data-meta"] = "🏮 Khoj at " + formatDate(new Date());
|
||||||
chat_body.appendChild(new_response);
|
chat_body.appendChild(new_response);
|
||||||
|
|
||||||
let new_response_text = document.createElement("div");
|
let newResponseText = document.createElement("div");
|
||||||
new_response_text.classList.add("chat-message-text", "khoj");
|
newResponseText.classList.add("chat-message-text", "khoj");
|
||||||
new_response.appendChild(new_response_text);
|
new_response.appendChild(newResponseText);
|
||||||
|
|
||||||
// Temporary status message to indicate that Khoj is thinking
|
// Temporary status message to indicate that Khoj is thinking
|
||||||
let loadingSpinner = document.createElement("div");
|
let loadingSpinner = document.createElement("div");
|
||||||
loadingSpinner.classList.add("spinner");
|
loadingSpinner.classList.add("spinner");
|
||||||
new_response_text.appendChild(loadingSpinner);
|
newResponseText.appendChild(loadingSpinner);
|
||||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||||
|
|
||||||
// Call specified Khoj API which returns a streamed response of type text/plain
|
// Call specified Khoj API which returns a streamed response of type text/plain
|
||||||
@@ -92,6 +112,10 @@
|
|||||||
function readStream() {
|
function readStream() {
|
||||||
reader.read().then(({ done, value }) => {
|
reader.read().then(({ done, value }) => {
|
||||||
if (done) {
|
if (done) {
|
||||||
|
// Evaluate the contents of new_response_text.innerHTML after all the data has been streamed
|
||||||
|
const currentHTML = newResponseText.innerHTML;
|
||||||
|
newResponseText.innerHTML = formatHTMLMessage(currentHTML);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,22 +124,23 @@
|
|||||||
|
|
||||||
if (chunk.includes("### compiled references:")) {
|
if (chunk.includes("### compiled references:")) {
|
||||||
const additionalResponse = chunk.split("### compiled references:")[0];
|
const additionalResponse = chunk.split("### compiled references:")[0];
|
||||||
new_response_text.innerHTML += additionalResponse;
|
newResponseText.innerHTML += additionalResponse;
|
||||||
|
|
||||||
const rawReference = chunk.split("### compiled references:")[1];
|
const rawReference = chunk.split("### compiled references:")[1];
|
||||||
const rawReferenceAsJson = JSON.parse(rawReference);
|
const rawReferenceAsJson = JSON.parse(rawReference);
|
||||||
let polishedReference = rawReferenceAsJson.map((reference, index) => generateReference(reference, index))
|
let polishedReference = rawReferenceAsJson.map((reference, index) => generateReference(reference, index))
|
||||||
.join("<sup>,</sup>");
|
.join("<sup>,</sup>");
|
||||||
|
|
||||||
new_response_text.innerHTML += polishedReference;
|
newResponseText.innerHTML += polishedReference;
|
||||||
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
document.getElementById("chat-body").scrollTop = document.getElementById("chat-body").scrollHeight;
|
||||||
|
readStream();
|
||||||
} else {
|
} else {
|
||||||
// Display response from Khoj
|
// Display response from Khoj
|
||||||
if (new_response_text.getElementsByClassName("spinner").length > 0) {
|
if (newResponseText.getElementsByClassName("spinner").length > 0) {
|
||||||
new_response_text.removeChild(loadingSpinner);
|
newResponseText.removeChild(loadingSpinner);
|
||||||
}
|
}
|
||||||
|
|
||||||
new_response_text.innerHTML += chunk;
|
newResponseText.innerHTML += chunk;
|
||||||
readStream();
|
readStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,6 +482,21 @@
|
|||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.programmatic-output {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||||
|
color: #333;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 10px 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 10px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
if ("{{demo}}" === "False") {
|
if ("{{demo}}" === "False") {
|
||||||
|
|||||||
Reference in New Issue
Block a user