| <!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> |