Files
sibilla/static/app.js
T

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;
}