RAGnarok / static /script.js
om4r932's picture
Add Markdown compability
43b72e8
class DocumentSearchChatBot {
constructor() {
this.selectedDocuments = new Set();
this.searchResults = [];
this.chatHistory = [];
this.initializeEventListeners();
this.updateThresholdDisplay();
}
initializeEventListeners() {
// Search form
document.getElementById('search-form').addEventListener('submit', (e) => {
e.preventDefault();
this.performSearch();
});
// Threshold slider
document.getElementById('threshold').addEventListener('input', (e) => {
this.updateThresholdDisplay();
});
// Selection controls
document.getElementById('select-all').addEventListener('click', () => {
this.selectAllDocuments();
});
document.getElementById('unselect-all').addEventListener('click', () => {
this.unselectAllDocuments();
});
// Chat launch
document.getElementById('start-chat').addEventListener('click', () => {
this.startChat();
});
// Chat form
document.getElementById('chat-form').addEventListener('submit', (e) => {
e.preventDefault();
this.sendChatMessage();
});
// Back to search
document.getElementById('back-to-search').addEventListener('click', () => {
this.backToSearch();
});
// Modal close
document.querySelector('.modal-close').addEventListener('click', () => {
this.closeModal();
});
// Close modal on background click
document.getElementById('modal').addEventListener('click', (e) => {
if (e.target.id === 'modal') {
this.closeModal();
}
});
}
updateThresholdDisplay() {
const threshold = document.getElementById('threshold').value;
document.getElementById('threshold-value').textContent = threshold;
}
async performSearch() {
const keyword = document.getElementById('keyword').value.trim();
const threshold = parseFloat(document.getElementById('threshold').value);
if (!keyword) return;
this.showLoading();
try {
const response = await fetch('/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
keyword: keyword,
threshold: threshold
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
this.searchResults = data.results || [];
this.displayResults();
} catch (error) {
console.error('Error on search:', error);
this.showError(`Error on search. Please try again. : ${error}`);
} finally {
this.hideLoading();
}
}
displayResults() {
const resultsContainer = document.getElementById('results-container');
const resultsSection = document.getElementById('results-section');
if (this.searchResults.length === 0) {
resultsContainer.innerHTML = '<p class="no-results">No results has been found.</p>';
resultsSection.classList.remove('hidden');
return;
}
resultsContainer.innerHTML = '';
this.searchResults.forEach((result, index) => {
const card = this.createResultCard(result, index);
resultsContainer.appendChild(card);
let viewBtn = card.querySelector('.view-btn');
viewBtn.addEventListener('click', () => {
this.showDocumentContent(result.id, result.section, result.content);
});
});
resultsSection.classList.remove('hidden');
this.updateChatButtonState();
}
escapeHtml(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
createResultCard(result, index) {
const card = document.createElement('div');
card.className = 'result-card';
card.dataset.index = index;
card.innerHTML = `
<div class="card-header">
<div class="card-info">
<div class="card-id">ID: ${this.escapeHtml(result.id)}</div>
<div class="card-title">${this.escapeHtml(result.title)}</div>
<div class="card-section">${this.escapeHtml(result.section)}</div>
</div>
<div class="similarity-score">${this.escapeHtml(result.similarity)}%</div>
</div>
<div class="card-actions">
<button class="view-btn" data-id="${this.escapeHtml(result.id)}" data-section="${this.escapeHtml(result.section)}" data-content="${this.escapeHtml(result.content)}">
<span class="material-icons">visibility</span>
Voir le contenu
</button>
<input type="checkbox" class="select-checkbox" onchange="app.toggleDocumentSelection(${this.escapeHtml(index)})">
</div>
`;
return card;
}
toggleDocumentSelection(index) {
const card = document.querySelector(`[data-index="${index}"]`);
const checkbox = card.querySelector('.select-checkbox');
if (checkbox.checked) {
this.selectedDocuments.add(index);
card.classList.add('selected');
} else {
this.selectedDocuments.delete(index);
card.classList.remove('selected');
}
this.updateChatButtonState();
}
selectAllDocuments() {
const checkboxes = document.querySelectorAll('.select-checkbox');
checkboxes.forEach((checkbox, index) => {
checkbox.checked = true;
this.selectedDocuments.add(index);
checkbox.closest('.result-card').classList.add('selected');
});
this.updateChatButtonState();
}
unselectAllDocuments() {
const checkboxes = document.querySelectorAll('.select-checkbox');
checkboxes.forEach((checkbox, index) => {
checkbox.checked = false;
this.selectedDocuments.delete(index);
checkbox.closest('.result-card').classList.remove('selected');
});
this.updateChatButtonState();
}
updateChatButtonState() {
const chatButton = document.getElementById('start-chat');
chatButton.disabled = this.selectedDocuments.size === 0;
}
showDocumentContent(id, section, content) {
// Simuler le contenu du document (remplacer par un appel API réel)
document.getElementById('modal-title').textContent = `Specification n°${id} - ${section}`;
document.getElementById('modal-body').textContent = content;
document.getElementById('modal').style.display = 'block';
}
closeModal() {
document.getElementById('modal').style.display = 'none';
}
startChat() {
if (this.selectedDocuments.size === 0) return;
const selectedDocs = Array.from(this.selectedDocuments).map(index => {
const doc = this.searchResults[index];
return `(${doc.id} ${doc.title} ${doc.section} ${doc.content || ""})`
}).join("\n");
this.chatHistory = [{
"role": "system",
"content": `You are a helpful AI assistant. You will answer any questions related to the following specifications: ${selectedDocs}`
}];
document.getElementById('search-section').classList.add('hidden');
document.getElementById('results-section').classList.add('hidden');
document.getElementById('chat-section').classList.remove('hidden');
// Ajouter un message de bienvenue
this.addChatMessage('bot', `Hello ! I'm ready to answer to all of your questions regarding the ${this.selectedDocuments.size} selected section(s) of different specifications. What do you want to know ?`);
}
backToSearch() {
document.getElementById('chat-section').classList.add('hidden');
document.getElementById('search-section').classList.remove('hidden');
document.getElementById('results-section').classList.remove('hidden');
// Vider les messages du chat
document.getElementById('chat-messages').innerHTML = '';
}
async sendChatMessage() {
const input = document.getElementById('chat-input');
const message = input.value.trim();
const model = document.getElementById('model-select').value;
if (!message) return;
// Ajouter le message de l'utilisateur
this.addChatMessage('user', message);
input.value = '';
this.chatHistory.push({
"role": "user",
"content": message
});
// Désactiver le formulaire pendant l'envoi
const form = document.getElementById('chat-form');
const submitBtn = form.querySelector('button');
submitBtn.disabled = true;
try {
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
messages: this.chatHistory,
model: model,
})
});
if (!response.ok) {
this.chatHistory.push({
"role": "assistant",
"content": `HTTP error! status: ${response.status}`
})
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
this.addChatMessage('bot', data.response);
this.chatHistory.push({
"role": "assistant",
"content": data.response
})
} catch (error) {
console.error('Error on sending message:', error);
this.addChatMessage('bot', `Sorry, an error has occurred. Please try again. : ${error}`);
} finally {
submitBtn.disabled = false;
}
}
addChatMessage(sender, message) {
const messagesContainer = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}`;
try {
// Vérifier si marked est disponible
if (typeof marked !== 'undefined' && marked.parse) {
messageDiv.innerHTML = marked.parse(message);
} else if (typeof marked !== 'undefined' && marked.marked) {
// Pour les versions plus anciennes
messageDiv.innerHTML = marked.marked(message);
} else {
console.error('Marked library not loaded properly');
messageDiv.textContent = message; // Fallback
}
} catch (error) {
console.error('Markdown parsing error:', error);
messageDiv.textContent = message; // Fallback en cas d'erreur
}
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
showLoading() {
document.getElementById('loading').classList.remove('hidden');
}
hideLoading() {
document.getElementById('loading').classList.add('hidden');
}
showError(message) {
// Vous pouvez implémenter une notification d'erreur plus sophistiquée
alert(message);
}
}
// Initialiser l'application
const app = new DocumentSearchChatBot();
// Fonction globale pour les événements onclick dans le HTML
window.app = app;