|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Mariam AI</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> |
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" rel="stylesheet"> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> |
|
<style> |
|
.message-transition { |
|
transition: all 0.3s ease-in-out; |
|
} |
|
.gradient-background { |
|
background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%); |
|
} |
|
.message-content pre { |
|
background-color: #f6f8fa; |
|
border-radius: 6px; |
|
padding: 16px; |
|
overflow-x: auto; |
|
margin: 8px 0; |
|
} |
|
.message-content code { |
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; |
|
font-size: 0.9em; |
|
} |
|
.message-content p { |
|
margin-bottom: 0.5rem; |
|
} |
|
.message-content ul, .message-content ol { |
|
margin-left: 1.5rem; |
|
margin-bottom: 0.5rem; |
|
} |
|
.message-content ul { |
|
list-style-type: disc; |
|
} |
|
.message-content ol { |
|
list-style-type: decimal; |
|
} |
|
.message-content a { |
|
color: #2563eb; |
|
text-decoration: underline; |
|
} |
|
.message-content table { |
|
border-collapse: collapse; |
|
margin: 8px 0; |
|
width: 100%; |
|
} |
|
.message-content th, .message-content td { |
|
border: 1px solid #e5e7eb; |
|
padding: 8px; |
|
text-align: left; |
|
} |
|
.message-content th { |
|
background-color: #f9fafb; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-50 min-h-screen"> |
|
<div class="container mx-auto px-4 py-8 max-w-4xl"> |
|
|
|
<div class="text-center mb-8"> |
|
<h1 class="text-4xl font-bold text-gray-800 mb-2">Mariam AI</h1> |
|
<p class="text-gray-600">Votre assistant intelligent personnel</p> |
|
</div> |
|
|
|
|
|
<div class="mb-6 p-4 bg-white rounded-lg shadow-md"> |
|
<div class="flex items-center justify-between"> |
|
<div class="flex items-center space-x-4"> |
|
<label class="flex items-center space-x-2 cursor-pointer"> |
|
<input type="checkbox" id="webSearchToggle" class="form-checkbox h-5 w-5 text-blue-600"> |
|
<span class="text-gray-700">Activer la recherche web</span> |
|
</label> |
|
</div> |
|
<div class="flex space-x-4"> |
|
<button onclick="clearHistory()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition duration-200"> |
|
<i class="fas fa-trash mr-2"></i>Effacer l'historique |
|
</button> |
|
<div class="relative"> |
|
<input type="file" id="fileInput" class="hidden" accept="image/*,.pdf,.txt"> |
|
<button onclick="document.getElementById('fileInput').click()" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition duration-200"> |
|
<i class="fas fa-upload mr-2"></i>Télécharger un fichier |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow-md h-[600px] flex flex-col"> |
|
|
|
<div id="chatMessages" class="flex-1 overflow-y-auto p-4 space-y-4"> |
|
|
|
{% for message in chat_history %} |
|
<div class="flex {{ 'justify-end' if message.role == 'user' else 'justify-start' }} message-transition"> |
|
<div class="max-w-[70%] p-3 rounded-lg {{ 'bg-blue-500 text-white rounded-br-none' if message.role == 'user' else 'bg-gray-100 text-gray-800 rounded-bl-none' }}"> |
|
{% if message.role == 'user' %} |
|
{{ message.content }} |
|
{% else %} |
|
<div class="message-content prose"> |
|
{{ message.content_html | safe }} |
|
</div> |
|
{% endif %} |
|
</div> |
|
</div> |
|
{% endfor %} |
|
</div> |
|
|
|
|
|
<div class="border-t p-4"> |
|
<form id="chatForm" class="flex space-x-2"> |
|
<input type="text" id="messageInput" class="flex-1 border rounded-lg px-4 py-2 focus:outline-none focus:border-blue-500" placeholder="Posez votre question..."> |
|
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2 rounded-lg transition duration-200"> |
|
<i class="fas fa-paper-plane"></i> |
|
</button> |
|
</form> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
let currentFile = null; |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', (event) => { |
|
document.querySelectorAll('pre code').forEach((el) => { |
|
hljs.highlightElement(el); |
|
}); |
|
}); |
|
|
|
|
|
function highlightCode() { |
|
document.querySelectorAll('pre code').forEach((el) => { |
|
hljs.highlightElement(el); |
|
}); |
|
} |
|
|
|
document.getElementById('fileInput').addEventListener('change', async (e) => { |
|
const file = e.target.files[0]; |
|
if (!file) return; |
|
|
|
const formData = new FormData(); |
|
formData.append('file', file); |
|
|
|
try { |
|
const response = await fetch('/upload', { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
currentFile = data.filename; |
|
alert('Fichier téléchargé avec succès !'); |
|
} else { |
|
alert('Erreur lors du téléchargement du fichier'); |
|
} |
|
} catch (error) { |
|
console.error('Error:', error); |
|
alert('Erreur lors du téléchargement du fichier'); |
|
} |
|
}); |
|
|
|
async function clearHistory() { |
|
try { |
|
const response = await fetch('/clear', { |
|
method: 'POST' |
|
}); |
|
const data = await response.json(); |
|
|
|
if (data.success) { |
|
document.getElementById('chatMessages').innerHTML = ''; |
|
} |
|
} catch (error) { |
|
console.error('Error:', error); |
|
alert('Erreur lors de la suppression de l\'historique'); |
|
} |
|
} |
|
|
|
document.getElementById('chatForm').addEventListener('submit', async (e) => { |
|
e.preventDefault(); |
|
|
|
const messageInput = document.getElementById('messageInput'); |
|
const message = messageInput.value.trim(); |
|
if (!message) return; |
|
|
|
|
|
addMessage('user', message); |
|
messageInput.value = ''; |
|
|
|
|
|
addTypingIndicator(); |
|
|
|
try { |
|
const response = await fetch('/chat', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify({ |
|
message: message, |
|
web_search: document.getElementById('webSearchToggle').checked, |
|
file: currentFile |
|
}), |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
|
|
removeTypingIndicator(); |
|
|
|
if (data.error) { |
|
addMessage('assistant', `Erreur: ${data.error}`); |
|
} else { |
|
addMessage('assistant', data.response, data.response_html); |
|
highlightCode(); |
|
} |
|
|
|
|
|
currentFile = null; |
|
} catch (error) { |
|
removeTypingIndicator(); |
|
addMessage('assistant', 'Désolé, une erreur est survenue.'); |
|
} |
|
}); |
|
|
|
function addMessage(role, content, contentHtml = null) { |
|
const messagesContainer = document.getElementById('chatMessages'); |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = `flex ${role === 'user' ? 'justify-end' : 'justify-start'} message-transition`; |
|
|
|
const messageBubble = document.createElement('div'); |
|
messageBubble.className = `max-w-[70%] p-3 rounded-lg ${ |
|
role === 'user' |
|
? 'bg-blue-500 text-white rounded-br-none' |
|
: 'bg-gray-100 text-gray-800 rounded-bl-none' |
|
}`; |
|
|
|
if (role === 'user' || !contentHtml) { |
|
messageBubble.textContent = content; |
|
} else { |
|
const contentWrapper = document.createElement('div'); |
|
contentWrapper.className = 'message-content prose'; |
|
contentWrapper.innerHTML = contentHtml; |
|
messageBubble.appendChild(contentWrapper); |
|
} |
|
|
|
messageDiv.appendChild(messageBubble); |
|
messagesContainer.appendChild(messageDiv); |
|
scrollToBottom(); |
|
} |
|
|
|
function addTypingIndicator() { |
|
const messagesContainer = document.getElementById('chatMessages'); |
|
const indicatorDiv = document.createElement('div'); |
|
indicatorDiv.id = 'typingIndicator'; |
|
indicatorDiv.className = 'flex justify-start message-transition'; |
|
indicatorDiv.innerHTML = ` |
|
<div class="bg-gray-100 p-3 rounded-lg rounded-bl-none"> |
|
<div class="flex space-x-2"> |
|
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div> |
|
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div> |
|
<div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.4s"></div> |
|
</div> |
|
</div> |
|
`; |
|
messagesContainer.appendChild(indicatorDiv); |
|
scrollToBottom(); |
|
} |
|
|
|
function removeTypingIndicator() { |
|
const indicator = document.getElementById('typingIndicator'); |
|
if (indicator) { |
|
indicator.remove(); |
|
} |
|
} |
|
|
|
function scrollToBottom() { |
|
const messagesContainer = document.getElementById('chatMessages'); |
|
messagesContainer.scrollTop = messagesContainer.scrollHeight; |
|
} |
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { |
|
document.getElementById('chatForm').dispatchEvent(new Event('submit')); |
|
} |
|
}); |
|
|
|
|
|
const messageInput = document.getElementById('messageInput'); |
|
messageInput.addEventListener('input', function() { |
|
this.style.height = 'auto'; |
|
this.style.height = (this.scrollHeight) + 'px'; |
|
}); |
|
|
|
|
|
scrollToBottom(); |
|
</script> |
|
</body> |
|
</html> |