|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Mariam AI (Grok Style)</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> |
|
|
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
|
|
.loader { |
|
animation: spin 2s linear infinite; |
|
} |
|
|
|
|
|
.quick-action-btn { |
|
@apply bg-gray-800 hover:bg-gray-700 text-white font-semibold py-3 px-6 rounded-full w-full max-w-xs flex items-center justify-center transition duration-200 ease-in-out; |
|
|
|
} |
|
|
|
.message { |
|
transition: transform 0.2s ease-out; |
|
} |
|
|
|
.message:hover { |
|
transform: translateX(5px); |
|
} |
|
|
|
|
|
@media (max-width: 767px) { |
|
.quick-action-btn { |
|
@apply text-sm; |
|
} |
|
|
|
.quick-actions { |
|
display: none; |
|
} |
|
.bottom-bar { |
|
display: flex; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-900 text-white font-sans h-screen flex flex-col"> |
|
|
|
<div class="container mx-auto px-4 py-6 flex-grow flex flex-col"> |
|
|
|
<header class="flex justify-between items-center mb-8"> |
|
<div class="flex items-center"> |
|
<span class="text-2xl font-bold">Bonsoir, GCP ✨</span> |
|
</div> |
|
|
|
<div class="flex items-center"> |
|
<label class="flex items-center cursor-pointer mr-4"> |
|
<input type="checkbox" id="webSearchToggle" class="mr-2 h-5 w-5 text-blue-500 focus:ring-blue-500 border-gray-300 rounded"> |
|
<span class="text-sm">Activer la recherche web</span> |
|
</label> |
|
|
|
<button onclick="clearChat()" class="bg-red-600 hover:bg-red-700 text-white font-semibold py-2 px-4 rounded-full transition duration-200 ease-in-out"> |
|
Effacer |
|
</button> |
|
</div> |
|
</header> |
|
|
|
<div class="flex flex-col items-center justify-center flex-grow space-y-4 mb-8"> |
|
<button class="quick-action-btn"> |
|
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path></svg> |
|
Recherche |
|
</button> |
|
<button class="quick-action-btn"> |
|
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg> |
|
Tempête de cerveau |
|
</button> |
|
<button class="quick-action-btn"> |
|
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg> |
|
Analyser les données |
|
</button> |
|
<button class="quick-action-btn"> |
|
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path></svg> |
|
Créer des images |
|
</button> |
|
<button class="quick-action-btn"> |
|
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path></svg> |
|
Code |
|
</button> |
|
</div> |
|
|
|
<div id="chatMessages" class="flex-1 overflow-y-auto mb-4 space-y-4"> |
|
{% for message in messages %} |
|
<div class="flex {% if message.role == 'user' %}justify-end{% endif %}"> |
|
<div class="message max-w-3/4 p-3 rounded-lg {% if message.role == 'user' %}bg-blue-500 text-white{% else %}bg-gray-700{% endif %}"> |
|
{{ message.content }} |
|
</div> |
|
</div> |
|
{% endfor %} |
|
</div> |
|
|
|
<div class="border-t border-gray-700 pt-4"> |
|
<div class="mb-4 flex items-center"> |
|
<input type="file" id="fileUpload" class="hidden" accept=".jpg,.jpeg,.png,.pdf,.txt,.mp3,.mp4"> |
|
<label for="fileUpload" class="cursor-pointer bg-gray-700 hover:bg-gray-600 text-white py-2 px-4 rounded-full transition duration-200 ease-in-out mr-2"> |
|
<svg class="w-5 h-5 inline-block" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg> |
|
</label> |
|
<span id="fileName" class="text-sm text-gray-400"></span> |
|
</div> |
|
|
|
<div class="flex space-x-4"> |
|
|
|
<div class="relative flex-grow"> |
|
<input type="text" id="messageInput" |
|
class="w-full bg-gray-800 text-white rounded-full px-12 py-3 pl-4 focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200" |
|
placeholder="Montrez-"> |
|
<button class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-300"> |
|
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"></path></svg> |
|
</button> |
|
</div> |
|
|
|
<button onclick="sendMessage()" class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-6 rounded-full transition duration-200 ease-in-out"> |
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="md:hidden fixed bottom-0 left-0 right-0 bg-gray-800 p-2 flex justify-around"> |
|
<button class="text-gray-400 hover:text-white"> |
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path></svg> |
|
</button> |
|
<button class="text-gray-400 hover:text-white"> |
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><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"></path></svg> |
|
</button> |
|
<button class="text-gray-400 hover:text-white"> |
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg> |
|
</button> |
|
<button class="text-gray-400 hover:text-white"> |
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-6-3a2 2 0 11-4 0 2 2 0 014 0zm-2 4a5 5 0 00-4.546 2.916A5.986 5.986 0 0010 16a5.986 5.986 0 004.546-2.084A5 5 0 0010 11z" clip-rule="evenodd"></path></svg> |
|
</button> |
|
</div> |
|
|
|
<div id="loadingOverlay" class="fixed inset-0 bg-gray-900 bg-opacity-50 z-50 hidden flex items-center justify-center"> |
|
<div class="loader ease-linear rounded-full border-8 border-t-8 border-gray-200 h-32 w-32"></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 = `flex ${isUser ? 'justify-end' : ''}`; |
|
|
|
const innerDiv = document.createElement('div'); |
|
innerDiv.className = `message max-w-3/4 p-3 rounded-lg ${isUser ? 'bg-blue-500 text-white' : 'bg-gray-700'}`; |
|
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) { |
|
alert(`Erreur: ${data.error}`); |
|
} else { |
|
addMessage(`Fichier téléchargé: ${file.name}`); |
|
} |
|
} catch (error) { |
|
hideLoading(); |
|
alert(`Erreur: ${error.message}`); |
|
} |
|
}); |
|
|
|
async function clearChat() { |
|
try { |
|
showLoading(); |
|
await fetch('/clear_chat', { method: 'POST' }); |
|
chatMessages.innerHTML = ''; |
|
hideLoading(); |
|
fileName.textContent = ''; |
|
fileUpload.value = ''; |
|
|
|
} catch (error) { |
|
hideLoading(); |
|
alert(`Erreur: ${error.message}`); |
|
} |
|
} |
|
|
|
function showLoading() { |
|
loadingOverlay.classList.remove('hidden'); |
|
} |
|
|
|
function hideLoading() { |
|
loadingOverlay.classList.add('hidden'); |
|
} |
|
|
|
document.querySelectorAll('.quick-action-btn').forEach(button => { |
|
button.addEventListener('click', () => { |
|
const actionText = button.textContent.trim(); |
|
switch (actionText) { |
|
case 'Recherche': |
|
messageInput.value = "Effectue une recherche sur "; |
|
break; |
|
|
|
case 'Tempête de cerveau': |
|
messageInput.value = "Donne moi des idées sur "; |
|
break; |
|
|
|
case 'Analyser les données': |
|
messageInput.value = "Analyse les données suivantes: "; |
|
break; |
|
|
|
case 'Créer des images': |
|
messageInput.value = "Crée une image de "; |
|
break; |
|
|
|
case 'Code': |
|
messageInput.value = "Écris du code pour "; |
|
break; |
|
default: |
|
console.log('Action non reconnue:', actionText); |
|
} |
|
messageInput.focus(); |
|
|
|
}); |
|
}); |
|
</script> |
|
</body> |
|
</html> |