Docfile commited on
Commit
70d14f7
·
verified ·
1 Parent(s): c66c001

Delete templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +0 -588
templates/index.html DELETED
@@ -1,588 +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 Espagnol - Analyse de Documents</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://unpkg.com/[email protected]/marked.min.js"></script>
9
- <!-- SweetAlert2 -->
10
- <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
11
- <!-- PrismJS Core & Theme -->
12
- <script src="https://cdnjs.cloudflare.com/ajax/libs/prismjs/1.29.0/prism.min.js" integrity="sha512-7Z9J3l1+EYfeaPKcGXu3lq3MS/7SluIYINAaicZN83z9/9XjUhIYU/YHwZxpTBIlXgy9XEOXRJW1QQkAcLpbzm7hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
13
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prismjs/1.29.0/themes/prism-tomorrow.min.css" integrity="sha512-vswe+cgvic/XBoF1OcM/TeJ2FW0OofqAVdCZiEYkd6dwGXOyEVrd0XB9aLhtFOUUjLz90GzvnBCgXWLSntyo7w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
14
- <!-- PrismJS Languages (Load components individually if needed, or use autoloader) -->
15
- <script src="https://cdnjs.cloudflare.com/ajax/libs/prismjs/1.29.0/components/prism-core.min.js" integrity="sha512-9khQRAUBYEJDCDVP2ywQ0LRUbnjL3ZfrPJorj7MF8+KEvn3chLJniiG9LUDXTQGshyrGXxGzvSgKyYad7is+7Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
16
- <script src="https://cdnjs.cloudflare.com/ajax/libs/prismjs/1.29.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha512-GP4x8UWxWyh4BMbyJGOGneiTbUdnpcBtDbJcdM8XcSzaHjMTzOL/zENvu6QRGxzNssVSKHncmXbvH/SsqR2nLw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
17
-
18
- <style>
19
- @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=Poppins:wght@300;400;500;600&display=swap');
20
-
21
- body {
22
- font-family: 'Poppins', sans-serif;
23
- }
24
- h1, h2, h3, h4, h5, h6 {
25
- font-family: 'Playfair Display', serif;
26
- }
27
-
28
- .fade-in { animation: fadeIn 0.5s ease-in; }
29
- .slide-up { animation: slideUp 0.5s ease-out; }
30
- @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
31
- @keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
32
-
33
- .gradient-text {
34
- background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
35
- -webkit-background-clip: text;
36
- background-clip: text;
37
- color: transparent;
38
- }
39
- .upload-zone {
40
- background: linear-gradient(145deg, #f9fafb, #f3f4f6);
41
- box-shadow: 5px 5px 15px #d1d9e6, -5px -5px 15px #ffffff;
42
- transition: all 0.3s ease;
43
- }
44
- .upload-zone.dragover {
45
- border-color: #4f46e5;
46
- background: #e0e7ff;
47
- transform: scale(1.02);
48
- }
49
- .waves { position: absolute; bottom: 0; left: 0; width: 100%; overflow: hidden; line-height: 0; transform: rotate(180deg); z-index: 0; }
50
- .waves svg { position: relative; display: block; width: calc(100% + 1.3px); height: 150px; }
51
- .waves .fill-indigo-50 { fill: #eef2ff; }
52
-
53
- /* Responsive Analysis Box & Prose */
54
- .prose { max-width: 100%; }
55
- .prose pre[class*="language-"] { /* Target only Prism <pre> blocks */
56
- background-color: #2d2d2d !important; /* Match Prism Tomorrow, use !important if needed */
57
- color: #ccc;
58
- padding: 1em;
59
- margin: 1.5em 0;
60
- overflow-x: auto;
61
- border-radius: 0.375rem;
62
- border: 1px solid #444; /* Subtle border */
63
- }
64
- .prose code[class*="language-"] { /* Target only Prism <code> blocks */
65
- font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; /* Monospace font */
66
- color: #ccc;
67
- background: none;
68
- text-shadow: none;
69
- font-size: 0.9em; /* Slightly smaller code font */
70
- direction: ltr;
71
- text-align: left;
72
- white-space: pre;
73
- word-spacing: normal;
74
- word-break: normal;
75
- line-height: 1.5;
76
- -moz-tab-size: 4;
77
- -o-tab-size: 4;
78
- tab-size: 4;
79
- -webkit-hyphens: none;
80
- -moz-hyphens: none;
81
- -ms-hyphens: none;
82
- hyphens: none;
83
- }
84
- /* Ensure inline code also gets styled if needed, but differently */
85
- .prose :not(pre) > code {
86
- background-color: #f3f4f6; /* Light gray background for inline code */
87
- color: #4b5563; /* Darker gray text */
88
- padding: 0.2em 0.4em;
89
- border-radius: 0.25rem;
90
- font-size: 0.9em;
91
- }
92
- .prose h1, .prose h2, .prose h3 { color: #1F2937; margin-top: 1.5em; margin-bottom: 0.8em; padding-bottom: 0.3em; border-bottom: 1px solid #e5e7eb; }
93
- .prose p { margin-bottom: 1.25em; line-height: 1.7; }
94
- .prose ul, .prose ol { margin-left: 1.5em; margin-bottom: 1.25em; }
95
- .prose li > p { margin-bottom: 0.5em; }
96
- /* Button disabled state */
97
- button:disabled {
98
- opacity: 0.5;
99
- cursor: not-allowed;
100
- background-color: #a5b4fc;
101
- }
102
- /* Fix potential Prism theme override from Tailwind */
103
- :not(pre) > code[class*="language-"], pre[class*="language-"] {
104
- background: #2d2d2d;
105
- }
106
-
107
- /* --- Added Style for Disabled Radio Label --- */
108
- label:has(input[type="radio"]:disabled) {
109
- cursor: not-allowed !important; /* Override cursor-pointer from Tailwind */
110
- opacity: 0.5; /* Visually dim the disabled option */
111
- pointer-events: none; /* Prevent click events on the label */
112
- }
113
- label:has(input[type="radio"]:disabled):hover {
114
- background-color: transparent !important; /* Remove hover background */
115
- }
116
-
117
- </style>
118
- </head>
119
- <body class="bg-gradient-to-br from-gray-50 to-gray-100 min-h-screen relative overflow-x-hidden">
120
- <div class="waves">
121
- <svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 120" preserveAspectRatio="none">
122
- <path d="M321.39,56.44c58-10.79,114.16-30.13,172-41.86,82.39-16.72,168.19-17.73,250.45-.39C823.78,31,906.67,72,985.66,92.83c70.05,18.48,146.53,26.09,214.34,3V0H0V27.35A600.21,600.21,0,0,0,321.39,56.44Z" class="fill-indigo-50"></path>
123
- </svg>
124
- </div>
125
-
126
- <div class="max-w-4xl mx-auto px-4 py-12 relative z-10">
127
- <!-- Header -->
128
- <header class="text-center mb-12 fade-in">
129
- <h1 class="text-4xl md:text-5xl font-bold mb-3 font-['Playfair_Display'] gradient-text">
130
- Mariam Espagnol
131
- </h1>
132
- <p class="text-lg text-gray-600 font-light max-w-xl mx-auto">
133
- Analysez vos documents iconographiques ou textuels en espagnol
134
- </p>
135
- </header>
136
-
137
- <!-- Main Content -->
138
- <div class="bg-white rounded-2xl shadow-xl p-6 md:p-8 mb-8 slide-up">
139
- <!-- File Type Selection -->
140
- <div class="mb-6">
141
- <label class="block text-base font-medium text-gray-700 mb-3">1. Choisissez le type de document</label>
142
- <div class="flex flex-col sm:flex-row gap-4">
143
- <label class="relative flex items-center group cursor-pointer p-3 border border-gray-200 rounded-lg hover:bg-indigo-50 transition-colors w-full">
144
- <input type="radio" name="fileType" value="image" checked
145
- class="peer sr-only file-type-radio">
146
- <div class="w-5 h-5 mr-3 border-2 border-gray-300 rounded-full flex-shrink-0 peer-checked:border-indigo-500 peer-checked:bg-indigo-500 transition-all"></div>
147
- <span class="text-sm text-gray-700 group-hover:text-indigo-600">Document iconographique (Image, Photo, Dessin...) avec méthodologie.</span>
148
- </label>
149
- <label class="relative flex items-center group cursor-pointer p-3 border border-gray-200 rounded-lg hover:bg-indigo-50 transition-colors w-full">
150
- <input type="radio" name="fileType" value="text" disabled <!-- <-- Ajout de l'attribut disabled ici -->
151
- class="peer sr-only file-type-radio">
152
- <div class="w-5 h-5 mr-3 border-2 border-gray-300 rounded-full flex-shrink-0 peer-checked:border-indigo-500 peer-checked:bg-indigo-500 transition-all"></div>
153
- <span class="text-sm text-gray-700 group-hover:text-indigo-600">Document textuel (Texte, PDF lisible...) avec méthodologie.</span>
154
- </label>
155
- </div>
156
- </div>
157
-
158
- <!-- Upload Zone -->
159
- <div class="mb-6">
160
- <label class="block text-base font-medium text-gray-700 mb-3">2. Sélectionnez votre fichier</label>
161
- <div id="upload-zone" class="upload-zone border-2 border-dashed border-gray-300 rounded-xl p-6 text-center cursor-pointer transition-all duration-300 relative overflow-hidden">
162
- <input id="file-upload" name="file" type="file" class="sr-only" accept="image/*">
163
- <div class="space-y-3">
164
- <div class="w-16 h-16 mx-auto rounded-full bg-indigo-100 flex items-center justify-center border-4 border-white shadow-sm">
165
- <svg class="w-8 h-8 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
166
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
167
- </svg>
168
- </div>
169
- <div class="text-sm text-gray-600">
170
- <label for="file-upload" class="relative cursor-pointer rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
171
- <span>Déposez votre fichier ici</span>
172
- </label>
173
- <span class="text-gray-500"> ou </span>
174
- <label for="file-upload" class="cursor-pointer text-indigo-600 hover:text-indigo-500 font-medium">cliquez pour sélectionner</label>
175
- </div>
176
- <p class="text-xs text-gray-500" id="file-types-info">Images (PNG, JPG, GIF) ou Textes (TXT, PDF) - Max 16MB</p>
177
- </div>
178
- </div>
179
- </div>
180
-
181
- <!-- Preview -->
182
- <div id="preview" class="mt-6 hidden slide-up">
183
- <h3 class="text-base font-medium text-gray-800 mb-3">Aperçu</h3>
184
- <div class="preview-container border rounded-lg p-4 bg-gray-50 max-h-72 overflow-auto relative">
185
- <img id="image-preview" class="max-w-full h-auto hidden rounded block mx-auto" alt="Aperçu Image">
186
- <pre id="text-preview" class="text-sm text-gray-700 whitespace-pre-wrap hidden bg-white p-3 rounded shadow-inner"></pre>
187
- <p id="file-info" class="text-xs text-gray-500 mt-2 text-center"></p>
188
- <button id="remove-file-button" class="absolute top-2 right-2 bg-red-100 text-red-600 hover:bg-red-200 rounded-full p-1 text-xs hidden" title="Retirer le fichier">
189
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
190
- <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
191
- </svg>
192
- </button>
193
- </div>
194
- </div>
195
-
196
- <!-- Submit Button -->
197
- <div class="mt-6 text-center">
198
- <button id="submit-button"
199
- disabled
200
- class="bg-indigo-600 text-white font-medium py-2.5 px-6 rounded-lg shadow hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition duration-150 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-indigo-300">
201
- <span id="submit-button-text">3. Lancer l'analyse</span>
202
- <span id="submit-button-spinner" class="hidden">
203
- <svg class="animate-spin inline-block -ml-1 mr-2 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
204
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
205
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
206
- </svg>
207
- Analyse en cours...
208
- </span>
209
- </button>
210
- </div>
211
-
212
- <!-- Analysis Results -->
213
- <div id="results" class="mt-10 hidden slide-up">
214
- <h3 class="text-lg font-semibold text-gray-900 mb-4 border-b pb-2">Analyse détaillée</h3>
215
- <div class="bg-gray-50 rounded-lg p-4 md:p-6 shadow-inner overflow-x-auto border border-gray-200">
216
- <!-- Applying prose styles here -->
217
- <div id="analysis-content" class="prose prose-sm sm:prose-base max-w-none prose-indigo">
218
- <p class="text-gray-500">L'analyse apparaîtra ici...</p>
219
- </div>
220
- </div>
221
- <div class="mt-4 text-right">
222
- <button id="copy-analysis-button" class="text-sm bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium py-1.5 px-3 rounded-md transition duration-150 ease-in-out">
223
- Copier l'analyse
224
- </button>
225
- </div>
226
- </div>
227
- </div> <!-- End main content block -->
228
-
229
- <footer class="text-center mt-12 text-gray-500 text-sm fade-in">
230
- <p>© 2025 Mariam Espagnol - Outil d'analyse</p>
231
- </footer>
232
-
233
- </div> <!-- End max-width container -->
234
-
235
- <script>
236
- document.addEventListener('DOMContentLoaded', function() {
237
- const uploadZone = document.getElementById('upload-zone');
238
- const fileInput = document.getElementById('file-upload');
239
- const preview = document.getElementById('preview');
240
- const imagePreview = document.getElementById('image-preview');
241
- const textPreview = document.getElementById('text-preview');
242
- const fileInfo = document.getElementById('file-info');
243
- const removeFileButton = document.getElementById('remove-file-button');
244
- const submitButton = document.getElementById('submit-button');
245
- const submitButtonText = document.getElementById('submit-button-text');
246
- const submitButtonSpinner = document.getElementById('submit-button-spinner');
247
- const results = document.getElementById('results');
248
- const analysisContent = document.getElementById('analysis-content');
249
- const copyAnalysisButton = document.getElementById('copy-analysis-button');
250
- const fileTypeRadios = document.querySelectorAll('.file-type-radio');
251
- const fileTypesInfo = document.getElementById('file-types-info');
252
-
253
- let selectedFile = null;
254
-
255
- // Configure marked with Prism check
256
- marked.setOptions({
257
- breaks: true,
258
- gfm: true,
259
- headerIds: false,
260
- mangle: false,
261
- highlight: function(code, lang) {
262
- // ** Check if Prism and the language are loaded **
263
- if (typeof Prism !== 'undefined' && Prism.languages[lang]) {
264
- try {
265
- return Prism.highlight(code, Prism.languages[lang], lang);
266
- } catch (error) {
267
- console.warn(`Prism highlighting failed for language ${lang}:`, error)
268
- return code; // Return unhighlighted on error
269
- }
270
- }
271
- // ** Fallback if Prism or language not available **
272
- return code;
273
- }
274
- });
275
-
276
- // --- Helper Functions ---
277
- function showLoading(isLoading) {
278
- if (isLoading) {
279
- submitButtonText.classList.add('hidden');
280
- submitButtonSpinner.classList.remove('hidden');
281
- // Button should already be disabled before calling showLoading(true)
282
- // submitButton.disabled = true;
283
- } else {
284
- submitButtonText.classList.remove('hidden');
285
- submitButtonSpinner.classList.add('hidden');
286
- submitButton.disabled = (selectedFile === null);
287
- }
288
- }
289
-
290
- function resetUI() {
291
- selectedFile = null;
292
- fileInput.value = '';
293
- preview.classList.add('hidden');
294
- imagePreview.classList.add('hidden');
295
- textPreview.classList.add('hidden');
296
- imagePreview.src = '#';
297
- textPreview.textContent = '';
298
- fileInfo.textContent = '';
299
- removeFileButton.classList.add('hidden');
300
- results.classList.add('hidden');
301
- analysisContent.innerHTML = '<p class="text-gray-500">L\'analyse apparaîtra ici...</p>';
302
- submitButton.disabled = true;
303
- showLoading(false); // Ensure spinner is hidden
304
- updateAcceptAttribute(); // Update file type info based on selected radio
305
- }
306
-
307
- function updateAcceptAttribute() {
308
- // Find the *checked* radio button, ignoring disabled ones
309
- const selectedRadio = document.querySelector('input[name="fileType"]:checked');
310
- const selectedType = selectedRadio ? selectedRadio.value : 'image'; // Default to image if none checked (shouldn't happen with 'checked' on image)
311
-
312
- if (selectedType === 'image') {
313
- fileInput.accept = "image/png, image/jpeg, image/gif, image/webp";
314
- fileTypesInfo.textContent = "Images (PNG, JPG, GIF, WEBP) - Max 16MB";
315
- } else { // This branch should theoretically not be reachable if 'text' is disabled and 'image' is checked
316
- fileInput.accept = "text/plain, application/pdf, .txt";
317
- fileTypesInfo.textContent = "Textes (TXT, PDF lisible) - Max 16MB";
318
- }
319
-
320
- // If a file is selected, check if it matches the currently *selectable* type.
321
- // Since 'text' is disabled, any selected file must match 'image'.
322
- if (selectedFile) {
323
- const currentFileTypeMatchesSelection =
324
- (selectedType === 'image' && selectedFile.type.startsWith('image/')); // Only need to check against 'image' now
325
-
326
- if(!currentFileTypeMatchesSelection) {
327
- // This case should only happen if a text file was selected *before*
328
- // 'text' was disabled or if the user manually changes something
329
- // unexpectedly. With 'text' disabled from the start, this might not
330
- // be strictly necessary, but it's good defensive programming.
331
- resetUI();
332
- // Use SweetAlert for this message too
333
- Swal.fire({
334
- icon: 'warning',
335
- title: 'Type de document changé',
336
- text: 'Le type de document a été modifié. Veuillez resélectionner votre fichier.',
337
- });
338
- }
339
- }
340
- }
341
-
342
-
343
- // --- Event Listeners ---
344
- // Note: Change listener on radios is less critical if one is disabled,
345
- // but keep it for robustness or future changes.
346
- fileTypeRadios.forEach(radio => {
347
- // Only add listener if the radio is not disabled
348
- if (!radio.disabled) {
349
- radio.addEventListener('change', updateAcceptAttribute);
350
- }
351
- });
352
- uploadZone.addEventListener('click', () => {
353
- // Only trigger file input if the 'upload-zone' itself isn't logically disabled
354
- // (e.g., during upload, although button is disabled then)
355
- if (!submitButtonSpinner.classList.contains('hidden')) return; // Don't allow file selection during analysis
356
- fileInput.click();
357
- });
358
- uploadZone.addEventListener('dragover', (e) => {
359
- e.preventDefault();
360
- if (!submitButtonSpinner.classList.contains('hidden')) return; // Don't allow drag/drop during analysis
361
- uploadZone.classList.add('dragover');
362
- });
363
- uploadZone.addEventListener('dragleave', () => {
364
- uploadZone.classList.remove('dragover');
365
- });
366
- uploadZone.addEventListener('drop', (e) => {
367
- e.preventDefault();
368
- uploadZone.classList.remove('dragover');
369
- if (!submitButtonSpinner.classList.contains('hidden')) return; // Don't allow drag/drop during analysis
370
- if (e.dataTransfer.files.length) {
371
- handleFileSelection(e.dataTransfer.files[0]);
372
- }
373
- });
374
- fileInput.addEventListener('change', (e) => {
375
- if (e.target.files.length) {
376
- handleFileSelection(e.target.files[0]);
377
- }
378
- });
379
- removeFileButton.addEventListener('click', resetUI);
380
- submitButton.addEventListener('click', handleSubmit);
381
- copyAnalysisButton.addEventListener('click', copyAnalysisToClipboard);
382
-
383
-
384
- // --- Core Logic Functions ---
385
-
386
- function handleFileSelection(file) {
387
- // Ensure we only accept the type corresponding to the *checked* (and not disabled) radio
388
- const selectedRadio = document.querySelector('input[name="fileType"]:checked');
389
- const selectedType = selectedRadio ? selectedRadio.value : null;
390
-
391
- if (!selectedType) {
392
- // This should not happen if 'image' is checked by default and not disabled
393
- Swal.fire({
394
- icon: 'error',
395
- title: 'Erreur de configuration',
396
- text: 'Aucun type de document sélectionné ou disponible.',
397
- });
398
- resetUI();
399
- return;
400
- }
401
-
402
- const maxFileSize = 16 * 1024 * 1024;
403
- if (file.size > maxFileSize) {
404
- Swal.fire({
405
- icon: 'error',
406
- title: 'Fichier trop volumineux',
407
- text: `La taille maximale autorisée est de 16 Mo. Votre fichier fait ${(file.size / 1024 / 1024).toFixed(1)} Mo.`,
408
- });
409
- resetUI();
410
- return;
411
- }
412
-
413
- // Only allow image type if 'image' is selected (which is the only selectable option)
414
- if (selectedType === 'image') {
415
- const allowedImageTypes = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
416
- const isImageTypeByName = /\.(jpe?g|png|gif|webp)$/i.test(file.name);
417
- if (!allowedImageTypes.includes(file.type) && !isImageTypeByName) {
418
- Swal.fire({
419
- icon: 'error',
420
- title: 'Type de fichier invalide',
421
- text: 'Veuillez sélectionner une image (PNG, JPG, GIF, WEBP) car le type iconographique est sélectionné.',
422
- });
423
- resetUI();
424
- return;
425
- }
426
- }
427
- // The 'text' case for validation is no longer needed here since it's disabled.
428
-
429
-
430
- // If validation passes:
431
- selectedFile = file;
432
- preview.classList.remove('hidden');
433
- preview.classList.add('fade-in');
434
- results.classList.add('hidden');
435
- analysisContent.innerHTML = '<p class="text-gray-500">L\'analyse apparaîtra ici...</p>';
436
- fileInfo.textContent = `${file.name} (${(file.size / 1024).toFixed(1)} Ko)`;
437
- removeFileButton.classList.remove('hidden');
438
-
439
- // Only show image preview since only image type is selectable
440
- imagePreview.classList.remove('hidden');
441
- textPreview.classList.add('hidden'); // Hide text preview
442
- const reader = new FileReader();
443
- reader.onload = (e) => imagePreview.src = e.target.result;
444
- reader.readAsDataURL(file);
445
-
446
- submitButton.disabled = false;
447
- }
448
-
449
- function handleSubmit() {
450
- if (!selectedFile) {
451
- Swal.fire('Aucun fichier', 'Veuillez sélectionner un fichier avant de lancer l\'analyse.', 'warning');
452
- return;
453
- }
454
-
455
- // Always 'image' since 'text' is disabled
456
- const fileType = document.querySelector('input[name="fileType"]:checked').value;
457
-
458
- const formData = new FormData();
459
- formData.append('file', selectedFile);
460
- formData.append('fileType', fileType); // This will always send 'image'
461
-
462
- showLoading(true);
463
- results.classList.add('hidden');
464
- submitButton.disabled = true; // Explicitly disable button while loading
465
-
466
- fetch('/upload', {
467
- method: 'POST',
468
- body: formData
469
- })
470
- .then(response => {
471
- if (!response.ok) {
472
- return response.json().then(errData => {
473
- throw new Error(errData.error || `Erreur HTTP: ${response.status}`);
474
- }).catch(() => {
475
- throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`);
476
- });
477
- }
478
- return response.json();
479
- })
480
- .then(data => {
481
- showLoading(false); // This will re-enable the button based on selectedFile state
482
- if (data.success && data.analysis) {
483
- results.classList.remove('hidden');
484
- results.classList.add('slide-up');
485
- analysisContent.innerHTML = marked.parse(data.analysis);
486
-
487
- // ** Check if Prism is loaded before highlighting **
488
- if (typeof Prism !== 'undefined') {
489
- // Use Prism's autoloader or highlightAllUnder
490
- Prism.highlightAllUnder(analysisContent); // Simpler if autoloader is robust
491
- // Or manually trigger highlighting on specific elements if autoloader fails:
492
- // analysisContent.querySelectorAll('pre code').forEach((block) => {
493
- // Prism.highlightElement(block);
494
- // });
495
- } else {
496
- console.warn("Prism n'est pas chargé, impossible de colorer la syntaxe.");
497
- }
498
-
499
- results.scrollIntoView({ behavior: 'smooth', block: 'start' });
500
- } else {
501
- throw new Error(data.error || "L'analyse n'a pas pu être générée.");
502
- }
503
- })
504
- .catch(error => {
505
- showLoading(false); // This will re-enable the button
506
- // Use SweetAlert for error display
507
- Swal.fire({
508
- icon: 'error',
509
- title: 'Erreur lors de l\'analyse',
510
- text: error.message || 'Une erreur inconnue est survenue.',
511
- });
512
- // Optionally log detailed error
513
- console.error('Error during analysis:', error);
514
- });
515
- }
516
-
517
- function copyAnalysisToClipboard() {
518
- const analysisHtml = analysisContent.innerHTML;
519
- if (!analysisHtml || analysisHtml.includes('L\'analyse apparaîtra ici...')) {
520
- Swal.fire('Rien à copier', 'Aucune analyse n\'a été générée pour le moment.', 'info');
521
- return;
522
- }
523
-
524
- const tempDiv = document.createElement('div');
525
- tempDiv.innerHTML = analysisHtml;
526
- let textToCopy = tempDiv.textContent || tempDiv.innerText || "";
527
-
528
- // Basic conversion for better text format - improved code block handling
529
- textToCopy = analysisHtml
530
- .replace(/<br\s*\/?>/gi, '\n')
531
- .replace(/<\/p>/gi, '\n\n') // Added extra newline for paragraphs
532
- .replace(/<\/li>/gi, '\n')
533
- .replace(/<\/(h[1-6])>/gi, '\n\n')
534
- // Handle preformatted blocks separately - try to preserve internal newlines
535
- .replace(/<pre.*?><code.*?>([\s\S]*?)<\/code><\/pre>/gi, (match, codeContent) => {
536
- // Decode HTML entities within the code block first
537
- let decodedCode = codeContent
538
- .replace(/&/g, '&')
539
- .replace(/</g, '<')
540
- .replace(/>/g, '>')
541
- .replace(/"/g, '"')
542
- .replace(/'/g, "'");
543
- return '\n```\n' + decodedCode.trim() + '\n```\n';
544
- })
545
- .replace(/<[^>]+>/g, '') // Remove remaining tags
546
- .replace(/&/g, '&') // Decode common entities left outside code blocks
547
- .replace(/</g, '<')
548
- .replace(/>/g, '>')
549
- .replace(/"/g, '"')
550
- .replace(/'/g, "'")
551
- .replace(/\n{3,}/g, '\n\n') // Collapse excessive newlines
552
- .trim();
553
-
554
-
555
- navigator.clipboard.writeText(textToCopy)
556
- .then(() => {
557
- // Use SweetAlert for success
558
- Swal.fire({
559
- icon: 'success',
560
- title: 'Copié !',
561
- text: 'L\'analyse a été copiée dans le presse-papiers.',
562
- timer: 1500, // Auto-close after 1.5 seconds
563
- showConfirmButton: false // Hide the confirmation button
564
- });
565
- })
566
- .catch(err => {
567
- console.error('Erreur de copie: ', err);
568
- // Use SweetAlert for error
569
- Swal.fire({
570
- icon: 'error',
571
- title: 'Erreur de copie',
572
- text: 'Impossible de copier l\'analyse automatiquement. Vous pouvez essayer de la sélectionner manuellement.',
573
- });
574
- });
575
- }
576
-
577
-
578
- // --- Initial Setup ---
579
- // Set the 'image' radio to be the only one checkable/available
580
- document.querySelector('input[name="fileType"][value="image"]').checked = true;
581
- document.querySelector('input[name="fileType"][value="text"]').disabled = true; // Ensure it's disabled on load
582
- updateAcceptAttribute(); // Update file type info based on initial selection
583
- resetUI(); // Reset UI to initial state (no file selected)
584
-
585
- }); // End DOMContentLoaded
586
- </script>
587
- </body>
588
- </html>