From 673d0d367ca2e52dc7573524d6e05649c600fd23 Mon Sep 17 00:00:00 2001 From: Raghav Tirumale <62105787+MythicalCow@users.noreply.github.com> Date: Wed, 12 Jun 2024 06:21:35 -0400 Subject: [PATCH] Fix: Adding Support for Uploading Multiple Files (#803) * added support for uploading multiple files at a time. * optimized multiple file upload to use a batch upload * allowing files to upload even if there is one unsupported file --- src/khoj/interface/web/chat.html | 156 +++++++++++++++++-------------- 1 file changed, 88 insertions(+), 68 deletions(-) diff --git a/src/khoj/interface/web/chat.html b/src/khoj/interface/web/chat.html index 572ab9db..b678c226 100644 --- a/src/khoj/interface/web/chat.html +++ b/src/khoj/interface/web/chat.html @@ -810,7 +810,7 @@ To get started, just start typing below. You can also type / to see a list of co if (overlayText == null) { dropzone.classList.add('dragover'); var overlayText = document.createElement("div"); - overlayText.innerHTML = "Select a file or drag + drop it here to share it with Khoj"; + overlayText.innerHTML = "Select file(s) or drag + drop it here to share it with Khoj"; overlayText.className = "dropzone-overlay"; overlayText.id = "dropzone-overlay"; dropzone.appendChild(overlayText); @@ -818,9 +818,10 @@ To get started, just start typing below. You can also type / to see a list of co const fileInput = document.createElement('input'); fileInput.type = 'file'; + fileInput.multiple = true; fileInput.addEventListener('change', function() { - const selectedFile = fileInput.files[0]; - uploadDataForIndexing(selectedFile); + const selectedFiles = fileInput.files; + uploadDataForIndexing(selectedFiles); }); // Remove overlay text after file input is closed @@ -844,90 +845,109 @@ To get started, just start typing below. You can also type / to see a list of co fileInput.click(); } - function uploadDataForIndexing(file) { + function uploadDataForIndexing(files) { var dropzone = document.getElementById('chat-body'); + var badfiles = []; + var goodfiles = []; + var overlayText = document.getElementById("dropzone-overlay"); - if (!file || (!allowedExtensions.includes(file.type) && !allowedFileEndings.includes(file.name.split('.').pop()))) { - dropzone.classList.remove('dragover'); - if (file) { - alert("Sorry, that file type is not yet supported"); + for (let file of files) { + if (!file || (!allowedExtensions.includes(file.type) && !allowedFileEndings.includes(file.name.split('.').pop()))) { + if (file) { + badfiles.push(file.name); + } + } else { + goodfiles.push(file); } - var overlayText = document.getElementById("dropzone-overlay"); - if (overlayText != null) { - overlayText.remove(); - } - return; } - const fileName = file.name; - var fileContents = null; + if (badfiles.length > 0) { + alert("The following files are not supported yet:\n" + badfiles.join('\n')); + } + - var reader = new FileReader(); const formData = new FormData(); - - var overlayText = document.getElementById("dropzone-overlay"); if (overlayText != null) { // Display loading spinner var loadingSpinner = document.createElement("div"); - overlayText.innerHTML = "Uploading file for indexing"; + overlayText.innerHTML = "Uploading file(s) for indexing"; loadingSpinner.className = "spinner"; overlayText.appendChild(loadingSpinner); } - reader.onload = function (event) { - fileContents = event.target.result; - let fileType = file.type; - if (fileType === "") { - if (fileName.split('.').pop() === "org") { - fileType = "text/org"; - } else if (fileName.split('.').pop() === "md") { - fileType = "text/markdown"; - } else if (fileName.split('.').pop() === "txt") { - fileType = "text/plain"; - } else if (fileName.split('.').pop() === "html") { - fileType = "text/html"; - } else if (fileName.split('.').pop() === "pdf") { - fileType = "application/pdf"; - } - } - - let fileObj = new Blob([fileContents], { type: fileType }); - formData.append("files", fileObj, file.name); - console.log(formData); - - fetch("/api/v1/index/update?force=false&client=web", { - method: "POST", - body: formData, - }) - .then((data) => { - console.log(data); - dropzone.classList.remove('dragover'); - var overlayText = document.getElementById("dropzone-overlay"); - if (overlayText != null) { - overlayText.remove(); + // Create an array of Promises for file reading + const fileReadPromises = Array.from(goodfiles).map(file => { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.onload = function (event) { + let fileContents = event.target.result; + let fileType = file.type; + let fileName = file.name; + if (fileType === "") { + let fileExtension = fileName.split('.').pop(); + if (fileExtension === "org") { + fileType = "text/org"; + } else if (fileExtension === "md") { + fileType = "text/markdown"; + } else if (fileExtension === "txt") { + fileType = "text/plain"; + } else if (fileExtension === "html") { + fileType = "text/html"; + } else if (fileExtension === "pdf") { + fileType = "application/pdf"; + } else { + // Skip this file if its type is not supported + resolve(); + return; + } } - // Display indexing success message - flashStatusInChatInput("✅ File indexed successfully"); - renderAllFiles(); - addFileFilterToConversation(fileName); - loadFileFiltersFromConversation(); - }) - .catch((error) => { - console.log(error); - dropzone.classList.remove('dragover'); - var overlayText = document.getElementById("dropzone-overlay"); - if (overlayText != null) { - overlayText.remove(); - } - // Display indexing failure message - flashStatusInChatInput("⛔️ Failed to upload file for indexing"); + + let fileObj = new Blob([fileContents], { type: fileType }); + formData.append("files", fileObj, file.name); + resolve(); + }; + reader.onerror = reject; + reader.readAsArrayBuffer(file); + }); + }); + + // Wait for all files to be read before making the fetch request + Promise.all(fileReadPromises) + .then(() => { + return fetch("/api/v1/index/update?force=false&client=web", { + method: "POST", + body: formData, }); - }; - - reader.readAsArrayBuffer(file); + }) + .then((data) => { + console.log(data); + dropzone.classList.remove('dragover'); + var overlayText = document.getElementById("dropzone-overlay"); + if (overlayText != null) { + overlayText.remove(); + } + // Display indexing success message + flashStatusInChatInput("✅ File indexed successfully"); + renderAllFiles(); + for (let file of goodfiles) { + addFileFilterToConversation(file.name); + loadFileFiltersFromConversation(); + } + }) + .catch((error) => { + console.log(error); + dropzone.classList.remove('dragover'); + var overlayText = document.getElementById("dropzone-overlay"); + if (overlayText != null) { + overlayText.remove(); + } + // Display indexing failure message + flashStatusInChatInput("⛔️ Failed to upload file for indexing"); + }); } + function setupDropZone() { var dropzone = document.getElementById('chat-body');