Files
espiritismo-rag-system/frontend/static/js/script.js
T
lgcosta b9c0c66d21 feat: implementar sistema RAG Espiritismo completo
- Stack: GPT4All + Llama 3.2 3B, LangChain, Qdrant, FastAPI, Docker
- 11 tarefas implementadas com 100% de conclusão
- Funcionalidades: chat com IA, upload PDF, bot Telegram, agendamento
- 6 livros PDF indexados (A Gênese, Livro dos Espíritos, etc.)
- Documentação rica: README, BUILD, task reports, design specs
- Deploy com Docker Compose
- Production ready

Tech stack:
- AI: GPT4All 2.4.6, Llama 3.2 3B q4_0
- Backend: FastAPI 0.115.0, Celery 5.4.0, Redis 5.2.0
- RAG: LangChain 0.3.7, Qdrant 1.11.1
- Frontend: HTML5/CSS3/Vanilla JS
- Deploy: Docker Compose

Acceptance Criteria:
✅ CPU-only, <8GB RAM, ~5GB storage
✅ Suporta upload de PDFs
✅ Interface web rica e responsiva
✅ Bot Telegram funcional
✅ Agendamento de mensagens
✅ Código limpo e documentado
✅ Deploy via Docker

Status: PRODUCTION READY
Version: 1.0.0
Date: 2026-11-01
2026-11-01 10:56:17 -03:00

264 lines
7.6 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 message = input.value.trim();
if (!message) return;
// 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 }),
});
if (!response.ok) {
throw new Error('Erro ao enviar mensagem');
}
const data = await response.json();
// Remove loading
hideLoading();
// Add bot response
addMessage(data.response, 'bot');
// Add sources
if (data.sources && data.sources.length > 0) {
const sourceText = data.sources.map(s => `📖 ${s.metadata.title} (chunk ${s.metadata.chunk_id})`).join('\n');
addMessage(`Fontes: ${sourceText}`, 'bot', true);
}
} catch (error) {
hideLoading();
addMessage(`❌ Erro: ${error.message}`, 'bot');
}
}
function addMessage(content, type, isSource = false) {
const container = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}`;
if (isSource) {
messageDiv.style.fontSize = '0.85rem';
}
messageDiv.innerHTML = `<div class="message-content">${content}</div>`;
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';
bookDiv.innerHTML = `
<div class="book-icon">📖</div>
<div class="book-name">${book.title}</div>
`;
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';
div.innerHTML = `
<div class="schedule-info">
<span class="schedule-time">🕐 ${formatDateTime(schedule.time)}</span>
<span class="schedule-book">${schedule.book || 'Todos os livros'}</span>
</div>
<button class="schedule-delete" data-id="${schedule.id}">🗑️</button>
`;
container.appendChild(div);
// Add delete handler
div.querySelector('.schedule-delete').addEventListener('click', () => deleteSchedule(schedule.id));
});
}
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');
}