Docfile commited on
Commit
87528a3
·
verified ·
1 Parent(s): 699b387

Delete templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +0 -710
templates/index.html DELETED
@@ -1,710 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="fr">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Mariam M-1 | Solution Math/Physique/Chimie</title>
7
-
8
- <script src="https://cdn.tailwindcss.com"></script>
9
-
10
- <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
11
-
12
- <style>
13
- @font-face {
14
- font-family: 'LucideIcons';
15
- src: url(https://cdn.jsdelivr.net/npm/lucide-static@latest/font/Lucide.ttf) format('truetype');
16
- }
17
- .lucide {
18
- font-family: 'LucideIcons';
19
- font-size: 1.25rem; /* Adjust size as needed */
20
- line-height: 1;
21
- font-style: normal;
22
- font-weight: normal;
23
- font-variant: normal;
24
- text-transform: none;
25
- word-wrap: normal;
26
- speak: none;
27
- -webkit-font-smoothing: antialiased;
28
- -moz-osx-font-smoothing: grayscale;
29
- }
30
- </style>
31
-
32
- <script>
33
- window.MathJax = {
34
- tex: {
35
- inlineMath: [['$', '$']],
36
- displayMath: [['$$', '$$']],
37
- processEscapes: true,
38
- packages: {'[+]': ['ams', 'mhchem']}
39
- },
40
- chtml: {
41
- matchFontHeight: true,
42
- displayAlign: 'left',
43
- scale: 1,
44
- mtextInheritFont: true,
45
- merrorInheritFont: true,
46
- mathmlSpacing: false,
47
- skipAttributes: {},
48
- exFactor: .5,
49
- displayIndent: '0'
50
- },
51
- options: {
52
- enableMenu: false,
53
- ignoreHtmlClass: 'tex2jax_ignore',
54
- processHtmlClass: 'tex2jax_process',
55
- renderActions: { addMenu: [0, '', ''] },
56
- menuOptions: { settings: { zoom: 'None', renderer: 'CHTML' } }
57
- },
58
- startup: {
59
- ready: () => {
60
- console.log('MathJax is ready to go!');
61
- MathJax.startup.defaultReady();
62
- window.mathJaxReady = true;
63
- }
64
- }
65
- };
66
- </script>
67
- <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script" async></script>
68
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/marked.umd.min.js"></script>
69
-
70
- <style>
71
- /* Use Inter font */
72
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap');
73
- body {
74
- font-family: 'Inter', sans-serif;
75
- background-color: #f8fafc; /* Light gray background */
76
- }
77
-
78
- /* Improved Upload Area Styling */
79
- .uploadArea {
80
- background: #ffffff;
81
- border: 2px dashed #cbd5e1;
82
- transition: border-color 0.3s ease, background-color 0.3s ease;
83
- border-radius: 0.75rem;
84
- }
85
- .uploadArea:hover {
86
- border-color: #3b82f6;
87
- background-color: #eff6ff;
88
- }
89
-
90
- /* Consistent Button Styling */
91
- .blue-button {
92
- background-color: #3b82f6;
93
- color: white;
94
- padding: 0.75rem 1.5rem;
95
- border-radius: 0.5rem;
96
- font-weight: 500;
97
- transition: background-color 0.2s ease, transform 0.1s ease, opacity 0.2s ease;
98
- box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
99
- }
100
- .blue-button:hover:not(:disabled) {
101
- background-color: #2563eb;
102
- transform: translateY(-1px);
103
- }
104
- .blue-button:active:not(:disabled) {
105
- transform: translateY(0px);
106
- }
107
- .blue-button:disabled {
108
- opacity: 0.5;
109
- cursor: not-allowed;
110
- }
111
-
112
- /* Save Button Styling (in header) */
113
- #saveButton {
114
- background-color: #10b981; /* Green */
115
- color: white;
116
- padding: 0.5rem 1rem; /* Smaller padding for header button */
117
- border-radius: 0.375rem;
118
- transition: background-color 0.2s ease, opacity 0.2s ease;
119
- font-weight: 500;
120
- font-size: 0.875rem; /* text-sm */
121
- }
122
- #saveButton:hover:not(:disabled) {
123
- background-color: #059669; /* Darker green */
124
- }
125
- #saveButton:disabled {
126
- opacity: 0.4; /* More pronounced disabled state */
127
- cursor: not-allowed;
128
- background-color: #6ee7b7; /* Lighter green when disabled */
129
- }
130
-
131
-
132
- /* Loader Styling */
133
- .loader {
134
- width: 40px;
135
- height: 40px;
136
- border: 4px solid #3b82f6;
137
- border-bottom-color: transparent;
138
- border-radius: 50%;
139
- display: inline-block;
140
- animation: rotation 1s linear infinite;
141
- }
142
- @keyframes rotation {
143
- 0% { transform: rotate(0deg); }
144
- 100% { transform: rotate(360deg); }
145
- }
146
-
147
- /* Thought Box Animation */
148
- .thought-box {
149
- transition: max-height 0.5s ease-in-out, opacity 0.5s ease-in-out;
150
- max-height: 0;
151
- opacity: 0;
152
- overflow: hidden;
153
- border-top: 1px solid #e5e7eb;
154
- margin-top: 0.5rem;
155
- }
156
- .thought-box.open {
157
- max-height: 600px;
158
- opacity: 1;
159
- }
160
-
161
- /* Content Area Styling */
162
- #thoughtsContent {
163
- max-height: 600px;
164
- overflow-y: auto;
165
- scroll-behavior: smooth;
166
- padding: 1rem;
167
- background-color: #f9fafb;
168
- border-radius: 0.375rem;
169
- line-height: 1.6;
170
- color: #374151;
171
- word-wrap: break-word;
172
- font-size: 0.875rem; /* sm */
173
- }
174
- @media (min-width: 768px) {
175
- #thoughtsContent { font-size: 1rem; /* base */ }
176
- }
177
-
178
- /* Enhanced Answer Content Styling */
179
- #answerContent {
180
- max-height: none;
181
- overflow-y: visible;
182
- scroll-behavior: smooth;
183
- padding: 1rem; /* Base padding */
184
- background-color: #ffffff;
185
- color: #1f2937;
186
- word-wrap: break-word;
187
- font-size: 1rem; /* text-base */
188
- line-height: 1.7; /* leading-relaxed */
189
- }
190
- @media (min-width: 768px) {
191
- #answerContent {
192
- font-size: 1.125rem; /* text-lg */
193
- padding: 1.5rem; /* More padding on larger screens */
194
- }
195
- }
196
- #answerContent pre {
197
- background-color: #f3f4f6;
198
- padding: 1rem;
199
- border-radius: 0.375rem;
200
- overflow-x: auto;
201
- margin-top: 0.5rem;
202
- margin-bottom: 1rem;
203
- font-size: 0.9em;
204
- }
205
- #answerContent code {
206
- font-family: 'Courier New', Courier, monospace;
207
- }
208
- #answerContent img {
209
- max-width: 100%;
210
- height: auto;
211
- margin-top: 0.75rem;
212
- margin-bottom: 1rem;
213
- border-radius: 0.375rem;
214
- border: 1px solid #e5e7eb;
215
- }
216
- #answerContent > *:last-child {
217
- margin-bottom: 0; /* Remove bottom margin from last element */
218
- }
219
-
220
- /* MathJax Container Styling */
221
- #answerContent mjx-container {
222
- overflow-x: auto;
223
- overflow-y: hidden;
224
- padding-bottom: 8px;
225
- margin-bottom: 1rem;
226
- scrollbar-width: thin;
227
- scrollbar-color: #a0aec0 #edf2f7;
228
- }
229
- #answerContent mjx-container::-webkit-scrollbar { height: 6px; }
230
- #answerContent mjx-container::-webkit-scrollbar-track { background: #edf2f7; border-radius: 10px; }
231
- #answerContent mjx-container::-webkit-scrollbar-thumb { background-color: #a0aec0; border-radius: 10px; border: 1px solid #edf2f7; }
232
-
233
- /* Image Preview */
234
- .preview-image {
235
- max-width: 100%;
236
- max-height: 300px;
237
- object-fit: contain;
238
- border-radius: 0.5rem;
239
- margin-top: 1rem;
240
- }
241
-
242
- /* Timestamp Styling */
243
- .timestamp { color: #6b7280; font-size: 0.8rem; margin-left: 8px; }
244
-
245
- /* Table Styling */
246
- .table-responsive { overflow-x: auto; width: 100%; margin-bottom: 1rem; }
247
- table { border-collapse: collapse; width: 100%; min-width: 400px; }
248
- th, td { border: 1px solid #e5e7eb; padding: 0.75rem; text-align: left; vertical-align: top; }
249
- th { background-color: #f3f4f6; font-weight: 600; color: #1f2937; }
250
- tbody tr:nth-child(odd) { background-color: #ffffff; }
251
- tbody tr:nth-child(even) { background-color: #f9fafb; }
252
-
253
- /* Removed .save-button-container styles */
254
-
255
- /* Fullscreen Modal Styling */
256
- #savedModal { display: none; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); z-index: 50; opacity: 0; transition: opacity 0.3s ease; }
257
- #savedModal.active { display: flex; align-items: center; justify-content: center; opacity: 1; }
258
- #savedModalContent { background: #fff; width: 90%; max-width: 600px; max-height: 90vh; border-radius: 0.75rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); display: flex; flex-direction: column; overflow: hidden; }
259
- #savedModal header { padding: 1rem 1.5rem; border-bottom: 1px solid #e5e7eb; }
260
- #savedListContainer { padding: 1rem 1.5rem; overflow-y: auto; flex-grow: 1; }
261
- #savedModal footer { padding: 1rem 1.5rem; border-top: 1px solid #e5e7eb; background-color: #f9fafb; }
262
- #closeSaved { background: none; border: none; font-size: 1.75rem; line-height: 1; color: #6b7280; cursor: pointer; transition: color 0.2s ease; }
263
- #closeSaved:hover { color: #1f2937; }
264
- #savedList li button { padding: 0.5rem 0; border-radius: 0.25rem; transition: background-color 0.2s ease; display: block; width: 100%; }
265
- #savedList li button:hover { background-color: #eff6ff; }
266
- </style>
267
- </head>
268
- <body class="p-4 md:p-6">
269
- <div class="max-w-4xl mx-auto bg-white shadow-lg rounded-lg overflow-hidden">
270
- <header class="p-5 md:p-6 border-b border-gray-200 relative">
271
- <div class="flex flex-col sm:flex-row justify-between items-center">
272
- <div class="text-center sm:text-left mb-4 sm:mb-0">
273
- <h1 class="text-3xl md:text-4xl font-bold text-blue-600">Mariam M-1</h1>
274
- <p class="text-gray-500 mt-1 text-sm sm:text-base">Solution Math / Physique / Chimie Intelligente</p>
275
- </div>
276
- <div class="flex items-center space-x-2">
277
- <button id="saveButton" disabled>Sauvegarder</button>
278
- <button id="openSaved" class="blue-button px-3 py-1.5 text-sm">
279
- Sauvegardes
280
- </button>
281
- </div>
282
- </div>
283
- </header>
284
-
285
- <main id="mainContent" class="p-5 md:p-8">
286
- <form id="problemForm" class="space-y-6" novalidate>
287
- <div class="uploadArea p-6 md:p-8 text-center relative cursor-pointer" aria-label="Zone de dépôt d'image">
288
- <input type="file" id="imageInput" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" aria-label="Choisir une image">
289
- <div class="space-y-3 pointer-events-none">
290
- <div class="w-16 h-16 mx-auto border-2 border-blue-300 rounded-full flex items-center justify-center bg-blue-50">
291
- <span class="lucide text-blue-500" aria-hidden="true">&#xe92a;</span> </div>
292
- <p class="text-gray-700 font-medium">Déposez votre image ici</p>
293
- <p class="text-gray-500 text-sm">Images uniquement (.jpg, .png, .gif, etc.)</p>
294
- </div>
295
- </div>
296
- <div id="imagePreview" class="hidden text-center">
297
- <img id="previewImage" class="preview-image mx-auto border border-gray-200" alt="Prévisualisation de l'image">
298
- <p id="fileName" class="text-sm text-gray-600 mt-2"></p>
299
- </div>
300
- <button type="submit" class="blue-button w-full py-3 text-base">
301
- Résoudre le problème
302
- </button>
303
- </form>
304
-
305
- <div id="loader" class="hidden mt-8 text-center">
306
- <span class="loader"></span>
307
- <p class="mt-4 text-gray-600">Analyse en cours...</p>
308
- </div>
309
-
310
- <section id="solution" class="hidden mt-8 space-y-6">
311
- <div class="border rounded-lg overflow-hidden bg-gray-50">
312
- <button id="thoughtsToggle" type="button" class="w-full flex justify-between items-center p-3 hover:bg-gray-100 transition duration-150 ease-in-out" aria-expanded="true">
313
- <span class="font-medium text-gray-700">Processus de Réflexion</span>
314
- <div class="flex items-center">
315
- <span id="timestamp" class="timestamp"></span>
316
- <svg id="toggleIcon" class="w-5 h-5 text-gray-500 ml-2 transform transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
317
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
318
- </svg>
319
- </div>
320
- </button>
321
- <div id="thoughtsBox" class="thought-box open">
322
- <div id="thoughtsContent"></div>
323
- </div>
324
- </div>
325
-
326
- <div class="border rounded-lg shadow-sm overflow-hidden bg-white">
327
- <div class="p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-blue-100">
328
- <h3 class="text-xl font-semibold text-gray-800">Solution Détaillée</h3>
329
- </div>
330
- <div id="answerContent" class="table-responsive">
331
- </div>
332
- </div>
333
- </section>
334
- </main>
335
- </div>
336
-
337
- <div id="savedModal">
338
- <div id="savedModalContent">
339
- <header class="flex justify-between items-center">
340
- <h2 class="text-xl font-semibold text-gray-800">Solutions Sauvegardées</h2>
341
- <button id="closeSaved" aria-label="Fermer">&times;</button>
342
- </header>
343
- <div id="savedListContainer">
344
- <ul id="savedList" class="space-y-2">
345
- <li class="text-center text-gray-500 italic">Aucune sauvegarde trouvée.</li>
346
- </ul>
347
- </div>
348
- <footer class="text-center">
349
- <button id="newExercise" class="blue-button w-full py-2.5 text-base">
350
- Résoudre un nouvel exercice
351
- </button>
352
- </footer>
353
- </div>
354
- </div>
355
-
356
- <script>
357
- document.addEventListener('DOMContentLoaded', () => {
358
- // --- DOM Element References ---
359
- const form = document.getElementById('problemForm');
360
- const imageInput = document.getElementById('imageInput');
361
- const loader = document.getElementById('loader');
362
- const solutionSection = document.getElementById('solution');
363
- const thoughtsContent = document.getElementById('thoughtsContent');
364
- const answerContent = document.getElementById('answerContent');
365
- const thoughtsToggle = document.getElementById('thoughtsToggle');
366
- const thoughtsBox = document.getElementById('thoughtsBox');
367
- const toggleIcon = document.getElementById('toggleIcon');
368
- const imagePreview = document.getElementById('imagePreview');
369
- const previewImage = document.getElementById('previewImage');
370
- const fileNameDisplay = document.getElementById('fileName');
371
- const timestamp = document.getElementById('timestamp');
372
- const saveButton = document.getElementById('saveButton'); // Reference remains valid
373
- const openSaved = document.getElementById('openSaved');
374
- const closeSaved = document.getElementById('closeSaved');
375
- const savedModal = document.getElementById('savedModal');
376
- const savedList = document.getElementById('savedList');
377
- const newExercise = document.getElementById('newExercise');
378
- const mainContent = document.getElementById('mainContent');
379
- const dropZone = document.querySelector('.uploadArea');
380
-
381
- // --- State Variables ---
382
- let startTime = null;
383
- let timerInterval = null;
384
- let thoughtsBuffer = '';
385
- let answerBuffer = '';
386
- let currentMode = null;
387
- let updateTimeout = null;
388
- let mathJaxProcessing = false;
389
-
390
- // --- Timer Functions ---
391
- const updateTimestamp = () => { if (startTime) timestamp.textContent = `${Math.floor((Date.now() - startTime) / 1000)}s`; };
392
- const startTimer = () => { startTime = Date.now(); timerInterval = setInterval(updateTimestamp, 1000); updateTimestamp(); };
393
- const stopTimer = () => { clearInterval(timerInterval); };
394
-
395
- // --- File Handling & Preview (Image Only) ---
396
- const handleFileSelect = file => {
397
- // Clear previous preview and name
398
- previewImage.src = '';
399
- fileNameDisplay.textContent = '';
400
- imagePreview.classList.add('hidden');
401
- imageInput.value = ''; // Clear input value initially
402
-
403
- if (!file) return;
404
-
405
- // **Validation: Check if it's an image**
406
- if (!file.type.startsWith('image/')) {
407
- showError('Fichier non valide. Veuillez sélectionner une image (jpg, png, gif...).');
408
- return; // Stop processing if not an image
409
- }
410
-
411
- // If valid image, proceed with preview
412
- fileNameDisplay.textContent = file.name;
413
- const reader = new FileReader();
414
- reader.onload = e => {
415
- previewImage.src = e.target.result;
416
- imagePreview.classList.remove('hidden');
417
- // Re-assign the valid file to the input (needed if cleared earlier)
418
- // This is tricky, usually done via DataTransfer, but simpler to just let the original selection stand if valid
419
- };
420
- reader.onerror = () => { showError('Erreur lors de la lecture de l\'image.'); };
421
- reader.readAsDataURL(file);
422
- };
423
-
424
- // --- UI Toggles ---
425
- thoughtsToggle.addEventListener('click', () => {
426
- const isOpen = thoughtsBox.classList.toggle('open');
427
- toggleIcon.style.transform = isOpen ? 'rotate(180deg)' : 'rotate(0deg)';
428
- thoughtsToggle.setAttribute('aria-expanded', isOpen);
429
- });
430
-
431
- // --- Drag and Drop (Image Only Validation) ---
432
- const handleDragDrop = (e) => {
433
- e.preventDefault();
434
- dropZone.classList.remove('border-blue-400', 'bg-blue-50');
435
- const file = e.dataTransfer.files[0];
436
- if (file) {
437
- // Validate type before assigning and handling
438
- if (file.type.startsWith('image/')) {
439
- // Create a new FileList to assign to input.files
440
- const dataTransfer = new DataTransfer();
441
- dataTransfer.items.add(file);
442
- imageInput.files = dataTransfer.files;
443
- handleFileSelect(file); // Handle the valid file
444
- } else {
445
- showError('Fichier non valide. Veuillez déposer une image (jpg, png, gif...).');
446
- imageInput.value = ''; // Clear input if invalid file dropped
447
- }
448
- }
449
- }
450
-
451
- dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('border-blue-400', 'bg-blue-50'); });
452
- dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('border-blue-400', 'bg-blue-50'); });
453
- dropZone.addEventListener('drop', handleDragDrop); // Use updated handler
454
- dropZone.addEventListener('click', () => { imageInput.click(); });
455
- imageInput.addEventListener('change', (e) => {
456
- // Handle selection, validation is inside handleFileSelect
457
- handleFileSelect(e.target.files[0]);
458
- });
459
-
460
-
461
- // --- MathJax Rendering ---
462
- const typesetContent = async (elementId) => {
463
- if (!window.mathJaxReady || mathJaxProcessing) return;
464
- const element = document.getElementById(elementId);
465
- if (!element || (!element.innerHTML.includes('$') && !element.innerHTML.includes('\\ce{'))) return;
466
- mathJaxProcessing = true;
467
- try {
468
- MathJax.startup.document.clear();
469
- MathJax.startup.document.elements = [element];
470
- await MathJax.typesetPromise();
471
- } catch (error) {
472
- console.error("MathJax Typesetting Error:", error);
473
- showError("Erreur lors de l'affichage des formules.");
474
- } finally {
475
- mathJaxProcessing = false;
476
- }
477
- };
478
-
479
- // --- Content Update & Display ---
480
- const updateDisplay = async () => {
481
- thoughtsContent.innerHTML = marked.parse(thoughtsBuffer);
482
- answerContent.innerHTML = marked.parse(answerBuffer);
483
- await typesetContent('answerContent');
484
- updateTimeout = null;
485
- };
486
- const scheduleUpdate = () => { if (!updateTimeout) updateTimeout = requestAnimationFrame(updateDisplay); };
487
-
488
- // Configure Marked
489
- marked.setOptions({ gfm: true, breaks: true, mangle: false, headerIds: false });
490
-
491
- // --- Form Submission (API Call) ---
492
- form.addEventListener('submit', async e => {
493
- e.preventDefault();
494
- const file = imageInput.files[0];
495
- // Double check file exists and is an image before sending
496
- if (!file || !file.type.startsWith('image/')) {
497
- showError('Veuillez sélectionner une image valide.');
498
- return;
499
- }
500
-
501
- // Reset UI state
502
- startTimer();
503
- saveButton.disabled = true; // Disable save button on new submission
504
- loader.classList.remove('hidden');
505
- solutionSection.classList.add('hidden');
506
- thoughtsContent.innerHTML = '';
507
- answerContent.innerHTML = '';
508
- thoughtsBuffer = '';
509
- answerBuffer = '';
510
- currentMode = null;
511
- if (!thoughtsBox.classList.contains('open')) thoughtsToggle.click();
512
-
513
- const formData = new FormData();
514
- formData.append('file', file); // Changed key to 'file'
515
-
516
- try {
517
- const response = await fetch('/solve', { method: 'POST', body: formData }); // Replace with your endpoint
518
-
519
- if (!response.ok) {
520
- let errorMsg = `Erreur serveur: ${response.status} ${response.statusText}`;
521
- try { errorMsg = (await response.json()).error || errorMsg; } catch (_) {}
522
- throw new Error(errorMsg);
523
- }
524
- if (!response.body) throw new Error("La réponse du serveur est vide.");
525
-
526
- const reader = response.body.getReader();
527
- const decoder = new TextDecoder();
528
- let buffer = '';
529
-
530
- // Process stream
531
- const processChunk = async ({ done, value }) => {
532
- if (done) {
533
- if (buffer) processDataLine(buffer); // Process remaining buffer
534
- scheduleUpdate(); // Final update
535
- stopTimer();
536
- loader.classList.add('hidden');
537
- // **Enable Save Button only if answer content exists**
538
- if (answerBuffer.trim() !== '') {
539
- saveButton.disabled = false;
540
- } else {
541
- saveButton.disabled = true; // Keep disabled if no answer
542
- if (!currentMode) showError("Aucune donnée valide reçue du serveur.");
543
- }
544
- return;
545
- }
546
-
547
- buffer += decoder.decode(value, { stream: true });
548
- const lines = buffer.split('\n\n');
549
- buffer = lines.pop();
550
- lines.forEach(processDataLine);
551
- scheduleUpdate();
552
- reader.read().then(processChunk);
553
- };
554
-
555
- const processDataLine = (line) => {
556
- if (!line.startsWith('data:')) return;
557
- try {
558
- const data = JSON.parse(line.substring(5).trim());
559
- if (data.mode) {
560
- currentMode = data.mode;
561
- loader.classList.add('hidden');
562
- solutionSection.classList.remove('hidden');
563
- }
564
- if (data.content) {
565
- if (currentMode === 'thinking') thoughtsBuffer += data.content;
566
- else if (currentMode === 'answering') answerBuffer += data.content;
567
- }
568
- if (data.error) {
569
- showError(`Erreur de traitement: ${data.error}`);
570
- stopTimer();
571
- saveButton.disabled = true; // Ensure disabled on error
572
- }
573
- } catch (parseError) { console.error("Erreur de parsing JSON:", parseError, "Ligne:", line); }
574
- };
575
-
576
- reader.read().then(processChunk);
577
-
578
- } catch (error) {
579
- console.error('Erreur de communication ou traitement:', error);
580
- showError(`Une erreur est survenue: ${error.message}`);
581
- loader.classList.add('hidden');
582
- stopTimer();
583
- saveButton.disabled = true; // Ensure disabled on fetch error
584
- }
585
- });
586
-
587
- // --- Save Solution ---
588
- saveButton.addEventListener('click', () => {
589
- if (saveButton.disabled) return; // Prevent saving if disabled
590
-
591
- Swal.fire({
592
- title: 'Nommer cette sauvegarde',
593
- input: 'text',
594
- inputPlaceholder: 'Ex: Physique - Circuit RLC',
595
- showCancelButton: true,
596
- confirmButtonText: 'Sauvegarder',
597
- cancelButtonText: 'Annuler',
598
- inputValidator: (value) => !value && 'Veuillez entrer un nom !'
599
- }).then((result) => {
600
- if (result.isConfirmed && result.value) {
601
- const saveName = result.value;
602
- const saveData = {
603
- answer: answerContent.innerHTML,
604
- thinking: thoughtsContent.innerHTML,
605
- date: new Date().toLocaleString('fr-FR')
606
- };
607
- let savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
608
- savedExercises[saveName] = saveData;
609
- localStorage.setItem('savedExercises', JSON.stringify(savedExercises));
610
- Swal.fire({ icon: 'success', title: 'Sauvegardé !', text: `Solution sauvegardée sous "${saveName}".`, timer: 2000, showConfirmButton: false });
611
- }
612
- });
613
- });
614
-
615
- // --- Load Saved Solutions ---
616
- const loadSavedList = () => {
617
- savedList.innerHTML = '';
618
- const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
619
- const keys = Object.keys(savedExercises);
620
- if (keys.length === 0) {
621
- savedList.innerHTML = '<li class="text-center text-gray-500 italic py-4">Aucune sauvegarde trouvée.</li>';
622
- return;
623
- }
624
- keys.sort((a, b) => a.localeCompare(b, 'fr', { sensitivity: 'base' })).forEach(name => {
625
- const data = savedExercises[name];
626
- const li = document.createElement('li');
627
- li.className = 'border-b border-gray-200 last:border-b-0';
628
- li.innerHTML = `
629
- <div class="flex justify-between items-center p-2 hover:bg-gray-50 transition-colors duration-150">
630
- <button class="text-left text-blue-600 hover:underline flex-grow mr-2 text-sm" data-save="${name}">
631
- <span class="font-medium">${name}</span> <span class="text-gray-500 text-xs block">(${data.date})</span>
632
- </button>
633
- <button class="text-red-500 hover:text-red-700 text-xs font-medium px-2 py-1 rounded hover:bg-red-100 transition-colors duration-150" data-delete="${name}">Supprimer</button>
634
- </div>`;
635
- savedList.appendChild(li);
636
- });
637
- };
638
-
639
- // --- Saved List Event Delegation (View/Delete) ---
640
- savedList.addEventListener('click', async (e) => {
641
- const saveName = e.target.closest('[data-save]')?.dataset.save;
642
- const deleteName = e.target.closest('[data-delete]')?.dataset.delete;
643
-
644
- if (saveName) {
645
- const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
646
- const data = savedExercises[saveName];
647
- if (data) {
648
- form.classList.add('hidden');
649
- loader.classList.add('hidden');
650
- solutionSection.classList.remove('hidden');
651
- thoughtsContent.innerHTML = data.thinking;
652
- answerContent.innerHTML = data.answer;
653
- const hasThinkingContent = data.thinking && data.thinking.trim() !== '';
654
- if (hasThinkingContent && !thoughtsBox.classList.contains('open')) thoughtsToggle.click();
655
- else if (!hasThinkingContent && thoughtsBox.classList.contains('open')) thoughtsToggle.click();
656
- await typesetContent('answerContent');
657
- saveButton.disabled = false; // Enable save button when loading a solution
658
- savedModal.classList.remove('active');
659
- }
660
- } else if (deleteName) {
661
- Swal.fire({
662
- title: 'Êtes-vous sûr ?', text: `Voulez-vous vraiment supprimer "${deleteName}" ?`, icon: 'warning',
663
- showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6',
664
- confirmButtonText: 'Oui, supprimer !', cancelButtonText: 'Annuler'
665
- }).then((result) => {
666
- if (result.isConfirmed) {
667
- let savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
668
- delete savedExercises[deleteName];
669
- localStorage.setItem('savedExercises', JSON.stringify(savedExercises));
670
- loadSavedList();
671
- Swal.fire('Supprimé !', 'La sauvegarde a été supprimée.', 'success');
672
- }
673
- });
674
- }
675
- });
676
-
677
- // --- Modal Open/Close Logic ---
678
- openSaved.addEventListener('click', () => { loadSavedList(); savedModal.classList.add('active'); });
679
- closeSaved.addEventListener('click', () => { savedModal.classList.remove('active'); });
680
- savedModal.addEventListener('click', (e) => { if (e.target === savedModal) savedModal.classList.remove('active'); });
681
-
682
- // --- New Exercise Button (inside modal) ---
683
- newExercise.addEventListener('click', () => {
684
- form.reset(); // Resets the form including file input
685
- form.classList.remove('hidden');
686
- solutionSection.classList.add('hidden');
687
- imagePreview.classList.add('hidden');
688
- previewImage.src = '';
689
- fileNameDisplay.textContent = '';
690
- thoughtsContent.innerHTML = '';
691
- answerContent.innerHTML = '';
692
- thoughtsBuffer = '';
693
- answerBuffer = '';
694
- timestamp.textContent = '';
695
- startTime = null;
696
- saveButton.disabled = true; // Disable save button for new exercise
697
- savedModal.classList.remove('active');
698
- });
699
-
700
- // --- Utility: Show Error Messages ---
701
- const showError = (message) => { Swal.fire({ icon: 'error', title: 'Erreur', text: message, confirmButtonColor: '#3b82f6' }); };
702
-
703
- // --- Initial Setup ---
704
- saveButton.disabled = true; // Ensure save button is disabled on initial load
705
-
706
- }); // End DOMContentLoaded
707
- </script>
708
-
709
- </body>
710
- </html>