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}
${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.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 = 'Drives';
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 += `
${isLast ? part : `${part}`}
`;
}
});
// 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}
${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.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();
});