Docfile commited on
Commit
d327e7b
·
verified ·
1 Parent(s): 9806663

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +153 -257
templates/index.html CHANGED
@@ -4,267 +4,87 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Mariam M-0 | Solution Mathématique</title>
7
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
8
-
9
- <!-- Optimized MathJax Configuration -->
10
- <script>
11
- window.MathJax = {
12
- tex: {
13
- inlineMath: [['$', '$']],
14
- displayMath: [['$$', '$$']],
15
- processEscapes: true,
16
- packages: ['base', 'ams']
17
- },
18
- options: {
19
- enableMenu: false,
20
- messageStyle: 'none',
21
- renderActions: {
22
- // Add a new action to handle our delayed typesetting
23
- add: [135, 'delayedTypeset', (doc) => {
24
- // Typeset the entire answerContent
25
- return MathJax.typesetPromise([document.getElementById('answerContent')]);
26
- }]
27
- }
28
- },
29
- startup: {
30
- pageReady: () => {
31
- console.log('MathJax is fully loaded and ready.');
32
- window.mathJaxReady = true; // Signal that MathJax is ready
33
- }
34
- }
35
- };
36
- </script>
37
- <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script" async></script>
38
- <script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.min.js" defer></script>
39
 
40
  <style>
41
- @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700&display=swap');
42
-
43
  body {
44
  font-family: 'Space Grotesk', sans-serif;
45
  }
46
-
47
- .uploadArea {
48
- background: #f3f4f6;
49
- border: 2px dashed #d1d5db;
50
- transition: border-color 0.2s ease;
51
- }
52
-
53
- .uploadArea:hover {
54
- border-color: #3b82f6;
55
- }
56
-
57
- .blue-button {
58
- background: #3b82f6;
59
- transition: background-color 0.2s ease;
60
- }
61
-
62
- .blue-button:hover {
63
- background: #2563eb;
64
- }
65
-
66
- .loader {
67
- width: 48px;
68
- height: 48px;
69
- border: 3px solid #3b82f6;
70
- border-bottom-color: transparent;
71
- border-radius: 50%;
72
- display: inline-block;
73
- animation: rotation 1s linear infinite;
74
- }
75
-
76
- @keyframes rotation {
77
- 0% { transform: rotate(0deg); }
78
- 100% { transform: rotate(360deg); }
79
- }
80
-
81
- .thought-box {
82
- transition: max-height 0.3s ease-out;
83
- max-height: 0;
84
- overflow: hidden;
85
- }
86
-
87
- .thought-box.open {
88
- max-height: 500px;
89
- }
90
-
91
- #thoughtsContent, #answerContent {
92
- max-height: 500px;
93
- overflow-y: auto;
94
- scroll-behavior: smooth;
95
- }
96
-
97
- .preview-image {
98
- max-width: 300px;
99
- max-height: 300px;
100
- object-fit: contain;
101
- }
102
-
103
- .timestamp {
104
- color: #3b82f6;
105
- font-size: 0.9em;
106
- margin-left: 8px;
107
- }
108
  </style>
109
-
110
  </head>
111
- <body class="p-4">
112
- <div class="max-w-4xl mx-auto">
113
- <div class="p-6">
114
- <div class="text-center mb-8">
115
- <h1 class="text-4xl font-bold text-blue-600">Mariam M-0</h1>
116
- <p class="text-gray-600">Solution Mathématique Intelligente</p>
117
- </div>
118
-
119
- <form id="problemForm" class="space-y-6">
120
- <div class="uploadArea p-8 text-center relative">
121
- <input type="file" id="imageInput" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer">
122
- <div class="space-y-3">
123
- <div class="w-16 h-16 mx-auto border-2 border-blue-400 rounded-full flex items-center justify-center">
124
- <svg class="w-8 h-8 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
125
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="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" />
126
- </svg>
127
- </div>
128
- <p class="text-gray-700 font-medium">Déposez votre image ici</p>
129
- <p class="text-gray-500 text-sm">ou cliquez pour sélectionner</p>
130
- </div>
131
- </div>
132
-
133
- <div id="imagePreview" class="hidden text-center">
134
- <img id="previewImage" class="preview-image mx-auto" alt="Prévisualisation">
135
- </div>
136
-
137
- <button type="submit" class="blue-button w-full py-3 text-white font-medium rounded-lg">
138
- Résoudre le problème
139
- </button>
140
- </form>
141
-
142
- <div id="loader" class="hidden mt-8 text-center">
143
- <span class="loader"></span>
144
- <p class="mt-4 text-gray-600">Analyse en cours...</p>
145
- </div>
146
-
147
- <div id="solution" class="hidden mt-8 space-y-6">
148
- <div class="border-t pt-4">
149
- <button id="thoughtsToggle" class="w-full flex justify-between items-center p-2">
150
- <span class="font-medium text-gray-700">Processus de Réflexion</span>
151
- <span id="timestamp" class="timestamp"></span>
152
- </button>
153
- <div id="thoughtsBox" class="thought-box">
154
- <div id="thoughtsContent" class="p-4 text-gray-600"></div>
155
- </div>
156
- </div>
157
-
158
- <div class="border-t pt-6">
159
- <h3 class="text-xl font-bold text-gray-800 mb-4">Solution</h3>
160
- <div id="answerContent" class="text-gray-700"></div>
161
- </div>
162
- </div>
163
- </div>
164
- </div>
165
-
166
- <script>
167
- document.addEventListener('DOMContentLoaded', () => {
168
- const form = document.getElementById('problemForm');
169
- const imageInput = document.getElementById('imageInput');
170
- const loader = document.getElementById('loader');
171
- const solution = document.getElementById('solution');
172
- const thoughtsContent = document.getElementById('thoughtsContent');
173
- const answerContent = document.getElementById('answerContent');
174
- const thoughtsToggle = document.getElementById('thoughtsToggle');
175
- const thoughtsBox = document.getElementById('thoughtsBox');
176
- const imagePreview = document.getElementById('imagePreview');
177
- const previewImage = document.getElementById('previewImage');
178
- const timestamp = document.getElementById('timestamp');
179
-
180
- let startTime = null;
181
- let timerInterval = null;
182
- let answerBuffer = '';
183
-
184
- const updateTimestamp = () => {
185
- if (startTime) {
186
- timestamp.textContent = `${Math.floor((Date.now() - startTime) / 1000)}s`;
187
- }
188
  };
189
 
190
  const startTimer = () => {
191
- startTime = Date.now();
192
- timerInterval = setInterval(updateTimestamp, 1000);
193
- updateTimestamp();
 
194
  };
195
 
196
  const stopTimer = () => {
197
- clearInterval(timerInterval);
198
- startTime = null;
199
- timestamp.textContent = '';
200
- };
201
-
202
- const handleFileSelect = file => {
203
- if (!file) return;
204
- const reader = new FileReader();
205
- reader.onload = e => {
206
- previewImage.src = e.target.result;
207
- imagePreview.classList.remove('hidden');
208
- };
209
- reader.readAsDataURL(file);
210
- };
211
-
212
- thoughtsToggle.addEventListener('click', () => {
213
- thoughtsBox.classList.toggle('open');
214
- });
215
-
216
- imageInput.addEventListener('change', e => handleFileSelect(e.target.files[0]));
217
-
218
- const dropZone = document.querySelector('.uploadArea');
219
- dropZone.addEventListener('dragover', e => {
220
- e.preventDefault();
221
- dropZone.classList.add('border-blue-400');
222
- });
223
-
224
- dropZone.addEventListener('dragleave', e => {
225
- e.preventDefault();
226
- dropZone.classList.remove('border-blue-400');
227
- });
228
-
229
- dropZone.addEventListener('drop', e => {
230
- e.preventDefault();
231
- dropZone.classList.remove('border-blue-400');
232
- handleFileSelect(e.dataTransfer.files[0]);
233
- });
234
-
235
- const typesetAnswerIfReady = () => {
236
- if (window.mathJaxReady) {
237
- MathJax.startup.document.elements = [document.getElementById('answerContent')];
238
- MathJax.typeset();
239
-
240
- answerContent.scrollTop = answerContent.scrollHeight;
241
- } else {
242
- // If not ready, wait a bit and try again
243
- setTimeout(typesetAnswerIfReady, 200);
244
- }
245
  };
246
 
247
- form.addEventListener('submit', async e => {
248
  e.preventDefault();
249
- const file = imageInput.files[0];
250
  if (!file) {
251
  alert('Veuillez sélectionner une image.');
252
  return;
253
  }
254
 
 
255
  startTimer();
256
- loader.classList.remove('hidden');
257
- solution.classList.add('hidden');
258
- thoughtsContent.innerHTML = '';
259
- answerContent.innerHTML = '';
260
- answerBuffer = '';
261
- thoughtsBox.classList.add('open');
262
 
263
  const formData = new FormData();
264
  formData.append('image', file);
265
 
266
  try {
267
- let currentMode = null;
268
  const response = await fetch('/solve', {
269
  method: 'POST',
270
  body: formData
@@ -273,50 +93,126 @@
273
  const reader = response.body.getReader();
274
  const decoder = new TextDecoder();
275
  let buffer = '';
 
 
 
 
 
276
 
277
- const processChunk = chunk => {
278
- buffer += decoder.decode(chunk, { stream: true });
279
  const lines = buffer.split('\n\n');
280
  buffer = lines.pop();
281
 
282
  lines.forEach(line => {
283
  if (!line.startsWith('data:')) return;
284
  const data = JSON.parse(line.slice(5));
285
-
286
  if (data.mode) {
287
  currentMode = data.mode;
288
- loader.classList.add('hidden');
289
- solution.classList.remove('hidden');
290
  }
291
 
292
  if (!data.content) return;
293
 
294
- const container = currentMode === 'thinking' ? thoughtsContent : answerContent;
295
-
296
- if (currentMode === 'answering'){
297
- container.innerHTML += marked.parse(data.content);
298
- typesetAnswerIfReady();
299
- } else {
300
- container.innerHTML += marked.parse(data.content);
301
  }
302
  });
303
- };
304
-
305
- while (true) {
306
- const { done, value } = await reader.read();
307
- if (done) break;
308
- processChunk(value);
309
  }
310
-
311
- stopTimer();
312
  } catch (error) {
313
  console.error('Erreur:', error);
314
  alert('Une erreur est survenue.');
315
- loader.classList.add('hidden');
 
316
  stopTimer();
317
  }
318
- });
319
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  </script>
321
  </body>
322
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Mariam M-0 | Solution Mathématique</title>
7
+
8
+ <!-- Tailwind CSS -->
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+
11
+ <!-- React and ReactDOM -->
12
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
13
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
14
+
15
+ <!-- Babel pour JSX -->
16
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
17
+
18
+ <!-- Lucide Icons -->
19
+ <script src="https://unpkg.com/lucide@latest"></script>
20
+
21
+ <!-- Police Space Grotesk -->
22
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700&display=swap" rel="stylesheet">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  <style>
 
 
25
  body {
26
  font-family: 'Space Grotesk', sans-serif;
27
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  </style>
 
29
  </head>
30
+ <body>
31
+ <div id="root"></div>
32
+
33
+ <script type="text/babel">
34
+ const { useState, useRef, useEffect } = React;
35
+ const { createIcon } = lucide;
36
+
37
+ function MathSolver() {
38
+ const [file, setFile] = useState(null);
39
+ const [loading, setLoading] = useState(false);
40
+ const [showSolution, setShowSolution] = useState(false);
41
+ const [previewUrl, setPreviewUrl] = useState('');
42
+ const [thoughts, setThoughts] = useState('');
43
+ const [answer, setAnswer] = useState('');
44
+ const [isThoughtsOpen, setIsThoughtsOpen] = useState(true);
45
+ const [elapsed, setElapsed] = useState(0);
46
+
47
+ const timerRef = useRef(null);
48
+ const startTimeRef = useRef(null);
49
+
50
+ const handleFileSelect = (selectedFile) => {
51
+ if (!selectedFile) return;
52
+ setFile(selectedFile);
53
+ const reader = new FileReader();
54
+ reader.onload = (e) => setPreviewUrl(e.target.result);
55
+ reader.readAsDataURL(selectedFile);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  };
57
 
58
  const startTimer = () => {
59
+ startTimeRef.current = Date.now();
60
+ timerRef.current = setInterval(() => {
61
+ setElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
62
+ }, 1000);
63
  };
64
 
65
  const stopTimer = () => {
66
+ clearInterval(timerRef.current);
67
+ startTimeRef.current = null;
68
+ setElapsed(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  };
70
 
71
+ const handleSubmit = async (e) => {
72
  e.preventDefault();
 
73
  if (!file) {
74
  alert('Veuillez sélectionner une image.');
75
  return;
76
  }
77
 
78
+ setLoading(true);
79
  startTimer();
80
+ setShowSolution(false);
81
+ setThoughts('');
82
+ setAnswer('');
 
 
 
83
 
84
  const formData = new FormData();
85
  formData.append('image', file);
86
 
87
  try {
 
88
  const response = await fetch('/solve', {
89
  method: 'POST',
90
  body: formData
 
93
  const reader = response.body.getReader();
94
  const decoder = new TextDecoder();
95
  let buffer = '';
96
+ let currentMode = null;
97
+
98
+ while (true) {
99
+ const { done, value } = await reader.read();
100
+ if (done) break;
101
 
102
+ buffer += decoder.decode(value, { stream: true });
 
103
  const lines = buffer.split('\n\n');
104
  buffer = lines.pop();
105
 
106
  lines.forEach(line => {
107
  if (!line.startsWith('data:')) return;
108
  const data = JSON.parse(line.slice(5));
109
+
110
  if (data.mode) {
111
  currentMode = data.mode;
112
+ setLoading(false);
113
+ setShowSolution(true);
114
  }
115
 
116
  if (!data.content) return;
117
 
118
+ if (currentMode === 'thinking') {
119
+ setThoughts(prev => prev + data.content);
120
+ } else if (currentMode === 'answering') {
121
+ setAnswer(prev => prev + data.content);
 
 
 
122
  }
123
  });
 
 
 
 
 
 
124
  }
 
 
125
  } catch (error) {
126
  console.error('Erreur:', error);
127
  alert('Une erreur est survenue.');
128
+ } finally {
129
+ setLoading(false);
130
  stopTimer();
131
  }
132
+ };
133
+
134
+ useEffect(() => {
135
+ return () => clearInterval(timerRef.current);
136
+ }, []);
137
+
138
+ return (
139
+ <div className="p-4">
140
+ <div className="max-w-4xl mx-auto">
141
+ <div className="p-6">
142
+ <div className="text-center mb-8">
143
+ <h1 className="text-4xl font-bold text-blue-600">Mariam M-0</h1>
144
+ <p className="text-gray-600">Solution Mathématique Intelligente</p>
145
+ </div>
146
+
147
+ <form onSubmit={handleSubmit} className="space-y-6">
148
+ <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">
149
+ <input
150
+ type="file"
151
+ onChange={(e) => handleFileSelect(e.target.files[0])}
152
+ accept="image/*"
153
+ className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
154
+ />
155
+ <div className="space-y-3">
156
+ <div className="w-16 h-16 mx-auto border-2 border-blue-400 rounded-full flex items-center justify-center">
157
+ <svg className="w-8 h-8 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
158
+ <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" />
159
+ </svg>
160
+ </div>
161
+ <p className="text-gray-700 font-medium">Déposez votre image ici</p>
162
+ <p className="text-gray-500 text-sm">ou cliquez pour sélectionner</p>
163
+ </div>
164
+ </div>
165
+
166
+ {previewUrl && (
167
+ <div className="text-center">
168
+ <img src={previewUrl} alt="Prévisualisation" className="max-w-sm mx-auto h-auto object-contain" />
169
+ </div>
170
+ )}
171
+
172
+ <button
173
+ type="submit"
174
+ className="w-full py-3 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors"
175
+ >
176
+ Résoudre le problème
177
+ </button>
178
+ </form>
179
+
180
+ {loading && (
181
+ <div className="mt-8 text-center">
182
+ <div className="w-12 h-12 border-3 border-blue-500 border-t-transparent rounded-full animate-spin mx-auto" />
183
+ <p className="mt-4 text-gray-600">Analyse en cours...</p>
184
+ </div>
185
+ )}
186
+
187
+ {showSolution && (
188
+ <div className="mt-8 space-y-6">
189
+ <div className="border-t pt-4">
190
+ <button
191
+ onClick={() => setIsThoughtsOpen(!isThoughtsOpen)}
192
+ className="w-full flex justify-between items-center p-2"
193
+ >
194
+ <span className="font-medium text-gray-700">Processus de Réflexion</span>
195
+ {elapsed > 0 && <span className="text-blue-500 text-sm">{elapsed}s</span>}
196
+ </button>
197
+ <div className={`transition-all duration-300 overflow-hidden ${isThoughtsOpen ? 'max-h-96' : 'max-h-0'}`}>
198
+ <div className="p-4 text-gray-600 overflow-y-auto">{thoughts}</div>
199
+ </div>
200
+ </div>
201
+
202
+ <div className="border-t pt-6">
203
+ <h3 className="text-xl font-bold text-gray-800 mb-4">Solution</h3>
204
+ <div className="text-gray-700 overflow-y-auto max-h-96">{answer}</div>
205
+ </div>
206
+ </div>
207
+ )}
208
+ </div>
209
+ </div>
210
+ </div>
211
+ );
212
+ }
213
+
214
+ const root = ReactDOM.createRoot(document.getElementById('root'));
215
+ root.render(<MathSolver />);
216
  </script>
217
  </body>
218
  </html>