Mariam-cards / templates /index.html
Docfile's picture
Update templates/index.html
7affd69 verified
raw
history blame
17.5 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Résolution d'exercices avec Gemini</title>
<!-- Inclusion de Tailwind CSS via CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Inclusion de MathJax pour le rendu LaTeX -->
<!-- Il est préférable de charger MathJax v3 -->
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']], // Délimiteurs pour les maths en ligne
displayMath: [['$$', '$$'], ['\\[', '\\]']], // Délimiteurs pour les maths en affichage
processEscapes: true // Traiter les échappements comme \$
},
svg: {
fontCache: 'global' // Améliore la performance du rendu SVG
}
};
</script>
<script type="text/javascript" id="MathJax-script" async
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">
</script>
<!-- Inclusion de highlight.js pour la coloration syntaxique -->
<!-- Choisir un thème (ex: github-dark) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<!-- Optionnel: Charger des langages spécifiques si nécessaire -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script> -->
<!-- Styles personnalisés additionnels si besoin (peuvent être intégrés à Tailwind config pour des projets plus gros) -->
<style>
/* Style pour s'assurer que MathJax n'affecte pas trop la hauteur de ligne */
mjx-container {
line-height: normal !important; /* Ajuster si nécessaire */
display: inline-block !important; /* Pour un meilleur alignement inline */
}
/* Amélioration visuelle pour les blocs de code */
pre code.hljs {
border-radius: 0.375rem; /* rounded-md */
padding: 1rem; /* p-4 */
}
</style>
</head>
<body class="bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-100 text-gray-800 font-sans min-h-screen flex flex-col items-center py-12 px-4">
<div class="w-full max-w-3xl">
<h1 class="text-4xl font-bold text-center mb-10 text-indigo-700">
Résolution d'exercices avec Gemini
</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
<!-- Section Mode Normal -->
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-200">
<h2 class="text-2xl font-semibold mb-5 text-indigo-600">Mode Normal (Gemini Pro)</h2>
<form id="solve-form" enctype="multipart/form-data">
<div class="mb-4">
<label for="image" class="block text-sm font-medium text-gray-700 mb-2">
Téléchargez une image de votre exercice :
</label>
<input type="file" id="image" name="image" accept="image/*" required
class="block w-full text-sm text-gray-500 border border-gray-300 rounded-lg cursor-pointer
file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0
file:text-sm file:font-semibold file:bg-indigo-100 file:text-indigo-700
hover:file:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<button type="submit"
class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-lg
focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-opacity-50
transition duration-150 ease-in-out">
Résoudre
</button>
</form>
</div>
<!-- Section Mode Rapide -->
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-200">
<h2 class="text-2xl font-semibold mb-5 text-purple-600">Mode Rapide (Gemini Flash)</h2>
<form id="solved-form" enctype="multipart/form-data">
<div class="mb-4">
<label for="image2" class="block text-sm font-medium text-gray-700 mb-2">
Téléchargez une image de votre exercice :
</label>
<input type="file" id="image2" name="image" accept="image/*" required
class="block w-full text-sm text-gray-500 border border-gray-300 rounded-lg cursor-pointer
file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0
file:text-sm file:font-semibold file:bg-purple-100 file:text-purple-700
hover:file:bg-purple-200 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500">
</div>
<button type="submit"
class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded-lg
focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-opacity-50
transition duration-150 ease-in-out">
Résoudre rapidement
</button>
</form>
</div>
</div>
<!-- Conteneur de Résultats -->
<div id="result-container" class="bg-white p-6 rounded-xl shadow-lg border border-gray-200 w-full min-h-[150px] prose max-w-none prose-indigo">
<!-- "prose" applique des styles par défaut au contenu -->
<!-- "max-w-none" empêche prose de limiter la largeur -->
<!-- "prose-indigo" adapte les couleurs de prose au thème indigo -->
<p class="text-gray-500 italic text-center">Les résultats apparaîtront ici...</p>
</div>
</div>
<!-- Inclusion du script JS personnalisé (mis à jour) -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const resultContainer = document.getElementById('result-container');
// Fonction pour créer un élément de code formaté et le colorer
function createCodeElement(code, language = null) {
const pre = document.createElement('pre');
// Note: La classe 'not-prose' peut être utile si les styles de 'prose' interfèrent
pre.className = 'my-4 p-0 bg-transparent not-prose'; // Réinitialise le padding/bg pour que hljs prenne le dessus
const codeEl = document.createElement('code');
// Ajouter la classe de langage si spécifié pour highlight.js
if (language) {
codeEl.className = `language-${language}`;
}
codeEl.textContent = code;
pre.appendChild(codeEl);
// Appliquer highlight.js à cet élément spécifique
hljs.highlightElement(codeEl);
return pre;
}
// Fonction pour créer un élément de texte/Markdown (sera stylisé par "prose")
function createMarkdownElement(text) {
const div = document.createElement('div');
div.className = 'markdown-content mb-4'; // prose gère le style général
// Simple conversion des sauts de ligne en <br> pour un rendu basique
// Une vraie lib Markdown (marked.js, markdown-it) serait mieux pour du vrai Markdown
div.innerHTML = text.replace(/\n/g, '<br>');
return div;
}
// Fonction pour créer un élément spécialisé pour le résultat de code
function createResultElement(text) {
const div = document.createElement('div');
// Styles Tailwind pour un résultat distinct
div.className = 'code-result my-4 p-3 bg-green-50 border-l-4 border-green-500 text-sm text-green-800 rounded-r-lg';
div.innerHTML = text.replace(/\n/g, '<br>');
return div;
}
// Fonction pour créer un élément image
function createImageElement(base64Data, format = 'png') {
const img = document.createElement('img');
img.src = `data:image/${format};base64,${base64Data}`;
// Styles Tailwind pour les images
img.className = 'max-w-full h-auto my-4 border border-gray-300 rounded p-1 shadow-sm mx-auto block'; // Centrer l'image
return img;
}
// Fonction pour créer un indicateur (chargement, réflexion)
function createIndicator(text, type = 'loading') {
const div = document.createElement('div');
div.dataset.indicatorType = type; // Pour pouvoir le retrouver/supprimer
// Styles Tailwind pour les indicateurs
div.className = 'indicator my-3 text-center italic text-gray-500';
div.textContent = text;
return div;
}
// Fonction pour créer un message d'erreur
function createErrorElement(text) {
const div = document.createElement('div');
// Styles Tailwind pour les erreurs
div.className = 'error-message my-4 p-4 bg-red-100 border border-red-300 text-red-700 rounded-lg';
div.textContent = 'Erreur: ' + text;
return div;
}
// Fonction pour traiter les données SSE et mettre à jour l'UI
function processSseData(jsonData) {
if (jsonData.mode === 'thinking') {
// Supprimer l'indicateur de chargement s'il existe
const loadingIndicator = resultContainer.querySelector('[data-indicator-type="loading"]');
if (loadingIndicator) loadingIndicator.remove();
// Afficher l'indicateur de réflexion (s'il n'existe pas déjà)
if (!resultContainer.querySelector('[data-indicator-type="thinking"]')) {
resultContainer.appendChild(createIndicator('Gemini réfléchit...', 'thinking'));
}
} else if (jsonData.mode === 'answering') {
// Supprimer l'indicateur de réflexion s'il existe
const thinkingIndicator = resultContainer.querySelector('[data-indicator-type="thinking"]');
if (thinkingIndicator) thinkingIndicator.remove();
}
if (jsonData.content) {
let element;
switch(jsonData.type) {
case 'text':
element = createMarkdownElement(jsonData.content);
break;
case 'code':
// Essayez de détecter le langage si possible (sinon hljs tente de deviner)
// Vous pourriez passer le langage depuis le backend si connu
element = createCodeElement(jsonData.content);
break;
case 'result':
element = createResultElement(jsonData.content);
break;
case 'image':
element = createImageElement(jsonData.content);
break;
default: // Traiter comme du texte par défaut
element = createMarkdownElement(jsonData.content);
}
resultContainer.appendChild(element);
// Demander à MathJax de re-scanner le conteneur pour le nouveau contenu LaTeX
// Utilisation de la nouvelle API MathJax 3
if (typeof MathJax !== 'undefined' && MathJax.typesetPromise) {
MathJax.typesetPromise([element]).catch((err) => console.error('MathJax processing error:', err));
}
}
if (jsonData.error) {
resultContainer.appendChild(createErrorElement(jsonData.error));
// Supprimer les indicateurs en cas d'erreur
resultContainer.querySelectorAll('.indicator').forEach(el => el.remove());
}
}
// Fonction pour gérer les événements SSE via Fetch API
async function setupFetchStream(url, formData) {
// Vider le conteneur de résultats et afficher chargement
resultContainer.innerHTML = '';
resultContainer.appendChild(createIndicator('Chargement en cours...', 'loading'));
try {
const response = await fetch(url, {
method: 'POST',
body: formData
// Pas besoin de 'Content-Type': 'multipart/form-data', le navigateur le met avec FormData
});
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`);
}
// Vider à nouveau au cas où la requête prend du temps avant que le stream commence
resultContainer.innerHTML = '';
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = ''; // Pour gérer les messages SSE coupés entre les chunks
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Traiter les messages complets dans le buffer
let boundary = buffer.indexOf('\n\n');
while (boundary !== -1) {
const message = buffer.substring(0, boundary);
buffer = buffer.substring(boundary + 2); // +2 pour \n\n
if (message.startsWith('data: ')) {
try {
const jsonData = JSON.parse(message.substring(6));
processSseData(jsonData);
} catch (e) {
console.error('Erreur parsing JSON du SSE:', e, 'Data:', message.substring(6));
}
}
// Rechercher la prochaine limite
boundary = buffer.indexOf('\n\n');
}
// Faire défiler vers le bas
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
}
// Traiter ce qui reste dans le buffer (au cas où le stream se termine sans \n\n final)
if (buffer.startsWith('data: ')) {
try {
const jsonData = JSON.parse(buffer.substring(6));
processSseData(jsonData);
} catch (e) {
console.error('Erreur parsing JSON du dernier chunk SSE:', e, 'Data:', buffer.substring(6));
}
}
} catch (error) {
console.error('Erreur Fetch Stream:', error);
resultContainer.innerHTML = ''; // Nettoyer les indicateurs
resultContainer.appendChild(createErrorElement(error.message));
} finally {
// Optionnel: Supprimer l'indicateur de chargement final s'il est toujours là
const loadingIndicator = resultContainer.querySelector('[data-indicator-type="loading"]');
if (loadingIndicator) loadingIndicator.remove();
}
}
// Gestion du formulaire pour /solve
const solveForm = document.getElementById('solve-form');
if (solveForm) {
solveForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(solveForm);
setupFetchStream('/solve', formData);
});
}
// Gestion du formulaire pour /solved
const solvedForm = document.getElementById('solved-form');
if (solvedForm) {
solvedForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(solvedForm);
setupFetchStream('/solved', formData);
});
}
});
</script>
</body>
</html>