Files
espiritismo-rag-system/frontend/static/js/script.js
T
lgcosta 8eeb6b89f4 fix: responder a partir dos livros com embeddings em português
Usa o modelo multilingual, ignora trechos curtos e o template do Llama 3.2
para a resposta sair em uma frase completa. O chat deixa a API livre durante
a geração, o upload indexa o PDF e os agendamentos não são gravados duas vezes.
2026-11-01 12:34:54 -03:00

274 lines
8.3 KiB
JavaScript

// API base URL
const API_BASE = '/api';
// Initialize
document.addEventListener('DOMContentLoaded', () => {
loadBooks();
loadSchedules();
// Event listeners
document.getElementById('send-btn').addEventListener('click', sendChatMessage);
document.getElementById('chat-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendChatMessage();
});
document.getElementById('upload-btn').addEventListener('click', uploadPDF);
document.getElementById('add-schedule-btn').addEventListener('click', addSchedule);
});
async function sendChatMessage() {
const input = document.getElementById('chat-input');
const button = document.getElementById('send-btn');
const message = input.value.trim();
if (!message || button.disabled) return;
button.disabled = true;
// Add user message to chat
addMessage(message, 'user');
input.value = '';
// Show loading
showLoading();
try {
const response = await fetch(`${API_BASE}/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, top_k: 3 }),
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
const detail = typeof data.detail === 'string' ? data.detail : 'Erro ao enviar mensagem';
throw new Error(detail);
}
hideLoading();
addMessage(data.response, 'bot');
if (data.sources && data.sources.length > 0) {
const sourceText = data.sources.map((source) => {
const meta = source.metadata || source;
return `📖 ${meta.title || 'Desconhecido'} (trecho ${meta.chunk_id ?? '?'})`;
}).join('\n');
addMessage(`Fontes:\n${sourceText}`, 'bot');
}
} catch (error) {
hideLoading();
addMessage(`Erro: ${error.message}`, 'bot');
} finally {
button.disabled = false;
input.focus();
}
}
function addMessage(content, type) {
const container = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}`;
const body = document.createElement('div');
body.className = 'message-content';
body.textContent = content;
messageDiv.appendChild(body);
container.appendChild(messageDiv);
container.scrollTop = container.scrollHeight;
}
function showLoading() {
const container = document.getElementById('chat-messages');
const loadingDiv = document.createElement('div');
loadingDiv.id = 'chat-loading';
loadingDiv.className = 'message bot';
loadingDiv.innerHTML = '<div class="message-content">Carregando...</div>';
container.appendChild(loadingDiv);
container.scrollTop = container.scrollHeight;
}
function hideLoading() {
const loading = document.getElementById('chat-loading');
if (loading) loading.remove();
}
async function loadBooks() {
try {
const response = await fetch(`${API_BASE}/books`);
const books = await response.json();
const booksList = document.getElementById('books-list');
booksList.innerHTML = '';
if (books.length === 0) {
booksList.innerHTML = '<p style="color: #666;">Nenhum livro encontrado.</p>';
return;
}
books.forEach(book => {
const bookDiv = document.createElement('div');
bookDiv.className = 'book-item';
const icon = document.createElement('div');
icon.className = 'book-icon';
icon.textContent = '📖';
const name = document.createElement('div');
name.className = 'book-name';
name.textContent = book.title;
bookDiv.append(icon, name);
booksList.appendChild(bookDiv);
});
// Update book dropdown
const select = document.getElementById('schedule-book');
select.innerHTML = '<option value="">Todos os livros</option>';
books.forEach(book => {
const option = document.createElement('option');
option.value = book.title;
option.textContent = book.title;
select.appendChild(option);
});
} catch (error) {
console.error('Erro ao carregar livros:', error);
}
}
async function uploadPDF() {
const fileInput = document.getElementById('file-input');
const file = fileInput.files[0];
if (!file) {
alert('Selecione um arquivo PDF primeiro');
return;
}
const statusDiv = document.getElementById('upload-status');
statusDiv.textContent = 'Enviando...';
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${API_BASE}/upload`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error('Erro ao fazer upload');
}
const data = await response.json();
statusDiv.textContent = `✅ ${data.filename} enviado com sucesso!`;
statusDiv.style.color = '#4caf50';
setTimeout(() => {
statusDiv.textContent = '';
fileInput.value = '';
}, 3000);
// Reload books
loadBooks();
} catch (error) {
statusDiv.textContent = `❌ Erro: ${error.message}`;
statusDiv.style.color = '#f44336';
}
}
async function loadSchedules() {
try {
const response = await fetch(`${API_BASE}/schedules`);
const schedules = await response.json();
renderSchedules(schedules);
} catch (error) {
console.error('Erro ao carregar agendamentos:', error);
}
}
function renderSchedules(schedules) {
const container = document.getElementById('schedules-list');
container.innerHTML = '';
if (schedules.length === 0) {
container.innerHTML = '<p style="color: #666;">Nenhum agendamento.</p>';
return;
}
schedules.forEach(schedule => {
const div = document.createElement('div');
div.className = 'schedule-item';
const info = document.createElement('div');
info.className = 'schedule-info';
const time = document.createElement('span');
time.className = 'schedule-time';
time.textContent = formatDateTime(schedule.time);
const book = document.createElement('span');
book.className = 'schedule-book';
book.textContent = schedule.book || 'Todos os livros';
info.append(time, book);
const remove = document.createElement('button');
remove.className = 'schedule-delete';
remove.type = 'button';
remove.textContent = 'Excluir';
remove.addEventListener('click', () => deleteSchedule(schedule.id));
div.append(info, remove);
container.appendChild(div);
});
}
async function addSchedule() {
const timeInput = document.getElementById('schedule-time');
const bookSelect = document.getElementById('schedule-book');
if (!timeInput.value) {
alert('Selecione uma data e hora');
return;
}
try {
const response = await fetch(`${API_BASE}/schedules`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
time: timeInput.value,
book: bookSelect.value || null,
enabled: true,
}),
});
if (!response.ok) {
throw new Error('Erro ao criar agendamento');
}
const schedule = await response.json();
loadSchedules();
timeInput.value = '';
bookSelect.value = '';
} catch (error) {
alert(`Erro: ${error.message}`);
}
}
async function deleteSchedule(scheduleId) {
if (!confirm('Tem certeza que deseja excluir este agendamento?')) {
return;
}
try {
const response = await fetch(`${API_BASE}/schedules/${scheduleId}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Erro ao excluir agendamento');
}
loadSchedules();
} catch (error) {
alert(`Erro: ${error.message}`);
}
}
function formatDateTime(dateString) {
const date = new Date(dateString);
return date.toLocaleString('pt-BR');
}