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.
This commit is contained in:
1 parent
7af7ba530d
commit
8eeb6b89f4
11 files changed
+185
-90
No files matched your search
@@ -85,6 +85,7 @@ header p {
|
||||
|
||||
.message-content {
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.message-source {
|
||||
@@ -170,3 +171,36 @@ th {
|
||||
border-color: #667eea;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.book-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.schedule-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.schedule-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
button.schedule-delete {
|
||||
width: auto;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
#upload-status {
|
||||
margin-top: 10px;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
@@ -17,9 +17,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
async function sendChatMessage() {
|
||||
const input = document.getElementById('chat-input');
|
||||
const button = document.getElementById('send-btn');
|
||||
const message = input.value.trim();
|
||||
|
||||
if (!message) return;
|
||||
if (!message || button.disabled) return;
|
||||
button.disabled = true;
|
||||
|
||||
// Add user message to chat
|
||||
addMessage(message, 'user');
|
||||
@@ -35,39 +37,39 @@ async function sendChatMessage() {
|
||||
body: JSON.stringify({ message, top_k: 3 }),
|
||||
});
|
||||
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error('Erro ao enviar mensagem');
|
||||
const detail = typeof data.detail === 'string' ? data.detail : 'Erro ao enviar mensagem';
|
||||
throw new Error(detail);
|
||||
}
|
||||
|
||||
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);
|
||||
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');
|
||||
addMessage(`Erro: ${error.message}`, 'bot');
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function addMessage(content, type, isSource = false) {
|
||||
function addMessage(content, type) {
|
||||
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>`;
|
||||
const body = document.createElement('div');
|
||||
body.className = 'message-content';
|
||||
body.textContent = content;
|
||||
messageDiv.appendChild(body);
|
||||
container.appendChild(messageDiv);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
@@ -103,10 +105,13 @@ async function loadBooks() {
|
||||
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>
|
||||
`;
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -189,17 +194,22 @@ function renderSchedules(schedules) {
|
||||
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>
|
||||
`;
|
||||
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);
|
||||
|
||||
// Add delete handler
|
||||
div.querySelector('.schedule-delete').addEventListener('click', () => deleteSchedule(schedule.id));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user