QuoteSearch / main.js
ruidiao's picture
Upload main.js
0c51417
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);
});
}