diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html index 4997ef99..ecd8ebf9 100644 --- a/src/interface/desktop/chat.html +++ b/src/interface/desktop/chat.html @@ -60,6 +60,52 @@ return referenceButton; } + function generateOnlineReference(reference, index) { + + // Generate HTML for Chat Reference + let title = reference.title; + let link = reference.link; + let snippet = reference.snippet; + let question = reference.question; + if (question) { + question = `Question: ${question}

`; + } else { + question = ""; + } + + let linkElement = document.createElement('a'); + linkElement.setAttribute('href', link); + linkElement.setAttribute('target', '_blank'); + linkElement.setAttribute('rel', 'noopener noreferrer'); + linkElement.classList.add("inline-chat-link"); + linkElement.classList.add("reference-link"); + linkElement.setAttribute('title', title); + linkElement.innerHTML = title; + + let referenceButton = document.createElement('button'); + referenceButton.innerHTML = linkElement.outerHTML; + referenceButton.id = `ref-${index}`; + referenceButton.classList.add("reference-button"); + referenceButton.classList.add("collapsed"); + referenceButton.tabIndex = 0; + + // Add event listener to toggle full reference on click + referenceButton.addEventListener('click', function() { + console.log(`Toggling ref-${index}`) + if (this.classList.contains("collapsed")) { + this.classList.remove("collapsed"); + this.classList.add("expanded"); + this.innerHTML = linkElement.outerHTML + `

${question + snippet}`; + } else { + this.classList.add("collapsed"); + this.classList.remove("expanded"); + this.innerHTML = linkElement.outerHTML; + } + }); + + return referenceButton; + } + function renderMessage(message, by, dt=null, annotations=null) { let message_time = formatDate(dt ?? new Date()); let by_name = by == "khoj" ? "🏮 Khoj" : "🤔 You"; @@ -90,8 +136,48 @@ chatBody.scrollTop = chatBody.scrollHeight; } - function renderMessageWithReference(message, by, context=null, dt=null) { - if (context == null || context.length == 0) { + function processOnlineReferences(referenceSection, onlineContext) { + let numOnlineReferences = 0; + for (let subquery in onlineContext) { + let onlineReference = onlineContext[subquery]; + if (onlineReference.organic && onlineReference.organic.length > 0) { + numOnlineReferences += onlineReference.organic.length; + for (let index in onlineReference.organic) { + let reference = onlineReference.organic[index]; + let polishedReference = generateOnlineReference(reference, index); + referenceSection.appendChild(polishedReference); + } + } + + if (onlineReference.knowledgeGraph && onlineReference.knowledgeGraph.length > 0) { + numOnlineReferences += onlineReference.knowledgeGraph.length; + for (let index in onlineReference.knowledgeGraph) { + let reference = onlineReference.knowledgeGraph[index]; + let polishedReference = generateOnlineReference(reference, index); + referenceSection.appendChild(polishedReference); + } + } + + if (onlineReference.peopleAlsoAsk && onlineReference.peopleAlsoAsk.length > 0) { + numOnlineReferences += onlineReference.peopleAlsoAsk.length; + for (let index in onlineReference.peopleAlsoAsk) { + let reference = onlineReference.peopleAlsoAsk[index]; + let polishedReference = generateOnlineReference(reference, index); + referenceSection.appendChild(polishedReference); + } + } + } + + return numOnlineReferences; + } + + function renderMessageWithReference(message, by, context=null, dt=null, onlineContext=null) { + if (context == null && onlineContext == null) { + renderMessage(message, by, dt); + return; + } + + if ((context && context.length == 0) && (onlineContext == null || (onlineContext && Object.keys(onlineContext).length == 0))) { renderMessage(message, by, dt); return; } @@ -100,8 +186,11 @@ let referenceExpandButton = document.createElement('button'); referenceExpandButton.classList.add("reference-expand-button"); - let expandButtonText = context.length == 1 ? "1 reference" : `${context.length} references`; - referenceExpandButton.innerHTML = expandButtonText; + let numReferences = 0; + + if (context) { + numReferences += context.length; + } references.appendChild(referenceExpandButton); @@ -127,6 +216,14 @@ referenceSection.appendChild(polishedReference); } } + + if (onlineContext) { + numReferences += processOnlineReferences(referenceSection, onlineContext); + } + + let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`; + referenceExpandButton.innerHTML = expandButtonText; + references.appendChild(referenceSection); renderMessage(message, by, dt, references); @@ -140,6 +237,8 @@ newHTML = newHTML.replace(/__([\s\S]*?)__/g, '$1'); // Remove any text between [INST] and tags. These are spurious instructions for the AI chat model. newHTML = newHTML.replace(/\[INST\].+(<\/s>)?/g, ''); + // For any text that has single backticks, replace them with tags + newHTML = newHTML.replace(/`([^`]+)`/g, '$1'); return newHTML; } @@ -221,15 +320,28 @@ let referenceExpandButton = document.createElement('button'); referenceExpandButton.classList.add("reference-expand-button"); - let expandButtonText = rawReferenceAsJson.length == 1 ? "1 reference" : `${rawReferenceAsJson.length} references`; - referenceExpandButton.innerHTML = expandButtonText; - references.appendChild(referenceExpandButton); 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"); @@ -240,10 +352,8 @@ } }); - rawReferenceAsJson.forEach((reference, index) => { - let polishedReference = generateReference(reference, index); - referenceSection.appendChild(polishedReference); - }); + let expandButtonText = numReferences == 1 ? "1 reference" : `${numReferences} references`; + referenceExpandButton.innerHTML = expandButtonText; references.appendChild(referenceSection); readStream(); } else { @@ -276,6 +386,9 @@ let chatInput = document.getElementById("chat-input"); chatInput.value = chatInput.value.trimStart(); + let questionStarterSuggestions = document.getElementById("question-starters"); + questionStarterSuggestions.style.display = "none"; + if (chatInput.value.startsWith("/") && chatInput.value.split(" ").length === 1) { let chatTooltip = document.getElementById("chat-tooltip"); chatTooltip.style.display = "block"; @@ -324,7 +437,7 @@ const khojToken = await window.tokenAPI.getToken(); const headers = { 'Authorization': `Bearer ${khojToken}` }; - fetch(`${hostURL}/api/chat/history?client=web`, { headers }) + fetch(`${hostURL}/api/chat/history?client=desktop`, { headers }) .then(response => response.json()) .then(data => { if (data.detail) { @@ -351,13 +464,38 @@ .then(response => { // Render conversation history, if any response.forEach(chat_log => { - renderMessageWithReference(chat_log.message, chat_log.by, chat_log.context, new Date(chat_log.created)); + renderMessageWithReference(chat_log.message, chat_log.by, chat_log.context, new Date(chat_log.created), chat_log.onlineContext); }); }) .catch(err => { return; }); + fetch(`${hostURL}/api/chat/starters?client=desktop`, { headers }) + .then(response => response.json()) + .then(data => { + // Render chat options, if any + if (data) { + let questionStarterSuggestions = document.getElementById("question-starters"); + for (let index in data) { + let questionStarter = data[index]; + let questionStarterButton = document.createElement('button'); + questionStarterButton.innerHTML = questionStarter; + questionStarterButton.classList.add("question-starter"); + questionStarterButton.addEventListener('click', function() { + questionStarterSuggestions.style.display = "none"; + document.getElementById("chat-input").value = questionStarter; + chat(); + }); + questionStarterSuggestions.appendChild(questionStarterButton); + } + questionStarterSuggestions.style.display = "grid"; + } + }) + .catch(err => { + return; + }); + fetch(`${hostURL}/api/chat/options`, { headers }) .then(response => response.json()) .then(data => { @@ -397,6 +535,9 @@
+ + +