diff --git a/src/interface/desktop/config.html b/src/interface/desktop/config.html index 077ea73d..f6a283d3 100644 --- a/src/interface/desktop/config.html +++ b/src/interface/desktop/config.html @@ -362,6 +362,7 @@ button.sync-data:hover { background-color: var(--summer-sun); box-shadow: 0px 3px 0px var(--background-color); + cursor: pointer; } .sync-force-toggle { align-content: center; diff --git a/src/khoj/database/migrations/0044_conversation_file_filters.py b/src/khoj/database/migrations/0044_conversation_file_filters.py new file mode 100644 index 00000000..cf8669f8 --- /dev/null +++ b/src/khoj/database/migrations/0044_conversation_file_filters.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.10 on 2024-05-29 19:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("database", "0043_alter_chatmodeloptions_model_type"), + ] + + operations = [ + migrations.AddField( + model_name="conversation", + name="file_filters", + field=models.JSONField(default=list), + ), + ] diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index def10c0a..edd262bd 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -258,6 +258,7 @@ class Conversation(BaseModel): slug = models.CharField(max_length=200, default=None, null=True, blank=True) title = models.CharField(max_length=200, default=None, null=True, blank=True) agent = models.ForeignKey(Agent, on_delete=models.SET_NULL, default=None, null=True, blank=True) + file_filters = models.JSONField(default=list) class PublicConversation(BaseModel): diff --git a/src/khoj/interface/web/chat.html b/src/khoj/interface/web/chat.html index b5cc1962..1a62f893 100644 --- a/src/khoj/interface/web/chat.html +++ b/src/khoj/interface/web/chat.html @@ -900,8 +900,15 @@ To get started, just start typing below. You can also type / to see a list of co } // Display indexing success message flashStatusInChatInput("✅ File indexed successfully"); - - + renderAllFiles(); + //get the selected-files ul first + var selectedFiles = document.getElementsByClassName("selected-files")[0]; + const escapedFileName = fileName.replace(/\./g, '\\.'); + const newFile = selectedFiles.querySelector(`#${escapedFileName}`); + if(!newFile){ + addFileFilterToConversation(fileName); + loadFileFiltersFromConversation(); + } }) .catch((error) => { console.log(error); @@ -1135,6 +1142,7 @@ To get started, just start typing below. You can also type / to see a list of co if (chatBody.dataset.conversationId) { chatHistoryUrl += `&conversation_id=${chatBody.dataset.conversationId}`; setupWebSocket(); + loadFileFiltersFromConversation(); } if (window.screen.width < 700) { @@ -1172,6 +1180,7 @@ To get started, just start typing below. You can also type / to see a list of co // Render conversation history, if any let chatBody = document.getElementById("chat-body"); chatBody.dataset.conversationId = response.conversation_id; + loadFileFiltersFromConversation(); setupWebSocket(); chatBody.dataset.conversationTitle = response.slug || `New conversation 🌱`; @@ -1320,7 +1329,6 @@ To get started, just start typing below. You can also type / to see a list of co document.getElementById("chat-input").value = query_via_url; chat(); } - } function fetchRemainingChatMessages(chatHistoryUrl) { @@ -1813,6 +1821,230 @@ To get started, just start typing below. You can also type / to see a list of co document.getElementById('existing-conversations').classList.toggle('collapsed'); document.getElementById('side-panel-collapse').style.transform = document.getElementById('side-panel').classList.contains('collapsed') ? 'rotate(0deg)' : 'rotate(180deg)'; } + var allFiles; + function renderAllFiles() { + fetch('/api/config/data/computer') + .then(response => response.json()) + .then(data => { + var indexedFiles = document.getElementsByClassName("indexed-files")[0]; + indexedFiles.innerHTML = ""; + + for (var filename of data) { + var listItem = document.createElement("li"); + listItem.className = "fileName"; + listItem.id = filename; + listItem.textContent = filename; + listItem.addEventListener('click', function() { + handleFileClick(this.id); + }); + indexedFiles.appendChild(listItem); + } + allFiles = data; + var nofilesmessage = document.getElementsByClassName("no-files-message")[0]; + if(allFiles.length === 0){ + nofilesmessage.innerHTML = "No files found. Visit "; + nofilesmessage.innerHTML += "Documentation" + } + else{ + nofilesmessage.innerHTML = ""; + } + }) + .catch((error) => { + console.error('Error:', error); + }); + } + function renderFilteredFiles(){ + var indexedFiles = document.getElementsByClassName("indexed-files")[0]; + indexedFiles.innerHTML = ""; + var input = document.getElementsByClassName("file-input")[0]; + var filter = input.value.toUpperCase(); + + for (var filename of allFiles) { + if (filename.toUpperCase().indexOf(filter) > -1) { + var listItem = document.createElement("li"); + listItem.className = "fileName"; + listItem.id = filename; + listItem.textContent = filename; + + // Add an event listener for the click event + listItem.addEventListener('click', function() { + handleFileClick(this.id); + }); + + // Append the list item to the indexed files container + indexedFiles.appendChild(listItem); + } + } + } + function handleFileClick(elementId) { + var element = document.getElementById(elementId); + if (element) { + var selectedFiles = document.getElementsByClassName("selected-files")[0]; + var selectedFile = document.getElementById(elementId); + + // Check if the element has a background color indicating selection + if (element.style.backgroundColor === "var(--primary-hover)") { + // Remove the file filter from the conversation + removeFileFilterFromConversation(elementId); + // Remove the selected file from the list of selected files + if (selectedFile) { + selectedFiles.removeChild(selectedFile); + } + var selectedFile = document.getElementById(elementId); + selectedFile.style.backgroundColor = "var(--primary)"; + selectedFile.style.border = "1px solid var(--primary-hover)"; + } else { + // If the element is not selected, select it + element.style.backgroundColor = "var(--primary-hover)"; // Set background color + element.style.border = "3px solid orange"; // Set border + // Add the file filter to the conversation + addFileFilterToConversation(elementId); + // Add the selected file to the list of selected files + var li = document.createElement("li"); + li.className = "fileName"; + li.id = elementId; + li.style.backgroundColor = "var(--primary-hover)"; // match the style + li.style.border = "3px solid orange"; // match the style + li.innerText = elementId; + selectedFiles.appendChild(li); + } + } else { + console.error('Element with id', elementId, 'not found.'); + } + } + + function addFileFilterToConversation(filename) { + var conversation_id = document.getElementById("chat-body").dataset.conversationId; + if (!conversation_id) { + console.error("Conversation ID not found on chat-body element."); + return; + } + + return fetch(`/api/chat/conversation/file-filters`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ filename, conversation_id }) // Pass the filename directly + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + console.log("Response from server:", data); + return data; + }) + .catch(error => { + console.error("Error:", error); + throw error; + }); + } + + function removeFileFilterFromConversation(filename) { + var conversation_id = document.getElementById("chat-body").dataset.conversationId; + if (!conversation_id) { + console.error("Conversation ID not found on chat-body element."); + return; + } + + return fetch(`/api/chat/conversation/file-filters`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ filename, conversation_id }) // Pass the filename directly + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.json(); + }) + .then(data => { + console.log("Response from server:", data); + return data; + }) + .catch(error => { + console.error("Error:", error); + throw error; + }); + } + + function getFileFiltersFromConversation() { + // Get the conversation_id from the data attribute + var conversation_id = document.getElementById("chat-body").dataset.conversationId; + + // Make sure conversation_id is not undefined or null + if (!conversation_id) { + console.error("No conversation ID found on chat-body element."); + return Promise.reject("No conversation ID found on chat-body element."); + } + + // Perform the fetch request + return fetch(`/api/chat/conversation/file-filters/${conversation_id}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(function(response) { + console.log("Response status:", response.status); // Log the response status + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return response.json(); + }) + .then(function(data) { + console.log("Response from server:", data); + return data; + }) + .catch(function(error) { + console.error("Error:", error); + throw error; // Rethrow the error to be handled elsewhere if needed + }); + } + + + function loadFileFiltersFromConversation(){ + getFileFiltersFromConversation() + .then(filters => { + var selectedFiles = document.getElementsByClassName("selected-files")[0]; + selectedFiles.innerHTML = ""; + for (var filter of filters) { + var li = document.createElement("li"); + li.className = "fileName"; + li.id = filter; + li.style.backgroundColor = "var(--primary-hover)"; // set background to orange + li.style.border = "2px solid orange"; // set border to orange + li.innerText = filter; + selectedFiles.appendChild(li); + } + //update indexed files to have checkmark if they are in the filters + var indexedFiles = document.getElementsByClassName("indexed-files")[0]; + indexedFiles.innerHTML = ""; + for (var filename of allFiles) { + var li = document.createElement("li"); + li.className = "fileName"; + li.id = filename; + li.innerText = filename; + if (filters.includes(filename)) { + li.style.backgroundColor = "var(--primary-hover)"; // set background to orange + li.style.border = "2px solid orange"; // set border to orange + } + li.setAttribute("onclick", "handleFileClick('" + filename + "')"); + indexedFiles.appendChild(li); + } + }) + .catch(error => { + // Handle any errors that occur during the fetch operation + console.error("Error loading file filters:", error); + }); + }
Files
+ +