// Global application state let currentResults = []; let currentMode = 'single'; // Initialization document.addEventListener('DOMContentLoaded', function() { initializeApp(); }); function initializeApp() { setupTabHandlers(); setupKeyboardHandlers(); updateHeaderStats('Ready'); } // Tab management function setupTabHandlers() { document.querySelectorAll('.tab-button').forEach(button => { button.addEventListener('click', function() { const mode = this.dataset.mode; switchMode(mode); }); }); } function switchMode(mode) { currentMode = mode; // Update tabs document.querySelectorAll('.tab-button').forEach(btn => { btn.classList.remove('active'); }); document.querySelector(`[data-mode="${mode}"]`).classList.add('active'); // Update forms document.querySelectorAll('.search-form').forEach(form => { form.classList.remove('active'); }); document.getElementById(`${mode}-form`).classList.add('active'); // Reset results hideResults(); } // Show version field only when the input looks like a spec (ETSI or 3GPP) const ETSI_SPEC_RE = /^\d{3} \d{3}/; const ETSI_TDOC_RE = /^(?:SET|SCP|SETTEC|SETREQ|SCPTEC|SCPREQ)\(\d+\)\d+/i; const GPP_SPEC_RE = /^\d{2}\.\d{3}/; function toggleVersionField() { const docId = document.getElementById('doc-id').value.trim(); const versionGroup = document.getElementById('single-version-group'); const formatGroup = document.getElementById('single-format-group'); const isSpec = ETSI_SPEC_RE.test(docId) || GPP_SPEC_RE.test(docId); const isEtsiSpec = ETSI_SPEC_RE.test(docId); versionGroup.style.display = isSpec ? 'block' : 'none'; formatGroup.style.display = isEtsiSpec ? 'block' : 'none'; if (!isSpec) document.getElementById('doc-version').value = ''; if (!isEtsiSpec) document.getElementById('doc-format').value = 'pdf'; } // Keyboard shortcuts management function setupKeyboardHandlers() { document.getElementById('doc-id').addEventListener('keypress', function(e) { if (e.key === 'Enter') searchSingle(); }); document.getElementById('keywords').addEventListener('keypress', function(e) { if (e.key === 'Enter') searchKeyword(); }); document.getElementById('bm25-keywords').addEventListener('keypress', function(e) { if (e.key === 'Enter') searchBM25(); }); } // Search functions async function searchSingle() { const docId = document.getElementById('doc-id').value.trim(); if (!docId) { showError('Please enter a document ID'); return; } const format = document.getElementById('doc-format').value || 'pdf'; const isDocx = ETSI_SPEC_RE.test(docId) && format === 'docx'; if (isDocx) { downloadDocx(docId); return; } showLoading(); updateHeaderStats('Searching...'); try { const version = document.getElementById('doc-version').value.trim() || null; const body = { doc_id: docId }; if (version) body.version = version; const response = await fetch(`/find/single`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(body) }); const data = await response.json(); if (response.ok) { displaySingleResult(data); updateHeaderStats(`Found in ${data.search_time.toFixed(3)}s`); } else { showError(data.detail); updateHeaderStats('Error'); } } catch (error) { showError('Error connecting to server'); updateHeaderStats('Error'); console.error('Error:', error); } finally { hideLoading(); } } async function downloadDocx(docId) { showLoading(); updateHeaderStats('Downloading DOCX...'); try { const version = document.getElementById('doc-version').value.trim() || null; const body = { doc_id: docId }; if (version) body.version = version; const response = await fetch('/find/docx', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (response.ok) { const blob = await response.blob(); const disposition = response.headers.get('Content-Disposition') || ''; const fnMatch = disposition.match(/filename="?([^";\n]+)"?/); const filename = fnMatch ? fnMatch[1] : `${docId.replace(/ /g, '_')}.docx`; const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); updateHeaderStats('DOCX downloaded'); } else { const data = await response.json(); showError(data.detail); updateHeaderStats('Error'); } } catch (error) { showError('Error connecting to server'); updateHeaderStats('Error'); console.error('Error:', error); } finally { hideLoading(); } } async function searchBatch() { const batchText = document.getElementById('batch-ids').value.trim(); if (!batchText) { showError('Please enter at least one document ID'); return; } const docIds = batchText.split('\n') .map(id => id.trim()) .filter(id => id !== ''); if (docIds.length === 0) { showError('Please enter at least one valid document ID'); return; } showLoading(); updateHeaderStats('Searching...'); try { const version = document.getElementById('batch-version').value.trim() || null; const body = { doc_ids: docIds }; if (version) body.version = version; const response = await fetch(`/find/batch`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(body) }); const data = await response.json(); if (response.ok) { displayBatchResults(data); updateHeaderStats(`${Object.keys(data.results).length} found, ${data.missing.length} missing - ${data.search_time.toFixed(3)}s`); } else { showError(data.detail); updateHeaderStats('Error'); } } catch (error) { showError('Error connecting to server'); updateHeaderStats('Error'); console.error('Error:', error); } finally { hideLoading(); } } async function searchKeyword() { const keywords = document.getElementById('keywords').value.trim(); const searchMode = document.getElementById('search-mode-filter').value; if (!keywords && searchMode === 'deep') { showError('Please enter at least one keyword in deep search mode'); return; } showLoading(); updateHeaderStats('Searching...'); try { const body = { keywords: keywords, search_mode: searchMode, case_sensitive: document.getElementById('case-sensitive-filter').checked, source: document.getElementById('source-filter').value, mode: document.getElementById('mode-filter').value }; const specType = document.getElementById('spec-type-filter').value; if (specType) { body.spec_type = specType; } const response = await fetch(`/search`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(body) }); const data = await response.json(); if (response.ok) { displaySearchResults(data); updateHeaderStats(`${data.results.length} result(s) - ${data.search_time.toFixed(3)}s`); } else { showError(data.detail); updateHeaderStats('Error'); } } catch (error) { showError('Error connecting to server'); updateHeaderStats('Error'); console.error('Error:', error); } finally { hideLoading(); } } async function searchBM25() { const keywords = document.getElementById('bm25-keywords').value.trim(); if (!keywords) { showError('Please enter a search query'); return; } showLoading(); updateHeaderStats('Searching...'); try { const body = { keywords: keywords, source: document.getElementById('bm25-source-filter').value, threshold: parseInt(document.getElementById('threshold').value) || 60 }; const specType = document.getElementById('bm25-spec-type-filter').value; if (specType) { body.spec_type = specType; } const response = await fetch(`/search/bm25`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(body) }); const data = await response.json(); if (response.ok) { displaySearchResults(data); updateHeaderStats(`${data.results.length} result(s) - ${data.search_time.toFixed(3)}s`); } else { showError(data.detail); updateHeaderStats('Error'); } } catch (error) { showError('Error connecting to server'); updateHeaderStats('Error'); console.error('Error:', error); } finally { hideLoading(); } } // Results display functions function displaySingleResult(data) { const resultsContent = document.getElementById('results-content'); const isEtsiTdoc = ETSI_TDOC_RE.test(data.doc_id); const urlBlock = isEtsiTdoc ? `
${content}