|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Assistant IA</title> |
|
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet"> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script> |
|
<style> |
|
:root { |
|
--primary-blue: #1E40AF; |
|
--light-blue: #EFF6FF; |
|
--accent-blue: #3B82F6; |
|
} |
|
|
|
body { |
|
background: linear-gradient(135deg, var(--light-blue) 0%, #ffffff 100%); |
|
min-height: 100vh; |
|
} |
|
|
|
.glass-effect { |
|
background: rgba(255, 255, 255, 0.9); |
|
backdrop-filter: blur(10px); |
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
} |
|
|
|
.message { |
|
transition: transform 0.3s ease; |
|
} |
|
|
|
.message:hover { |
|
transform: translateY(-2px); |
|
} |
|
|
|
.quick-action { |
|
background: linear-gradient(135deg, var(--primary-blue) 0%, var(--accent-blue) 100%); |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.quick-action:hover { |
|
transform: translateY(-2px); |
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); |
|
} |
|
|
|
.custom-input { |
|
border: 2px solid #E5E7EB; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.custom-input:focus { |
|
border-color: var(--accent-blue); |
|
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1); |
|
outline: none; |
|
} |
|
|
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
|
|
.loading-spinner { |
|
border-top-color: var(--accent-blue); |
|
animation: spin 1s linear infinite; |
|
} |
|
|
|
.file-upload-preview { |
|
background: rgba(59, 130, 246, 0.1); |
|
border: 2px dashed var(--accent-blue); |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.file-upload-preview:hover { |
|
background: rgba(59, 130, 246, 0.2); |
|
} |
|
</style> |
|
</head> |
|
<body class="font-sans"> |
|
<div class="container mx-auto px-4 py-8 max-w-6xl"> |
|
|
|
<header class="glass-effect rounded-2xl p-6 mb-8 flex justify-between items-center"> |
|
<div class="flex items-center space-x-4"> |
|
<div class="w-12 h-12 rounded-full bg-blue-600 flex items-center justify-center"> |
|
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/> |
|
</svg> |
|
</div> |
|
<div> |
|
<h1 class="text-2xl font-bold text-blue-900">Assistant IA</h1> |
|
<p class="text-blue-600">En ligne</p> |
|
</div> |
|
</div> |
|
<div class="flex items-center space-x-4"> |
|
<label class="flex items-center space-x-2 text-blue-900"> |
|
<input type="checkbox" id="webSearchToggle" class="w-4 h-4 text-blue-600 focus:ring-blue-500"> |
|
<span>Recherche web</span> |
|
</label> |
|
<button onclick="clearChat()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-all duration-300"> |
|
Effacer |
|
</button> |
|
</div> |
|
</header> |
|
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-8"> |
|
<button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2"> |
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/> |
|
</svg> |
|
<span>Recherche</span> |
|
</button> |
|
<button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2"> |
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/> |
|
</svg> |
|
<span>Brainstorm</span> |
|
</button> |
|
<button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2"> |
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/> |
|
</svg> |
|
<span>Analyse</span> |
|
</button> |
|
<button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2"> |
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/> |
|
</svg> |
|
<span>Images</span> |
|
</button> |
|
<button class="quick-action text-white p-4 rounded-xl flex items-center justify-center space-x-2"> |
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/> |
|
</svg> |
|
<span>Code</span> |
|
</button> |
|
</div> |
|
|
|
|
|
<div id="chatMessages" class="glass-effect rounded-2xl p-6 mb-8 h-[500px] overflow-y-auto space-y-4"> |
|
|
|
</div> |
|
|
|
|
|
<div class="glass-effect rounded-2xl p-6"> |
|
<div class="mb-4"> |
|
<input type="file" id="fileUpload" class="hidden" accept=".jpg,.jpeg,.png,.pdf,.txt,.mp3,.mp4"> |
|
<label for="fileUpload" class="file-upload-preview p-4 rounded-xl flex items-center justify-center cursor-pointer hover:bg-blue-50"> |
|
<svg class="w-6 h-6 text-blue-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/> |
|
</svg> |
|
<span id="fileName" class="text-blue-600">Déposer un fichier</span> |
|
</label> |
|
</div> |
|
|
|
<div class="flex space-x-4"> |
|
<div class="relative flex-grow"> |
|
<input type="text" id="messageInput" |
|
class="custom-input w-full rounded-xl px-6 py-4 text-blue-900 placeholder-blue-400" |
|
placeholder="Écrivez votre message..."> |
|
</div> |
|
<button onclick="sendMessage()" class="quick-action px-8 py-4 rounded-xl flex items-center justify-center"> |
|
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/> |
|
</svg> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="loadingOverlay" class="fixed inset-0 bg-white bg-opacity-75 backdrop-filter backdrop-blur-sm hidden flex items-center justify-center z-50"> |
|
<div class="loading-spinner w-16 h-16 border-4 border-blue-200 border-t-blue-600 rounded-full"></div> |
|
</div> |
|
|
|
<script> |
|
const messageInput = document.getElementById('messageInput'); |
|
const chatMessages = document.getElementById('chatMessages'); |
|
const webSearchToggle = document.getElementById('webSearchToggle'); |
|
const fileUpload = document.getElementById('fileUpload'); |
|
const fileName = document.getElementById('fileName'); |
|
const loadingOverlay = document.getElementById('loadingOverlay'); |
|
|
|
function addMessage(content, isUser = false) { |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = `message flex ${isUser ? 'justify-end' : 'justify-start'}`; |
|
|
|
const innerDiv = document.createElement('div'); |
|
innerDiv.className = `max-w-[75%] p-4 rounded-xl ${isUser ? 'bg-blue-600 text-white' : 'bg-white text-blue-900'} shadow-lg`; |
|
innerDiv.innerHTML = marked.parse(content); |
|
|
|
messageDiv.appendChild(innerDiv); |
|
chatMessages.appendChild(messageDiv); |
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
} |
|
|
|
async function sendMessage() { |
|
const message = messageInput.value.trim(); |
|
if (!message) return; |
|
|
|
addMessage(message, true); |
|
messageInput.value = ''; |
|
showLoading(); |
|
|
|
try { |
|
const response = await fetch('/send_message', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ |
|
message: message, |
|
web_search: webSearchToggle.checked |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
hideLoading(); |
|
if (data.error) { |
|
addMessage(`Erreur: ${data.error}`); |
|
} else { |
|
addMessage(data.response); |
|
} |
|
} catch (error) { |
|
hideLoading(); |
|
addMessage(`Erreur: ${error.message}`); |
|
} |
|
} |
|
|
|
messageInput.addEventListener('keypress', (e) => { |
|
if (e.key === 'Enter' && !e.shiftKey) { |
|
e.preventDefault(); |
|
sendMessage(); |
|
} |
|
}); |
|
|
|
fileUpload.addEventListener('change', async (e) => { |
|
const file = e.target.files[0]; |
|
if (!file) return; |
|
|
|
fileName.textContent = file.name; |
|
const formData = new FormData(); |
|
formData.append('file', file); |
|
|
|
showLoading(); |
|
|
|
try { |
|
const response = await fetch('/upload', { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
const data = await response.json(); |
|
hideLoading(); |
|
if (data.error) { |
|
addMessage(`Erreur: ${data.error}`); |
|
} else { |
|
addMessage(`Fichier téléchargé: ${file.name}`); |
|
} |
|
} catch (error) { |
|
hideLoading(); |
|
addMessage(`Erreur: ${error.message}`); |
|
} |
|
}); |
|
|
|
async function clearChat() { |
|
try { |
|
showLoading(); |
|
await fetch('/clear_chat', { method: 'POST' }); |
|
chatMessages.innerHTML = ''; |
|
hideLoading(); |
|
fileName.textContent = 'Déposer un fichier'; |
|
fileUpload.value = ''; |
|
} catch (error) { |
|
hideLoading(); |
|
addMessage(`Erreur: ${error.message}`); |
|
} |
|
} |
|
|
|
function showLoading() { |
|
loadingOverlay.classList.remove('hidden'); |
|
} |
|
|
|
function hideLoading() { |
|
loadingOverlay.classList.add('hidden'); |
|
} |
|
|
|
document.querySelectorAll('.quick-action').forEach(button => { |
|
button.addEventListener('click', () => { |
|
const text = button.querySelector('span').textContent.trim(); |
|
let prompt = ''; |
|
|
|
switch (text) { |
|
case 'Recherche': |
|
prompt = "Effectuez une recherche sur "; |
|
break; |
|
case 'Brainstorm': |
|
prompt = "Donnez-moi des idées sur "; |
|
break; |
|
case 'Analyse': |
|
prompt = "Analysez les données suivantes : "; |
|
break; |
|
case 'Images': |
|
prompt = "Créez une image de "; |
|
break; |
|
case 'Code': |
|
prompt = "Écrivez du code pour "; |
|
break; |
|
default: |
|
prompt = ""; |
|
} |
|
|
|
messageInput.value = prompt; |
|
messageInput.focus(); |
|
|
|
messageInput.setSelectionRange(prompt.length, prompt.length); |
|
}); |
|
}); |
|
|
|
|
|
function initializeChat() { |
|
|
|
const savedMessages = localStorage.getItem('chatMessages'); |
|
if (savedMessages) { |
|
const messages = JSON.parse(savedMessages); |
|
messages.forEach(msg => { |
|
addMessage(msg.content, msg.isUser); |
|
}); |
|
} |
|
|
|
|
|
messageInput.focus(); |
|
} |
|
|
|
|
|
function saveMessage(content, isUser) { |
|
const savedMessages = localStorage.getItem('chatMessages') || '[]'; |
|
const messages = JSON.parse(savedMessages); |
|
messages.push({ content, isUser }); |
|
localStorage.setItem('chatMessages', JSON.stringify(messages)); |
|
} |
|
|
|
|
|
function formatMessage(content) { |
|
return marked.parse(content); |
|
} |
|
|
|
|
|
window.addEventListener('error', function(e) { |
|
console.error('Erreur globale:', e.error); |
|
hideLoading(); |
|
addMessage('Une erreur est survenue. Veuillez réessayer.', false); |
|
}); |
|
|
|
|
|
window.addEventListener('offline', function() { |
|
addMessage('La connexion internet a été perdue. Vérifiez votre connexion.', false); |
|
}); |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', initializeChat); |
|
</script> |
|
</body> |
|
</html> |