console.log('script.js loaded'); let ws = null; // Initialize WebSocket connection function connectWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; ws = new WebSocket(`${protocol}//${window.location.host}/ws`); ws.onopen = function () { console.log('WebSocket connected'); }; ws.onmessage = function (event) { const status = JSON.parse(event.data); updateIndexingStatus(status); }; ws.onclose = function () { console.log('WebSocket disconnected, attempting to reconnect...'); setTimeout(connectWebSocket, 1000); }; ws.onerror = function (error) { console.error('WebSocket error:', error); }; } // Update indexing progress function updateIndexingStatus(status) { const statusDiv = document.getElementById('indexingStatus'); const progressBar = statusDiv.querySelector('.progress-bar'); const details = document.getElementById('indexingDetails'); if (status.status === 'idle') { // Fade out the status div statusDiv.style.opacity = '0'; setTimeout(() => { statusDiv.style.display = 'none'; statusDiv.style.opacity = '1'; }, 500); return; } // Show and update the status statusDiv.style.display = 'block'; statusDiv.style.opacity = '1'; // Calculate progress percentage const percentage = status.total_files > 0 ? Math.round((status.processed_files / status.total_files) * 100) : 0; progressBar.style.width = `${percentage}%`; progressBar.setAttribute('aria-valuenow', percentage); // Update status text let statusText = `Status: ${status.status}`; if (status.current_file) { statusText += ` | Current file: ${status.current_file}`; } if (status.total_files > 0) { statusText += ` | Progress: ${status.processed_files}/${status.total_files} (${percentage}%)`; } details.textContent = statusText; } // IntersectionObserver for lazy loading images let imageObserver = null; function observeLazyLoadImages() { const lazyLoadImages = document.querySelectorAll('img.lazy-load'); if (imageObserver) { // Disconnect previous observer if any imageObserver.disconnect(); } imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; const fullSrc = img.dataset.src; if (fullSrc) { img.src = fullSrc; img.removeAttribute('data-src'); img.classList.remove('lazy-load'); } observer.unobserve(img); } }); }, { rootMargin: '0px 0px 200px 0px' }); lazyLoadImages.forEach(img => { imageObserver.observe(img); }); } // Open file upload dialog function openFileUpload() { document.getElementById('multipleImageUpload').click(); } // Upload images async function uploadImages(event) { const files = event.target.files; if (!files || files.length === 0) return; // Validate file types const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']; const validFiles = Array.from(files).filter(file => allowedTypes.includes(file.type.toLowerCase()) ); if (validFiles.length === 0) { alert('Please select valid image files (JPEG, PNG, GIF, WebP)'); return; } if (validFiles.length !== files.length) { const skipped = files.length - validFiles.length; alert(`${skipped} file(s) were skipped as they are not valid image files`); } try { // Show upload progress showUploadProgress(validFiles.length); const formData = new FormData(); validFiles.forEach(file => { formData.append('files', file); }); const response = await fetch('/upload', { method: 'POST', body: formData }); if (response.ok) { const result = await response.json(); hideUploadProgress(); // Show success message showNotification('success', `Successfully uploaded ${validFiles.length} image(s)!`); // Reload folders and images await loadIndexedFolders(); } else { const error = await response.json(); hideUploadProgress(); showNotification('error', `Upload failed: ${error.detail || 'Unknown error'}`); } } catch (error) { console.error('Upload error:', error); hideUploadProgress(); showNotification('error', 'Upload failed. Please try again.'); } // Reset file input event.target.value = ''; } // Show upload progress function showUploadProgress(fileCount) { const progressDiv = document.getElementById('uploadProgress'); const statusText = document.getElementById('uploadStatus'); progressDiv.style.display = 'block'; statusText.textContent = `Uploading ${fileCount} image(s)...`; // Simulate progress const progressBar = document.getElementById('uploadProgressBar'); let progress = 0; const interval = setInterval(() => { progress += 5; progressBar.style.width = `${progress}%`; if (progress >= 90) { statusText.textContent = 'Processing and indexing images...'; clearInterval(interval); } }, 100); progressDiv.dataset.intervalId = interval; } // Hide upload progress function hideUploadProgress() { const progressDiv = document.getElementById('uploadProgress'); const progressBar = document.getElementById('uploadProgressBar'); if (progressDiv.dataset.intervalId) { clearInterval(progressDiv.dataset.intervalId); delete progressDiv.dataset.intervalId; } progressBar.style.width = '100%'; setTimeout(() => { progressDiv.style.display = 'none'; progressBar.style.width = '0%'; }, 1000); } // Show notification function showNotification(type, message) { const notification = document.createElement('div'); notification.className = `alert alert-${type === 'success' ? 'success' : 'danger'} alert-dismissible fade show position-fixed`; notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; max-width: 400px;'; notification.innerHTML = ` ${message} `; document.body.appendChild(notification); setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 5000); } // Load indexed folders async function loadIndexedFolders() { try { const response = await fetch('/folders'); const folders = await response.json(); const folderList = document.getElementById('folderList'); folderList.innerHTML = ''; if (folders.length === 0) { folderList.innerHTML = `
No folders indexed yet
`; return; } folders.forEach(folder => { const escapedPath = folder.path.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); const folderCard = document.createElement('div'); folderCard.className = `folder-item-card ${!folder.is_valid ? 'invalid' : ''}`; folderCard.innerHTML = `
${folder.path.split(/[\\/]/).pop()}
${folder.path}
${!folder.is_valid ? 'Path not accessible' : ''}
`; folderList.appendChild(folderCard); }); // Load images from all folders await loadImages(); } catch (error) { console.error('Error loading folders:', error); } } // Remove folder async function removeFolder(path) { if (confirm('Are you sure you want to remove this folder?')) { try { const encodedPath = encodeURIComponent(path).replace(/%5C/g, '\\'); const response = await fetch(`/folders/${encodedPath}`, { method: 'DELETE' }); if (response.ok) { await loadIndexedFolders(); } else { const error = await response.text(); alert(`Error removing folder: ${error}`); } } catch (error) { console.error('Error removing folder:', error); alert('Error removing folder. Please try again.'); } } } // Load images async function loadImages(folder = null) { try { const url = folder ? `/images?folder=${encodeURIComponent(folder)}` : '/images'; const response = await fetch(url); const images = await response.json(); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = ''; if (images.length === 0) { imageGrid.innerHTML = `
No images found

Upload some images to start building your visual search database

`; return; } images.forEach(image => { const card = document.createElement('div'); card.className = 'image-card'; card.innerHTML = `
${image.filename || image.path}
${image.filename || image.path} ${formatFileSize(image.file_size)}
`; imageGrid.appendChild(card); }); observeLazyLoadImages(); } catch (error) { console.error('Error loading images:', error); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = '
Error loading images. Please try again.
'; } } // Utility function to format file sizes function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Search images async function searchImages(event) { event.preventDefault(); const query = document.getElementById('searchInput').value; if (!query) return; try { const searchUrl = `/search/text?query=${encodeURIComponent(query)}`; const response = await fetch(searchUrl); const results = await response.json(); displaySearchResults(results); } catch (error) { console.error('Error searching images:', error); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = `
Search Error

An error occurred while searching. Please try again.

`; } } // Search by image async function searchByImage(event) { const file = event.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); try { const searchUrl = '/search/image'; const response = await fetch(searchUrl, { method: 'POST', body: formData }); const results = await response.json(); displaySearchResults(results); event.target.value = ''; } catch (error) { console.error('Error searching by image:', error); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = `
Image Search Error

An error occurred while processing your image. Please try again.

`; } } // Search by URL async function searchByUrl(event) { event.preventDefault(); const url = document.getElementById('urlInput').value; if (!url) return; try { const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = `
Loading...
Downloading and analyzing image...

This may take a few moments

`; const searchUrl = `/search/url?url=${encodeURIComponent(url)}`; const response = await fetch(searchUrl); const results = await response.json(); displaySearchResults(results); document.getElementById('urlInput').value = ''; toggleUrlSearch(); } catch (error) { console.error('Error searching by URL:', error); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = `
Error processing URL

Please check the URL and try again. Make sure it points to a valid image.

`; } } // Display search results function displaySearchResults(results) { const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = ''; if (results.length === 0) { imageGrid.innerHTML = `
No similar images found

Try adjusting your search terms or uploading a different image

`; return; } results.forEach(result => { const card = document.createElement('div'); card.className = 'image-card'; card.innerHTML = `
${result.filename || result.path}
${result.similarity}%
${result.filename || result.path} ${formatFileSize(result.file_size)}
`; imageGrid.appendChild(card); }); observeLazyLoadImages(); } // Toggle URL search form visibility function toggleUrlSearch() { const urlForm = document.getElementById('urlSearchForm'); const isVisible = urlForm.style.display !== 'none'; if (isVisible) { urlForm.style.display = 'none'; document.getElementById('urlInput').value = ''; } else { urlForm.style.display = 'flex'; document.getElementById('urlInput').focus(); } } // Initialize document.addEventListener('DOMContentLoaded', () => { connectWebSocket(); loadIndexedFolders(); }); // Initialize folder browser async function initFolderBrowser() { folderModal = new bootstrap.Modal(document.getElementById('folderBrowserModal')); await loadFolderContents(); await loadIndexedFolders(); } // Open folder browser modal function openFolderBrowser() { selectedFolder = null; folderModal.show(); loadFolderContents(); } function showDrives(breadcrumb, browser, data) { // Windows drives breadcrumb.innerHTML = ''; data.drives.forEach(drive => { const escapedDrive = drive.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); browser.innerHTML += `
${drive}
`; }); } function showFolderContents(breadcrumb, browser, data) { // Folder contents currentPath = data.current_path; // Update breadcrumb const pathParts = currentPath.split(/[\\/]/); let currentBreadcrumb = ''; pathParts.forEach((part, index) => { if (part) { // Check if the path contains backslashes to detect Windows const isWindows = currentPath.includes('\\'); currentBreadcrumb += part + (isWindows ? '\\' : '/'); const isLast = index === pathParts.length - 1; const escapedPath = currentBreadcrumb.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); breadcrumb.innerHTML += ` `; } }); // Add parent directory if (data.parent_path) { addParentDirectory(browser, data); } // Add folders and files addFolderContents(browser, data); } function addParentDirectory(browser, data) { const escapedParentPath = data.parent_path.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); browser.innerHTML += `
..
`; } function addFolderContents(browser, data) { data.contents.forEach(item => { const icon = item.type === 'directory' ? 'bi-folder' : 'bi-image'; const escapedPath = item.path.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); browser.innerHTML += `
${item.name}
`; }); } // Load folder contents async function loadFolderContents(path = null) { try { const url = path ? `/browse/${encodeURIComponent(path)}` : '/browse'; const response = await fetch(url); const data = await response.json(); const browser = document.getElementById('folderBrowser'); const breadcrumb = document.getElementById('folderBreadcrumb'); browser.innerHTML = ''; breadcrumb.innerHTML = ''; if (data.drives) { showDrives(breadcrumb, browser, data); } else { showFolderContents(breadcrumb, browser, data); } } catch (error) { console.error('Error loading folder contents:', error); } } // Select folder for indexing function selectFolder(path) { selectedFolder = path; addSelectedFolder(); } // Add selected folder async function addSelectedFolder() { folderModal.hide(); if (!selectedFolder && currentPath) { selectedFolder = currentPath; } if (selectedFolder) { try { const encodedPath = encodeURIComponent(selectedFolder); const response = await fetch(`/folders?folder_path=${encodedPath}`, { method: 'POST' }); if (response.ok) { await loadIndexedFolders(); selectedFolder = null; } else { const error = await response.json(); alert(`Error adding folder: ${error.detail || error.message || JSON.stringify(error)}`); } } catch (error) { console.error('Error adding folder:', error); alert('Error adding folder. Please try again.'); } } } // Load indexed folders async function loadIndexedFolders() { try { const response = await fetch('/folders'); const folders = await response.json(); const folderList = document.getElementById('folderList'); folderList.innerHTML = ''; if (folders.length === 0) { folderList.innerHTML = `
No folders indexed yet
`; return; } folders.forEach(folder => { const escapedPath = folder.path.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); const folderCard = document.createElement('div'); folderCard.className = `folder-item-card ${!folder.is_valid ? 'invalid' : ''}`; folderCard.innerHTML = `
${folder.path.split(/[\\/]/).pop()}
${folder.path}
${!folder.is_valid ? 'Path not accessible' : ''}
`; folderList.appendChild(folderCard); }); // Load images from all folders await loadImages(); } catch (error) { console.error('Error loading folders:', error); } } // Remove folder async function removeFolder(path) { if (confirm('Are you sure you want to remove this folder?')) { try { const encodedPath = encodeURIComponent(path).replace(/%5C/g, '\\'); const response = await fetch(`/folders/${encodedPath}`, { method: 'DELETE' }); if (response.ok) { await loadIndexedFolders(); } else { const error = await response.text(); alert(`Error removing folder: ${error}`); } } catch (error) { console.error('Error removing folder:', error); alert('Error removing folder. Please try again.'); } } } // Load images async function loadImages(folder = null) { try { const url = folder ? `/images?folder=${encodeURIComponent(folder)}` : '/images'; const response = await fetch(url); const images = await response.json(); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = ''; if (images.length === 0) { imageGrid.innerHTML = `
No images found

Add some folders to start indexing your images

`; return; } images.forEach(image => { const card = document.createElement('div'); card.className = 'image-card'; card.innerHTML = `
${image.filename || image.path}
${image.filename || image.path} ${formatFileSize(image.file_size)}
`; imageGrid.appendChild(card); }); observeLazyLoadImages(); // Initialize IntersectionObserver for new images } catch (error) { console.error('Error loading images:', error); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = '
Error loading images. Please try again.
'; } } // Utility function to format file sizes function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Get current folder path function getCurrentPath() { // Return the current path if we're in a folder, otherwise null return currentPath; } // Search images async function searchImages(event) { event.preventDefault(); const query = document.getElementById('searchInput').value; if (!query) return; try { // Only include folder parameter if we're inside the folder browser const searchUrl = `/search/text?query=${encodeURIComponent(query)}`; const response = await fetch(searchUrl); const results = await response.json(); displaySearchResults(results); } catch (error) { console.error('Error searching images:', error); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = `
Search Error

An error occurred while searching. Please try again.

`; } } // Search by image async function searchByImage(event) { const file = event.target.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); try { const searchUrl = '/search/image'; const response = await fetch(searchUrl, { method: 'POST', body: formData }); const results = await response.json(); displaySearchResults(results); // Reset file input event.target.value = ''; } catch (error) { console.error('Error searching by image:', error); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = `
Image Search Error

An error occurred while processing your image. Please try again.

`; } } // Search by URL async function searchByUrl(event) { event.preventDefault(); const url = document.getElementById('urlInput').value; if (!url) return; try { // Show loading state const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = `
Loading...
Downloading and analyzing image...

This may take a few moments

`; const searchUrl = `/search/url?url=${encodeURIComponent(url)}`; const response = await fetch(searchUrl); const results = await response.json(); displaySearchResults(results); // Clear URL input and hide form document.getElementById('urlInput').value = ''; toggleUrlSearch(); } catch (error) { console.error('Error searching by URL:', error); const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = `
Error processing URL

Please check the URL and try again. Make sure it points to a valid image.

`; } } // Display search results (common function for all search types) function displaySearchResults(results) { const imageGrid = document.getElementById('imageGrid'); imageGrid.innerHTML = ''; if (results.length === 0) { imageGrid.innerHTML = `
No similar images found

Try adjusting your search terms or uploading a different image

`; return; } results.forEach(result => { const card = document.createElement('div'); card.className = 'image-card'; card.innerHTML = `
${result.filename || result.path}
${result.similarity}%
${result.filename || result.path} ${formatFileSize(result.file_size)}
`; imageGrid.appendChild(card); }); observeLazyLoadImages(); // Initialize IntersectionObserver for new images } // Toggle URL search form visibility function toggleUrlSearch() { const urlForm = document.getElementById('urlSearchForm'); const isVisible = urlForm.style.display !== 'none'; if (isVisible) { urlForm.style.display = 'none'; document.getElementById('urlInput').value = ''; } else { urlForm.style.display = 'flex'; document.getElementById('urlInput').focus(); } } // Initialize document.addEventListener('DOMContentLoaded', () => { connectWebSocket(); initFolderBrowser(); });