Spaces:
Running
Running
| const statusDiv = document.getElementById('status'); | |
| const searchInput = document.getElementById('search-input'); | |
| const resultsDiv = document.getElementById('results'); | |
| const searchButton = document.getElementById('search-button'); | |
| const progressContainer = document.getElementById('progress-container'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const dataInfoDiv = document.getElementById('data-info'); | |
| const deleteDataButton = document.getElementById('delete-data-button'); | |
| const deleteButtonContainer = document.getElementById('delete-button-container'); | |
| const worker = new Worker('worker.js', { type: 'module' }); | |
| deleteButtonContainer.classList.add('hidden'); | |
| // Initial state | |
| // searchInput.disabled = true; // Removed to enable during initialization | |
| // searchButton.disabled = true; // Removed to enable during initialization | |
| // Ensure delete button is always visible (UI-level control). Worker will decide if data exists. | |
| deleteButtonContainer.classList.remove('hidden'); | |
| // Helper to format bytes to MB | |
| function formatBytesToMB(bytes) { | |
| return (bytes / (1024 * 1024)).toFixed(2); | |
| } | |
| worker.onmessage = (event) => { | |
| const { type, payload } = event.data; | |
| if (type === 'loading') { | |
| statusDiv.textContent = payload; | |
| progressContainer.classList.remove('hidden'); | |
| } else if (type === 'progress') { | |
| const progressPercentage = Math.round(payload.progress); | |
| progressBar.style.width = `${progressPercentage || 0}%`; | |
| statusDiv.textContent = payload.detail; | |
| progressContainer.classList.remove('hidden'); | |
| } else if (type === 'ready') { | |
| statusDiv.textContent = ''; // Clear status when index is ready | |
| progressContainer.classList.add('hidden'); | |
| searchInput.disabled = false; | |
| searchButton.disabled = false; | |
| // Delete button remains visible at all times per UI requirement | |
| } else if (type === 'results') { | |
| // statusDiv.textContent = 'Search complete.'; // Removed as results themselves convey completion | |
| progressContainer.classList.add('hidden'); // Hide progress bar | |
| renderResults(payload); | |
| statusDiv.textContent = ''; // Clear status after search is complete | |
| deleteButtonContainer.classList.remove('hidden'); // Ensure delete button is visible | |
| } else if (type === 'indexSize') { | |
| // Keep delete button visible always. Worker tells us whether data is cached. | |
| // Visual enable/disable could be added later if desired. | |
| if (payload.indexCached) { | |
| dataInfoDiv.textContent = ''; // Clear data info if cached | |
| searchInput.disabled = false; | |
| searchButton.disabled = false; | |
| if (payload.modelCached) { | |
| statusDiv.textContent = ''; // Clear status if both index and model are loaded | |
| } else { | |
| statusDiv.textContent = ''; // Clear status if index is loaded, model will load on first search | |
| } | |
| } else { | |
| dataInfoDiv.textContent = ''; // Clear data info if not cached | |
| // Keep delete button visible even if no cached data | |
| searchInput.disabled = false; // Ensure input is enabled | |
| searchButton.disabled = false; // Ensure button is enabled | |
| // Render message with an inline info icon that has an accessible tooltip | |
| const sizeMB = formatBytesToMB(payload.size); | |
| // Friendly, non-scary message with accessible tooltip | |
| statusDiv.innerHTML = `Heads‑up: one‑time download <strong>~${Math.round(sizeMB)} MB</strong> to enable fast, private searches in your browser. Runs locally — delete anytime.`; | |
| } | |
| } else if (type === 'dataDeleted') { | |
| // Show confirmation message briefly and then reload the page so the UI and worker state fully reset | |
| statusDiv.textContent = payload; | |
| dataInfoDiv.textContent = ''; // Clear data info | |
| progressContainer.classList.add('hidden'); // Hide progress bar | |
| // Give the user a moment to see the confirmation, then reload to clear caches and restart the worker | |
| setTimeout(() => { | |
| // A hard reload ensures service worker/worker gets a clean start in some environments | |
| try { | |
| location.reload(); | |
| } catch (e) { | |
| // If reload fails for any reason, fall back to re-requesting index size | |
| worker.postMessage({ type: 'getIndexSize' }); | |
| } | |
| }, 700); | |
| } else if (type === 'error') { | |
| statusDiv.textContent = `Error: ${payload}`; | |
| progressContainer.classList.add('hidden'); | |
| } | |
| }; | |
| // Send initial request for index size when the page loads | |
| worker.postMessage({ type: 'getIndexSize' }); | |
| function doSearch() { | |
| const query = searchInput.value; | |
| if (query) { | |
| worker.postMessage({ type: 'search', payload: query }); | |
| statusDiv.textContent = 'Searching...'; | |
| resultsDiv.innerHTML = ''; // Clear previous results | |
| return true; | |
| } else { | |
| resultsDiv.innerHTML = '<p class="text-gray-500">Please enter a query.</p>'; | |
| return false; | |
| } | |
| } | |
| searchButton.addEventListener('click', doSearch); | |
| // Trigger search on Enter (without Shift). Shift+Enter inserts newline. | |
| searchInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| // Prevent newline from being inserted | |
| e.preventDefault(); | |
| doSearch(); | |
| } | |
| }); | |
| // Make confirm non-blocking so the click handler returns quickly (avoids long task warnings) | |
| deleteDataButton.addEventListener('click', () => { | |
| setTimeout(() => { | |
| if (confirm('Are you sure you want to delete the cached data?')) { | |
| worker.postMessage({ type: 'deleteData' }); | |
| } | |
| }, 0); | |
| }); | |
| function renderResults(results) { | |
| resultsDiv.innerHTML = ''; | |
| if (results.length === 0) { | |
| resultsDiv.innerHTML = '<p class="text-gray-500">No results found.</p>'; | |
| return; | |
| } | |
| results.forEach(result => { | |
| const card = document.createElement('div'); | |
| card.className = 'bg-gray-50 p-4 rounded-lg shadow mb-4'; | |
| // Handle null author gracefully; categories were removed from the index | |
| const authorText = result.author ? `- ${result.author}` : '- Unknown Author'; | |
| card.innerHTML = ` | |
| <p class="text-lg font-medium text-gray-800">"${result.quote}"</p> | |
| <p class="text-right text-gray-600 italic mt-2">${authorText}</p> | |
| `; | |
| resultsDiv.appendChild(card); | |
| }); | |
| } |