Spaces:
Sleeping
Sleeping
<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> |