File size: 6,571 Bytes
1e32f6a
 
 
 
 
 
 
 
 
 
 
 
d635592
1e32f6a
 
 
d635592
 
1e32f6a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d635592
1e32f6a
 
 
 
 
 
 
d635592
 
1e32f6a
 
 
 
 
 
 
 
 
 
 
 
d635592
1e32f6a
 
609c06b
 
3409b6b
0c51417
1e32f6a
 
d635592
1e32f6a
 
 
d635592
 
 
 
 
 
 
 
 
 
1e32f6a
 
 
 
 
 
 
 
 
fd5d5af
1e32f6a
 
 
 
 
fd5d5af
1e32f6a
 
fd5d5af
 
 
 
 
 
 
 
 
 
 
 
1e32f6a
 
 
d635592
1e32f6a
d635592
 
 
 
 
1e32f6a
 
 
 
 
 
 
 
 
 
 
 
ea0ddf6
1e32f6a
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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);
    });
}