Spaces:
Sleeping
Sleeping
| // 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(); | |
| } | |
| // 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; | |
| } | |
| showLoading(); | |
| updateHeaderStats('Searching...'); | |
| try { | |
| const response = await fetch(`/find/single`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ doc_id: docId }) | |
| }); | |
| 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 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 response = await fetch(`/find/batch`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ doc_ids: docIds }) | |
| }); | |
| 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'); | |
| resultsContent.innerHTML = ` | |
| <div class="result-item"> | |
| <div class="result-header"> | |
| <div class="result-id">${data.doc_id}</div> | |
| <div class="result-status status-found">Found</div> | |
| </div> | |
| <div class="result-details"> | |
| ${data.version ? `<div class="result-detail"><strong>Version:</strong> ${data.version}</div>` : ''} | |
| ${data.scope ? `<div class="result-detail"><strong>Scope:</strong> ${data.scope}</div>` : ''} | |
| <div class="result-detail result-url"> | |
| <strong>URL:</strong> <a href="${data.url}" target="_blank">${data.url}</a> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| showResults(); | |
| } | |
| function displayBatchResults(data) { | |
| const resultsContent = document.getElementById('results-content'); | |
| let html = ''; | |
| // Found results | |
| Object.entries(data.results).forEach(([docId, url]) => { | |
| html += ` | |
| <div class="result-item"> | |
| <div class="result-header"> | |
| <div class="result-id">${docId}</div> | |
| <div class="result-status status-found">Found</div> | |
| </div> | |
| <div class="result-details"> | |
| <div class="result-detail result-url"> | |
| <strong>URL:</strong> <a href="${url}" target="_blank">${url}</a> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| // Missing documents | |
| data.missing.forEach(docId => { | |
| html += ` | |
| <div class="result-item"> | |
| <div class="result-header"> | |
| <div class="result-id">${docId}</div> | |
| <div class="result-status status-missing">Not Found</div> | |
| </div> | |
| <div class="result-details"> | |
| <div class="result-detail">Document not found or not indexed</div> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| resultsContent.innerHTML = html; | |
| showResults(); | |
| } | |
| function displaySearchResults(data) { | |
| const resultsContent = document.getElementById('results-content'); | |
| currentResults = data.results; | |
| let html = ''; | |
| data.results.forEach((spec, index) => { | |
| const hasContent = spec.contains && Object.keys(spec.contains).length > 0; | |
| html += ` | |
| <div class="result-item"> | |
| <div class="result-header"> | |
| <div class="result-id">${spec.id}</div> | |
| <div class="result-status status-found">${spec.type || 'Specification'}</div> | |
| </div> | |
| <div class="result-details"> | |
| <div class="result-detail"><strong>Title:</strong> ${spec.title}</div> | |
| ${spec.version ? `<div class="result-detail"><strong>Version:</strong> ${spec.version}</div>` : ''} | |
| ${spec.working_group ? `<div class="result-detail"><strong>Working Group:</strong> ${spec.working_group}</div>` : ''} | |
| ${spec.type ? `<div class="result-detail"><strong>Type:</strong> ${spec.type}</div>` : ''} | |
| ${spec.scope ? `<div class="result-detail"><strong>Scope:</strong> ${spec.scope}</div>` : ''} | |
| ${hasContent ? `<button class="view-content-btn" onclick="viewContent(${index})">View Content</button>` : ''} | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| resultsContent.innerHTML = html; | |
| showResults(); | |
| } | |
| // Content display functions | |
| function viewContent(index) { | |
| const spec = currentResults[index]; | |
| if (!spec.contains) return; | |
| document.getElementById('content-title').textContent = `${spec.id} - ${spec.title}`; | |
| const contentSections = document.getElementById('content-sections'); | |
| let html = ''; | |
| Object.entries(spec.contains).forEach(([sectionTitle, content]) => { | |
| html += ` | |
| <div class="content-section"> | |
| <h3>${sectionTitle}</h3> | |
| <p>${content}</p> | |
| <button class="copy-section-btn" onclick="copyText('${content.replace(/'/g, "\\'")}')"> | |
| Copy this section | |
| </button> | |
| </div> | |
| `; | |
| }); | |
| contentSections.innerHTML = html; | |
| showContentPage(); | |
| } | |
| function closeContentPage() { | |
| hideContentPage(); | |
| } | |
| function copyAllContent() { | |
| const sections = document.querySelectorAll('.content-section p'); | |
| const allText = Array.from(sections).map(p => p.textContent).join('\n\n'); | |
| copyText(allText); | |
| } | |
| function copyText(text) { | |
| navigator.clipboard.writeText(text).then(() => { | |
| showSuccess('Text copied to clipboard'); | |
| }).catch(() => { | |
| showError('Error copying text'); | |
| }); | |
| } | |
| // Interface utilities | |
| function showLoading() { | |
| document.getElementById('loading-container').style.display = 'flex'; | |
| hideResults(); | |
| hideError(); | |
| } | |
| function hideLoading() { | |
| document.getElementById('loading-container').style.display = 'none'; | |
| } | |
| function showResults() { | |
| document.getElementById('results-container').style.display = 'block'; | |
| hideError(); | |
| } | |
| function hideResults() { | |
| document.getElementById('results-container').style.display = 'none'; | |
| } | |
| function showContentPage() { | |
| document.getElementById('content-page').classList.add('active'); | |
| } | |
| function hideContentPage() { | |
| document.getElementById('content-page').classList.remove('active'); | |
| } | |
| function showError(message) { | |
| hideError(); | |
| const errorDiv = document.createElement('div'); | |
| errorDiv.className = 'error-message'; | |
| errorDiv.textContent = message; | |
| document.querySelector('.search-container').appendChild(errorDiv); | |
| setTimeout(() => { | |
| hideError(); | |
| }, 5000); | |
| } | |
| function showSuccess(message) { | |
| hideError(); | |
| const successDiv = document.createElement('div'); | |
| successDiv.className = 'success-message'; | |
| successDiv.textContent = message; | |
| document.querySelector('.search-container').appendChild(successDiv); | |
| setTimeout(() => { | |
| hideError(); | |
| }, 3000); | |
| } | |
| function hideError() { | |
| const existingMessages = document.querySelectorAll('.error-message, .success-message'); | |
| existingMessages.forEach(msg => msg.remove()); | |
| } | |
| function updateHeaderStats(text) { | |
| document.getElementById('header-stats').innerHTML = `<span class="stat-item">${text}</span>`; | |
| } |