Docfile's picture
Update templates/index.html
f779617 verified
raw
history blame
12.4 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mariam M-0 | Solution Mathématique</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- React et ReactDOM -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<!-- Babel -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- KaTeX -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js"></script>
<!-- Marked pour le Markdown -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Police Space Grotesk -->
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Space Grotesk', sans-serif;
}
.katex { font-size: 1.1em; }
.math-block { margin: 1em 0; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useRef, useEffect } = React;
// Fonction utilitaire pour rendre les formules LaTeX
const renderMath = (content, isBlock = false) => {
try {
return katex.renderToString(content, {
displayMode: isBlock,
throwOnError: false,
strict: false
});
} catch (e) {
console.error('Erreur LaTeX:', e);
return content;
}
};
// Fonction pour traiter le contenu avec Markdown et LaTeX
const processContent = (content) => {
// Configuration de marked pour gérer LaTeX
const renderer = new marked.Renderer();
// Gestion des formules en ligne
renderer.text = (text) => {
return text.replace(/\$([^\$]+)\$/g, (match, latex) => {
return renderMath(latex);
});
};
// Gestion des blocs de formules
renderer.paragraph = (text) => {
const blockMathRegex = /\$\$([\s\S]+?)\$\$/g;
text = text.replace(blockMathRegex, (match, latex) => {
return `<div class="math-block">${renderMath(latex, true)}</div>`;
});
return `<p>${text}</p>`;
};
marked.setOptions({
renderer: renderer,
breaks: true,
gfm: true
});
return marked(content);
};
function MathSolver() {
const [file, setFile] = useState(null);
const [loading, setLoading] = useState(false);
const [showSolution, setShowSolution] = useState(false);
const [previewUrl, setPreviewUrl] = useState('');
const [thoughts, setThoughts] = useState('');
const [answer, setAnswer] = useState('');
const [isThoughtsOpen, setIsThoughtsOpen] = useState(true);
const [elapsed, setElapsed] = useState(0);
const timerRef = useRef(null);
const startTimeRef = useRef(null);
const thoughtsRef = useRef(null);
const answerRef = useRef(null);
const handleFileSelect = (selectedFile) => {
if (!selectedFile) return;
setFile(selectedFile);
const reader = new FileReader();
reader.onload = (e) => setPreviewUrl(e.target.result);
reader.readAsDataURL(selectedFile);
};
const startTimer = () => {
startTimeRef.current = Date.now();
timerRef.current = setInterval(() => {
setElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
}, 1000);
};
const stopTimer = () => {
clearInterval(timerRef.current);
startTimeRef.current = null;
setElapsed(0);
};
// Mise à jour du contenu avec traitement LaTeX
useEffect(() => {
if (thoughtsRef.current) {
thoughtsRef.current.innerHTML = processContent(thoughts);
}
if (answerRef.current) {
answerRef.current.innerHTML = processContent(answer);
}
}, [thoughts, answer]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!file) {
alert('Veuillez sélectionner une image.');
return;
}
setLoading(true);
startTimer();
setShowSolution(false);
setThoughts('');
setAnswer('');
const formData = new FormData();
formData.append('image', file);
try {
const response = await fetch('/solve', {
method: 'POST',
body: formData
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let currentMode = null;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n\n');
buffer = lines.pop();
lines.forEach(line => {
if (!line.startsWith('data:')) return;
const data = JSON.parse(line.slice(5));
if (data.mode) {
currentMode = data.mode;
setLoading(false);
setShowSolution(true);
}
if (!data.content) return;
if (currentMode === 'thinking') {
setThoughts(prev => prev + data.content);
} else if (currentMode === 'answering') {
setAnswer(prev => prev + data.content);
}
});
}
} catch (error) {
console.error('Erreur:', error);
alert('Une erreur est survenue.');
} finally {
setLoading(false);
stopTimer();
}
};
useEffect(() => {
return () => clearInterval(timerRef.current);
}, []);
return (
<div className="p-4">
<div className="max-w-4xl mx-auto">
<div className="p-6">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-blue-600">Mariam M-0</h1>
<p className="text-gray-600">Solution Mathématique Intelligente</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="relative p-8 text-center bg-gray-50 border-2 border-dashed border-gray-300 hover:border-blue-500 transition-colors rounded-lg">
<input
type="file"
onChange={(e) => handleFileSelect(e.target.files[0])}
accept="image/*"
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
/>
<div className="space-y-3">
<div className="w-16 h-16 mx-auto border-2 border-blue-400 rounded-full flex items-center justify-center">
<svg className="w-8 h-8 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="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>
</div>
<p className="text-gray-700 font-medium">Déposez votre image ici</p>
<p className="text-gray-500 text-sm">ou cliquez pour sélectionner</p>
</div>
</div>
{previewUrl && (
<div className="text-center">
<img src={previewUrl} alt="Prévisualisation" className="max-w-sm mx-auto h-auto object-contain" />
</div>
)}
<button
type="submit"
className="w-full py-3 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors"
>
Résoudre le problème
</button>
</form>
{loading && (
<div className="mt-8 text-center">
<div className="w-12 h-12 border-3 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto" />
<p className="mt-4 text-gray-600">Analyse en cours...</p>
</div>
)}
{showSolution && (
<div className="mt-8 space-y-6">
<div className="border-t pt-4">
<button
onClick={() => setIsThoughtsOpen(!isThoughtsOpen)}
className="w-full flex justify-between items-center p-2"
>
<span className="font-medium text-gray-700">Processus de Réflexion</span>
{elapsed > 0 && <span className="text-blue-500 text-sm">{elapsed}s</span>}
</button>
<div className={`transition-all duration-300 overflow-hidden ${isThoughtsOpen ? 'max-h-96' : 'max-h-0'}`}>
<div ref={thoughtsRef} className="p-4 text-gray-600 overflow-y-auto" />
</div>
</div>
<div className="border-t pt-6">
<h3 className="text-xl font-bold text-gray-800 mb-4">Solution</h3>
<div ref={answerRef} className="text-gray-700 overflow-y-auto max-h-96" />
</div>
</div>
)}
</div>
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<MathSolver />);
</script>
</body>
</html>