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 ~${Math.round(sizeMB)} MB 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 = '
Please enter a query.
'; 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 = 'No results found.
'; 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 = `"${result.quote}"
${authorText}
`; resultsDiv.appendChild(card); }); }