Spaces:
Running
Running
// 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>`; | |
} |