109 lines
3.6 KiB
JavaScript
109 lines
3.6 KiB
JavaScript
/**
|
|
* Sibilla — Frontend search interface
|
|
*/
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const form = document.getElementById('search-form');
|
|
const queryInput = document.getElementById('query');
|
|
const submitBtn = document.getElementById('submit-btn');
|
|
const responseArea = document.getElementById('response-area');
|
|
const loading = document.getElementById('loading');
|
|
const answerDiv = document.getElementById('answer');
|
|
const sourcesDiv = document.getElementById('sources');
|
|
|
|
form.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const query = queryInput.value.trim();
|
|
if (!query) return;
|
|
|
|
// Show response area + loading
|
|
responseArea.style.display = 'block';
|
|
loading.style.display = 'flex';
|
|
answerDiv.innerHTML = '';
|
|
sourcesDiv.innerHTML = '';
|
|
submitBtn.disabled = true;
|
|
|
|
try {
|
|
const res = await fetch('/api/ask', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ query }),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const err = await res.json();
|
|
throw new Error(err.error || 'Errore sconosciuto');
|
|
}
|
|
|
|
const data = await res.json();
|
|
|
|
// Render answer as Markdown
|
|
answerDiv.innerHTML = renderMarkdown(data.answer);
|
|
|
|
// Render sources
|
|
if (data.sources && data.sources.length > 0) {
|
|
sourcesDiv.innerHTML = '<h3>📚 Fonti</h3>' +
|
|
data.sources.map(s =>
|
|
`<a href="${escapeHtml(s.url)}" target="_blank" rel="noopener" class="source-item">
|
|
<span class="source-title">${escapeHtml(s.title)}</span>
|
|
<span class="source-url">${escapeHtml(s.url)}</span>
|
|
</a>`
|
|
).join('');
|
|
}
|
|
|
|
} catch (err) {
|
|
answerDiv.innerHTML = `<div class="error-box">⚠️ ${escapeHtml(err.message)}</div>`;
|
|
} finally {
|
|
loading.style.display = 'none';
|
|
submitBtn.disabled = false;
|
|
}
|
|
});
|
|
|
|
// Allow Ctrl+Enter to submit
|
|
queryInput.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
form.dispatchEvent(new Event('submit'));
|
|
}
|
|
});
|
|
});
|
|
|
|
// ── Tiny Markdown renderer ─────────────────────────────────────
|
|
function renderMarkdown(text) {
|
|
if (!text) return '';
|
|
|
|
let html = escapeHtml(text);
|
|
|
|
// Headers
|
|
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
|
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
|
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
|
|
|
// Bold + italic
|
|
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
|
|
// Inline code
|
|
html = html.replace(/`(.+?)`/g, '<code>$1</code>');
|
|
|
|
// Links
|
|
html = html.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
|
|
|
// Paragraphs (double newlines)
|
|
html = html.replace(/\n\n/g, '</p><p>');
|
|
html = '<p>' + html + '</p>';
|
|
html = html.replace(/<p>\s*<\/p>/g, '');
|
|
|
|
// Line breaks
|
|
html = html.replace(/\n/g, '<br>');
|
|
|
|
return html;
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|