Spaces:
Sleeping
Sleeping
<html lang="fr"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Question Générale</title> | |
<!-- Style de secours pour masquer les éléments dès le début --> | |
<style> | |
.hidden { display: none ; } | |
</style> | |
<!-- Tailwind CSS --> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<!-- Marked (Markdown) --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js" defer></script> | |
<!-- Font Awesome --> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" /> | |
<!-- SweetAlert2 --> | |
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11" defer></script> | |
<!-- Polyfill --> | |
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6" defer></script> | |
<!-- Anime.js --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js" defer></script> | |
<!-- Configuration de MathJax --> | |
<script> | |
window.MathJax = { | |
tex: { | |
inlineMath: [['$', '$'], ['\\(', '\\)']], | |
displayMath: [['$$', '$$'], ['\\[', '\\]']], | |
processEscapes: true | |
}, | |
options: { | |
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre'] | |
}, | |
startup: { | |
pageReady: () => { | |
return MathJax.startup.defaultPageReady().then(() => { | |
console.log('MathJax initial typesetting complete'); | |
}); | |
} | |
} | |
}; | |
</script> | |
<!-- MathJax --> | |
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" defer></script> | |
<style> | |
:root { | |
--primary-color: #008cba; | |
--background-color: #f0f8ff; | |
--animation-timing: 0.3s; | |
} | |
body { | |
margin: 0; | |
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; | |
background-color: var(--background-color); | |
min-height: 100vh; | |
padding: 20px; | |
box-sizing: border-box; | |
overflow-x: hidden; | |
} | |
.navbar { | |
background-color: var(--primary-color); | |
color: white; | |
padding: 15px; | |
width: 100%; | |
text-align: center; | |
font-size: 24px; | |
font-weight: bold; | |
position: fixed; | |
top: 0; | |
left: 0; | |
z-index: 1000; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
animation: slideDown 0.5s ease-out; | |
} | |
.container { | |
margin-top: 80px; | |
max-width: 800px; | |
background-color: white; | |
border-radius: 8px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
padding: 20px; | |
animation: fadeInScale 0.5s forwards; | |
} | |
.button { | |
background-color: var(--primary-color); | |
color: white; | |
padding: 15px; | |
border: none; | |
border-radius: 8px; | |
font-size: 16px; | |
text-align: center; | |
cursor: pointer; | |
position: relative; | |
overflow: hidden; | |
transform: translateZ(0); | |
will-change: transform; | |
transition: all var(--animation-timing) ease; | |
} | |
.button:hover { | |
transform: scale(1.05) translateY(-2px); | |
box-shadow: 0 10px 20px rgba(0,0,0,0.2); | |
} | |
.button i { | |
margin-right: 10px; | |
transition: transform var(--animation-timing) ease; | |
} | |
.input-field { | |
width: 100%; | |
padding: 12px; | |
border: 2px solid var(--primary-color); | |
border-radius: 8px; | |
margin-bottom: 15px; | |
transition: all var(--animation-timing) ease; | |
} | |
.input-field:focus { | |
outline: none; | |
box-shadow: 0 0 0 3px rgba(0,140,186,0.2); | |
} | |
#response { | |
background-color: #f8f9fa; | |
border-radius: 8px; | |
padding: 15px; | |
margin-top: 20px; | |
animation: fadeIn 0.5s ease-out; | |
} | |
#loader { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
margin: 20px 0; | |
} | |
@keyframes fadeInScale { | |
0% { | |
opacity: 0; | |
transform: scale(0.5); | |
} | |
100% { | |
opacity: 1; | |
transform: scale(1); | |
} | |
} | |
@keyframes slideDown { | |
from { transform: translateY(-100%); } | |
to { transform: translateY(0); } | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
.url-item { | |
background-color: #f0f8ff; | |
padding: 10px; | |
border-radius: 6px; | |
margin-bottom: 8px; | |
display: flex; | |
align-items: center; | |
animation: fadeIn 0.3s ease-out; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="navbar">Mariam AI</div> | |
<div class="container mx-auto"> | |
<!-- En-tête --> | |
<div class="flex flex-col md:flex-row md:justify-between items-start md:items-center mb-6 space-y-4 md:space-y-0"> | |
<h1 class="text-3xl font-bold text-gray-800">Poser une question générale</h1> | |
<button onclick="showInfo()" class="button"> | |
<i class="fas fa-info-circle"></i> Info | |
</button> | |
</div> | |
<!-- Zone de question --> | |
<div class="mb-6"> | |
<label for="questionInput" class="block mb-2 text-lg font-medium text-gray-700">Votre question :</label> | |
<textarea id="questionInput" class="input-field" rows="4" placeholder="Entrez votre question ici..."></textarea> | |
</div> | |
<!-- Zone d'ajout d'URLs --> | |
<div class="mb-6"> | |
<label for="urlInput" class="block mb-2 text-lg font-medium text-gray-700">URLs (optionnel) :</label> | |
<div class="flex"> | |
<input type="text" id="urlInput" class="input-field" placeholder="Entrez une URL" /> | |
<button onclick="addUrl()" class="button ml-2"> | |
<i class="fas fa-plus"></i> Ajouter | |
</button> | |
</div> | |
<div id="urlList" class="mt-2 space-y-2"></div> | |
</div> | |
<!-- Zone d'ajout de fichiers --> | |
<div class="mb-6"> | |
<label for="fileUpload" class="block mb-2 text-lg font-medium text-gray-700">Fichiers (optionnel) :</label> | |
<input type="file" id="fileUpload" class="input-field" multiple /> | |
</div> | |
<!-- Bouton de soumission --> | |
<button onclick="submitQuestion()" class="button w-full"> | |
<i class="fas fa-paper-plane"></i> Soumettre | |
</button> | |
<!-- Loader --> | |
<div id="loader" class="hidden"> | |
<div class="animate-spin rounded-full h-12 w-12 border-t-4 border-b-4 border-blue-500"></div> | |
<p class="ml-2 text-gray-600">Chargement en cours...</p> | |
</div> | |
<!-- Affichage de la réponse --> | |
<div id="response" class="hidden"></div> | |
<!-- Bouton pour copier la réponse --> | |
<div id="copyResponseContainer" class="hidden mt-4"> | |
<button onclick="copyResponse()" class="button w-full"> | |
<i class="fas fa-copy"></i> Copier la réponse | |
</button> | |
</div> | |
</div> | |
<script> | |
function showInfo() { | |
Swal.fire({ | |
title: 'Information', | |
html: ` | |
<p class="mb-2">Ce formulaire vous permet de poser des questions générales.</p> | |
<p class="mb-2">Vous pouvez également ajouter des URLs et des fichiers pour fournir plus de contexte à votre question.</p> | |
<p class="mb-2">La réponse sera formatée en Markdown et peut inclure des équations LaTeX.</p> | |
<p>Si vous souhaitez résoudre vos exercices de mathématiques ici, signalez-le à Mariam pour une réponse en LaTeX.</p> | |
`, | |
icon: 'info', | |
confirmButtonText: 'Compris' | |
}); | |
} | |
function addUrl() { | |
const urlInput = document.getElementById('urlInput'); | |
const urlList = document.getElementById('urlList'); | |
const url = urlInput.value.trim(); | |
if (url) { | |
const urlItem = document.createElement('div'); | |
urlItem.className = 'url-item flex items-center bg-gray-200 p-2 rounded-lg shadow-sm'; | |
urlItem.innerHTML = ` | |
<span class="flex-grow truncate">${url}</span> | |
<button onclick="removeUrl(this)" class="ml-2 text-red-500 hover:text-red-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
`; | |
urlList.appendChild(urlItem); | |
urlInput.value = ''; | |
} | |
} | |
function removeUrl(button) { | |
const urlItem = button.closest('.url-item'); | |
if(urlItem) urlItem.remove(); | |
} | |
async function submitQuestion() { | |
const question = document.getElementById('questionInput').value.trim(); | |
if (!question) return; | |
const loader = document.getElementById('loader'); | |
const responseDiv = document.getElementById('response'); | |
const copyResponseContainer = document.getElementById('copyResponseContainer'); | |
// Afficher le loader et réinitialiser la zone de réponse | |
loader.classList.remove('hidden'); | |
responseDiv.classList.remove('hidden'); | |
responseDiv.innerHTML = ''; | |
responseDiv.classList.add('opacity-0'); | |
copyResponseContainer.classList.add('hidden'); | |
const formData = new FormData(); | |
formData.append('question', question); | |
// Récupération des URLs | |
document.querySelectorAll('#urlList .url-item').forEach(item => { | |
formData.append('urls', item.querySelector('span').textContent); | |
}); | |
// Récupération des fichiers | |
Array.from(document.getElementById('fileUpload').files).forEach(file => { | |
formData.append('files', file); | |
}); | |
try { | |
const response = await fetch('/submit', { | |
method: 'POST', | |
body: formData | |
}); | |
const data = await response.json(); | |
loader.classList.add('hidden'); | |
responseDiv.classList.remove('opacity-0'); | |
if (data.error) { | |
responseDiv.innerHTML = `<p class="text-red-600">Erreur : ${data.error}</p>`; | |
} else { | |
const htmlContent = marked.parse(data.response); | |
responseDiv.innerHTML = htmlContent; | |
copyResponseContainer.classList.remove('hidden'); | |
// Rendu de LaTeX avec MathJax | |
await MathJax.typesetPromise([responseDiv]); | |
console.log('LaTeX rendu avec succès'); | |
} | |
} catch (error) { | |
loader.classList.add('hidden'); | |
responseDiv.innerHTML = `<p class="text-red-600">Erreur : ${error.message}</p>`; | |
responseDiv.classList.remove('opacity-0'); | |
} | |
} | |
function copyResponse() { | |
const responseDiv = document.getElementById('response'); | |
const text = responseDiv.innerText; | |
if (navigator.clipboard && navigator.clipboard.writeText) { | |
navigator.clipboard.writeText(text) | |
.then(() => { | |
Swal.fire({ | |
icon: 'success', | |
title: 'Copié !', | |
text: 'La réponse a été copiée dans le presse-papiers.', | |
showConfirmButton: false, | |
timer: 1500 | |
}); | |
}) | |
.catch(err => { | |
console.error('Erreur lors de la copie avec Clipboard API:', err); | |
fallbackCopyTextToClipboard(text); | |
}); | |
} else { | |
fallbackCopyTextToClipboard(text); | |
} | |
} | |
function fallbackCopyTextToClipboard(text) { | |
const textArea = document.createElement("textarea"); | |
textArea.value = text; | |
// Éviter le scroll et positionner en dehors de l'écran | |
textArea.style.top = "0"; | |
textArea.style.left = "0"; | |
textArea.style.position = "fixed"; | |
document.body.appendChild(textArea); | |
textArea.focus(); | |
textArea.select(); | |
try { | |
const successful = document.execCommand('copy'); | |
if (successful) { | |
Swal.fire({ | |
icon: 'success', | |
title: 'Copié !', | |
text: 'La réponse a été copiée dans le presse-papiers.', | |
showConfirmButton: false, | |
timer: 1500 | |
}); | |
} else { | |
console.error('Erreur lors de la copie avec execCommand'); | |
} | |
} catch (err) { | |
console.error('Erreur lors de la copie:', err); | |
} | |
document.body.removeChild(textArea); | |
} | |
</script> | |
</body> | |
</html> | |