Spaces:
Running
Running
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; | |