Docfile commited on
Commit
81b84b5
·
verified ·
1 Parent(s): fb35fbe

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +219 -454
templates/index.html CHANGED
@@ -3,504 +3,269 @@
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
- <!-- Tailwind CSS -->
8
  <script src="https://cdn.tailwindcss.com"></script>
9
- <!-- Font Awesome -->
10
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
11
- <!-- Animation library -->
12
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
13
  <style>
14
- @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
15
-
16
  body {
17
- font-family: 'Poppins', sans-serif;
18
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
19
- min-height: 100vh;
20
  }
21
-
22
- .floating {
23
- animation: floating 3s ease-in-out infinite;
24
  }
25
-
26
- @keyframes floating {
27
- 0% { transform: translateY(0px); }
28
- 50% { transform: translateY(-15px); }
29
- 100% { transform: translateY(0px); }
 
30
  }
31
-
32
- .floating-slow {
33
- animation: floating-slow 5s ease-in-out infinite;
 
 
 
 
 
34
  }
35
-
36
- @keyframes floating-slow {
37
- 0% { transform: translateY(0px) rotate(0deg); }
38
- 50% { transform: translateY(-10px) rotate(2deg); }
39
- 100% { transform: translateY(0px) rotate(0deg); }
 
 
 
40
  }
41
-
42
- .glass {
43
- background: rgba(255, 255, 255, 0.2);
44
- backdrop-filter: blur(8px);
45
- -webkit-backdrop-filter: blur(8px);
46
- border: 1px solid rgba(255, 255, 255, 0.3);
47
  }
48
-
49
- .loader {
50
- width: 48px;
51
- height: 48px;
52
- border: 5px solid #FFF;
53
- border-bottom-color: #FF3D00;
54
- border-radius: 50%;
55
- display: inline-block;
56
- box-sizing: border-box;
57
- animation: rotation 1s linear infinite;
 
 
 
 
 
 
 
 
 
 
58
  }
59
 
60
- @keyframes rotation {
61
- 0% { transform: rotate(0deg); }
62
- 100% { transform: rotate(360deg); }
 
 
 
 
 
 
 
 
 
 
63
  }
64
-
65
- .custom-file-input::-webkit-file-upload-button {
66
- visibility: hidden;
67
- display: none;
68
  }
 
69
  </style>
70
  </head>
71
- <body class="bg-gradient-to-br from-blue-50 to-purple-100 p-6">
72
- <!-- Background decorations -->
73
- <div class="fixed -z-10 top-0 left-0 w-full h-full overflow-hidden">
74
- <div class="absolute top-20 left-10 w-32 h-32 bg-yellow-300 rounded-full opacity-20 floating"></div>
75
- <div class="absolute top-40 right-20 w-40 h-40 bg-blue-400 rounded-full opacity-20 floating-slow"></div>
76
- <div class="absolute bottom-20 left-1/4 w-24 h-24 bg-pink-300 rounded-full opacity-20 floating"></div>
77
- <div class="absolute bottom-40 right-1/3 w-36 h-36 bg-green-300 rounded-full opacity-20 floating-slow"></div>
78
- </div>
79
 
80
- <div class="max-w-7xl mx-auto">
81
- <!-- Header -->
82
- <header class="text-center mb-12 animate__animated animate__fadeInDown">
83
- <h1 class="text-5xl font-bold text-indigo-800 mb-4">
84
- <span class="inline-block transform hover:scale-105 transition-transform duration-300">
85
- Mariam Espagnol 🇪🇸
86
- </span>
87
- </h1>
88
- <p class="text-xl text-indigo-600">Analyse de documents textuels et iconographiques</p>
89
  </header>
90
 
91
- <!-- Main content -->
92
- <main class="grid grid-cols-1 lg:grid-cols-3 gap-8">
93
- <!-- Left panel (upload section) -->
94
- <div class="lg:col-span-1">
95
- <div class="glass rounded-3xl p-8 shadow-xl floating-slow animate__animated animate__fadeInLeft">
96
- <h2 class="text-2xl font-semibold text-indigo-700 mb-6">
97
- <i class="fas fa-upload mr-2"></i> Télécharger un document
98
- </h2>
99
-
100
  <div class="mb-6">
101
- <label class="block text-indigo-600 mb-3">Type de document:</label>
102
- <div class="flex space-x-4">
103
- <label class="flex items-center cursor-pointer">
104
- <input type="radio" name="fileType" value="image" checked class="hidden">
105
- <div class="w-6 h-6 border-2 border-indigo-400 rounded-full flex items-center justify-center mr-2 file-type-radio">
106
- <div class="w-3 h-3 bg-indigo-600 rounded-full opacity-100 radio-dot"></div>
107
- </div>
108
- <span>Iconographique</span>
 
 
 
 
 
109
  </label>
110
- <label class="flex items-center cursor-pointer">
111
- <input type="radio" name="fileType" value="text" class="hidden">
112
- <div class="w-6 h-6 border-2 border-indigo-400 rounded-full flex items-center justify-center mr-2 file-type-radio">
113
- <div class="w-3 h-3 bg-indigo-600 rounded-full opacity-0 radio-dot"></div>
114
- </div>
115
- <span>Textuel</span>
116
  </label>
117
  </div>
118
  </div>
119
-
120
  <div class="text-center">
121
- <div class="mb-8">
122
- <div id="drop-area" class="border-2 border-dashed border-indigo-300 rounded-xl p-8 text-center transition-colors hover:border-indigo-500 cursor-pointer bg-white bg-opacity-50">
123
- <i class="fas fa-cloud-upload-alt text-5xl text-indigo-400 mb-4"></i>
124
- <h3 class="text-lg text-indigo-600 mb-2">Glissez votre fichier ici</h3>
125
- <p class="text-sm text-gray-500 mb-4">ou</p>
126
- <label class="inline-block bg-indigo-600 text-white font-medium py-2 px-6 rounded-lg cursor-pointer hover:bg-indigo-700 transition-colors">
127
- Parcourir
128
- <input type="file" id="file-input" class="hidden custom-file-input" accept=".jpg,.jpeg,.png,.txt,.pdf">
129
- </label>
130
- <p id="file-name" class="mt-3 text-sm text-gray-600"></p>
131
- </div>
132
- </div>
133
-
134
- <button id="analyze-btn" class="w-full bg-indigo-700 hover:bg-indigo-800 text-white font-semibold py-3 px-8 rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed">
135
- <span id="btn-text">
136
- <i class="fas fa-magic mr-2"></i>
137
- Générer l'analyse
138
- </span>
139
- <span id="btn-loading" class="hidden">
140
- <i class="fas fa-circle-notch fa-spin mr-2"></i>
141
- Analyse en cours...
142
- </span>
143
  </button>
144
  </div>
145
-
146
- <div class="mt-8 text-sm text-indigo-800 bg-indigo-100 p-4 rounded-lg">
147
- <h3 class="font-semibold mb-2"><i class="fas fa-info-circle mr-1"></i> Comment ça marche ?</h3>
148
- <ol class="list-decimal pl-5 space-y-1">
149
- <li>Sélectionnez le type de document (iconographique ou textuel)</li>
150
- <li>Téléchargez votre fichier</li>
151
- <li>Cliquez sur "Générer l'analyse"</li>
152
- <li>Recevez une analyse complète en espagnol avec traduction française</li>
153
- </ol>
154
- </div>
 
 
 
 
155
  </div>
 
156
  </div>
157
-
158
- <!-- Right panel (results) -->
159
- <div class="lg:col-span-2 animate__animated animate__fadeInRight">
160
- <div id="results-container" class="glass rounded-3xl p-8 shadow-xl min-h-[600px] flex flex-col">
161
- <h2 class="text-2xl font-semibold text-indigo-700 mb-6">
162
- <i class="fas fa-file-alt mr-2"></i> Résultats de l'analyse
163
- </h2>
164
-
165
- <div id="initial-state" class="flex-grow flex flex-col items-center justify-center text-center">
166
- <img src="https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/1f4dd.svg" class="w-20 h-20 mb-6 floating" alt="Document emoji">
167
- <h3 class="text-xl font-medium text-indigo-600 mb-3">Prêt à analyser votre document</h3>
168
- <p class="text-gray-500 max-w-md">Téléchargez un document iconographique ou textuel pour obtenir une analyse complète en espagnol avec traduction française.</p>
169
- </div>
170
-
171
- <div id="loading-state" class="flex-grow flex flex-col items-center justify-center hidden">
172
- <span class="loader mb-6"></span>
173
- <h3 class="text-xl font-medium text-indigo-600 animate-pulse">Analyse en cours...</h3>
174
- <p class="text-gray-500 mt-2">Veuillez patienter pendant que nous analysons votre document.</p>
175
- </div>
176
-
177
- <div id="results-content" class="hidden flex-grow flex flex-col">
178
- <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
179
- <div class="col-span-1">
180
- <div id="file-preview" class="bg-white rounded-lg shadow-md overflow-hidden h-64 flex items-center justify-center">
181
- <img id="image-preview" src="" alt="Document preview" class="max-w-full max-h-full object-contain hidden">
182
- <div id="text-preview" class="p-4 text-sm text-gray-600 overflow-auto h-full w-full hidden">
183
- <i class="fas fa-file-alt text-4xl text-gray-300 mb-3"></i>
184
- <p class="text-center">Aperçu du document textuel</p>
185
- </div>
186
- </div>
187
- </div>
188
- <div class="col-span-2">
189
- <div class="bg-indigo-50 rounded-lg p-4 h-64 overflow-auto">
190
- <h3 class="text-lg font-medium text-indigo-700 mb-3">Résumé de l'analyse</h3>
191
- <div id="analysis-summary" class="text-sm"></div>
192
- </div>
193
- </div>
194
- </div>
195
-
196
- <div class="bg-white rounded-lg p-6 shadow-sm flex-grow overflow-auto">
197
- <div class="mb-4">
198
- <div class="flex space-x-2">
199
- <button id="tab-spanish" class="tab-btn bg-indigo-600 text-white px-4 py-2 rounded-t-lg font-medium">Espagnol</button>
200
- <button id="tab-french" class="tab-btn bg-gray-200 text-gray-700 px-4 py-2 rounded-t-lg font-medium">Français</button>
201
- </div>
202
- <div class="h-1 bg-indigo-600 w-full"></div>
203
- </div>
204
-
205
- <div id="tab-content-spanish" class="tab-content">
206
- <div id="spanish-content" class="prose max-w-none"></div>
207
- </div>
208
-
209
- <div id="tab-content-french" class="tab-content hidden">
210
- <div id="french-content" class="prose max-w-none"></div>
211
- </div>
212
- </div>
213
-
214
- <div class="mt-6 text-center">
215
- <button id="download-btn" class="bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-6 rounded-lg shadow hover:shadow-lg transition-all">
216
- <i class="fas fa-download mr-2"></i> Télécharger l'analyse
217
- </button>
218
- </div>
219
- </div>
220
  </div>
221
  </div>
222
  </main>
223
-
224
- <!-- Footer -->
225
- <footer class="mt-12 text-center text-gray-600 animate__animated animate__fadeInUp">
226
- <p>&copy; 2025 Mariam Espagnol - Tous droits réservés</p>
227
  </footer>
228
  </div>
229
 
230
- <!-- JavaScript -->
231
  <script>
232
- document.addEventListener('DOMContentLoaded', function() {
233
- // File Upload Logic
234
- const fileInput = document.getElementById('file-input');
235
- const fileName = document.getElementById('file-name');
236
- const dropArea = document.getElementById('drop-area');
237
- const analyzeBtn = document.getElementById('analyze-btn');
238
- const radioButtons = document.querySelectorAll('input[name="fileType"]');
239
-
240
- // Results and state management
241
- const initialState = document.getElementById('initial-state');
242
- const loadingState = document.getElementById('loading-state');
243
- const resultsContent = document.getElementById('results-content');
244
- const imagePreview = document.getElementById('image-preview');
245
- const textPreview = document.getElementById('text-preview');
246
- const spanishContent = document.getElementById('spanish-content');
247
- const frenchContent = document.getElementById('french-content');
248
- const analysisSummary = document.getElementById('analysis-summary');
249
- const btnText = document.getElementById('btn-text');
250
- const btnLoading = document.getElementById('btn-loading');
251
- const downloadBtn = document.getElementById('download-btn');
252
-
253
- // Tabs
254
- const tabSpanish = document.getElementById('tab-spanish');
255
- const tabFrench = document.getElementById('tab-french');
256
- const tabContentSpanish = document.getElementById('tab-content-spanish');
257
- const tabContentFrench = document.getElementById('tab-content-french');
258
-
259
- // Radio buttons behavior
260
- radioButtons.forEach(radio => {
261
- radio.addEventListener('change', function() {
262
- // Reset the file input
263
- fileInput.value = '';
264
- fileName.textContent = '';
265
- analyzeBtn.disabled = true;
266
-
267
- // Update all radio dots
268
- document.querySelectorAll('.radio-dot').forEach(dot => {
269
- dot.classList.add('opacity-0');
270
- dot.classList.remove('opacity-100');
271
- });
272
-
273
- // Mark the selected one
274
- if (this.checked) {
275
- this.closest('label').querySelector('.radio-dot').classList.remove('opacity-0');
276
- this.closest('label').querySelector('.radio-dot').classList.add('opacity-100');
277
- }
278
- });
279
- });
280
-
281
- // File input change
282
- fileInput.addEventListener('change', function(e) {
283
- handleFiles(this.files);
284
- });
285
-
286
- // Drag and drop functionality
287
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
288
- dropArea.addEventListener(eventName, preventDefaults, false);
289
- });
290
-
291
- function preventDefaults(e) {
292
- e.preventDefault();
293
- e.stopPropagation();
294
- }
295
-
296
- ['dragenter', 'dragover'].forEach(eventName => {
297
- dropArea.addEventListener(eventName, highlight, false);
298
- });
299
-
300
- ['dragleave', 'drop'].forEach(eventName => {
301
- dropArea.addEventListener(eventName, unhighlight, false);
302
- });
303
-
304
- function highlight() {
305
- dropArea.classList.add('border-indigo-500', 'bg-indigo-50');
306
- }
307
-
308
- function unhighlight() {
309
- dropArea.classList.remove('border-indigo-500', 'bg-indigo-50');
310
  }
311
-
312
- dropArea.addEventListener('drop', function(e) {
313
- const dt = e.dataTransfer;
314
- const files = dt.files;
315
- handleFiles(files);
316
- });
317
-
318
- function handleFiles(files) {
319
- if (files.length > 0) {
320
- const file = files[0];
321
- fileName.textContent = file.name;
322
- analyzeBtn.disabled = false;
323
-
324
- // Preview if it's an image
325
- if (file.type.startsWith('image/')) {
326
- const reader = new FileReader();
327
- reader.onload = function(e) {
328
- imagePreview.src = e.target.result;
329
- };
330
- reader.readAsDataURL(file);
331
  }
 
332
  }
333
- }
334
-
335
- // Analysis submission
336
- analyzeBtn.addEventListener('click', function() {
337
- if (!fileInput.files.length) return;
338
-
339
- // Show loading state
340
- initialState.classList.add('hidden');
341
- resultsContent.classList.add('hidden');
342
- loadingState.classList.remove('hidden');
343
-
344
- btnText.classList.add('hidden');
345
- btnLoading.classList.remove('hidden');
346
- analyzeBtn.disabled = true;
347
-
348
- const fileType = document.querySelector('input[name="fileType"]:checked').value;
349
- const file = fileInput.files[0];
350
-
351
- const formData = new FormData();
352
- formData.append('file', file);
353
- formData.append('fileType', fileType);
354
-
355
- fetch('/upload', {
356
- method: 'POST',
357
- body: formData
358
- })
359
- .then(response => response.json())
360
- .then(data => {
361
- if (data.success) {
362
- // Hide loading state
363
- loadingState.classList.add('hidden');
364
- resultsContent.classList.remove('hidden');
365
-
366
- // Process analysis
367
- const analysisText = data.analysis;
368
-
369
- // Split content based on language sections
370
- const [spanishPart, frenchPart] = splitAnalysis(analysisText);
371
-
372
- // Display preview based on file type
373
- if (fileType === 'image') {
374
- imagePreview.src = data.file_url;
375
- imagePreview.classList.remove('hidden');
376
- textPreview.classList.add('hidden');
377
- } else {
378
- imagePreview.classList.add('hidden');
379
- textPreview.classList.remove('hidden');
380
- }
381
-
382
- // Populate content
383
- spanishContent.innerHTML = formatText(spanishPart);
384
- frenchContent.innerHTML = formatText(frenchPart);
385
-
386
- // Create summary (first few sentences)
387
- const summaryText = frenchPart.split('. ').slice(0, 3).join('. ') + '.';
388
- analysisSummary.textContent = summaryText;
389
-
390
- // Enable download
391
- downloadBtn.disabled = false;
392
  } else {
393
- alert('Erreur: ' + (data.error || 'Échec de l\'analyse'));
394
- initialState.classList.remove('hidden');
395
- loadingState.classList.add('hidden');
396
  }
397
-
398
- btnText.classList.remove('hidden');
399
- btnLoading.classList.add('hidden');
400
- analyzeBtn.disabled = false;
401
- })
402
- .catch(error => {
403
- console.error('Error:', error);
404
- alert('Une erreur est survenue');
405
- initialState.classList.remove('hidden');
406
- loadingState.classList.add('hidden');
407
- btnText.classList.remove('hidden');
408
- btnLoading.classList.add('hidden');
409
- analyzeBtn.disabled = false;
410
- });
411
- });
412
-
413
- // Tab switching
414
- tabSpanish.addEventListener('click', () => {
415
- tabSpanish.classList.remove('bg-gray-200', 'text-gray-700');
416
- tabSpanish.classList.add('bg-indigo-600', 'text-white');
417
- tabFrench.classList.remove('bg-indigo-600', 'text-white');
418
- tabFrench.classList.add('bg-gray-200', 'text-gray-700');
419
-
420
- tabContentSpanish.classList.remove('hidden');
421
- tabContentFrench.classList.add('hidden');
422
- });
423
-
424
- tabFrench.addEventListener('click', () => {
425
- tabFrench.classList.remove('bg-gray-200', 'text-gray-700');
426
- tabFrench.classList.add('bg-indigo-600', 'text-white');
427
- tabSpanish.classList.remove('bg-indigo-600', 'text-white');
428
- tabSpanish.classList.add('bg-gray-200', 'text-gray-700');
429
-
430
- tabContentFrench.classList.remove('hidden');
431
- tabContentSpanish.classList.add('hidden');
432
- });
433
-
434
- // Download functionality
435
- downloadBtn.addEventListener('click', () => {
436
- const activeTab = document.querySelector('.tab-btn.bg-indigo-600');
437
- let content, filename;
438
-
439
- if (activeTab.id === 'tab-spanish') {
440
- content = spanishContent.innerHTML;
441
- filename = 'analysis_spanish.html';
442
  } else {
443
- content = frenchContent.innerHTML;
444
- filename = 'analysis_french.html';
445
  }
446
-
447
- // Create a simple HTML document
448
- const html = `
449
- <!DOCTYPE html>
450
- <html>
451
- <head>
452
- <meta charset="UTF-8">
453
- <title>Analyse - Mariam Espagnol</title>
454
- <style>
455
- body { font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }
456
- h1, h2, h3 { color: #4338ca; }
457
- </style>
458
- </head>
459
- <body>
460
- <h1>Analyse de document - Mariam Espagnol</h1>
461
- <div>${content}</div>
462
- </body>
463
- </html>
464
- `;
465
-
466
- const blob = new Blob([html], { type: 'text/html' });
467
- const url = URL.createObjectURL(blob);
468
- const a = document.createElement('a');
469
- a.href = url;
470
- a.download = filename;
471
- document.body.appendChild(a);
472
- a.click();
473
- document.body.removeChild(a);
474
- URL.revokeObjectURL(url);
475
- });
476
-
477
- // Helper functions
478
- function splitAnalysis(text) {
479
- // This is a simplified split - in real implementation,
480
- // you would need to parse based on your specific response format
481
-
482
- // For demonstration purposes, let's assume the text has a clear
483
- // Spanish part first, then French part marked by "TRADUCCIÓN FRANCESA" or similar
484
- const parts = text.split(/TRADUCCIÓN FRANCESA|TRADUCTION FRANÇAISE/i);
485
-
486
- if (parts.length >= 2) {
487
- return [parts[0].trim(), parts[1].trim()];
488
- }
489
-
490
- // If no clear separation, attempt a rough half-split (not ideal but fallback)
491
- const midPoint = Math.floor(text.length / 2);
492
- return [text.substring(0, midPoint).trim(), text.substring(midPoint).trim()];
493
- }
494
-
495
- function formatText(text) {
496
- // Basic formatting for better display
497
- return text
498
- .replace(/\n\n/g, '</p><p>')
499
- .replace(/\n/g, '<br>')
500
- .replace(/^/, '<p>')
501
- .replace(/$/, '</p>');
502
  }
503
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
504
  </script>
 
505
  </body>
506
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Analyseur de Documents IA</title>
7
+ <!-- Include Tailwind CSS -->
8
  <script src="https://cdn.tailwindcss.com"></script>
 
 
 
 
9
  <style>
10
+ /* Add a subtle background pattern or gradient if desired */
 
11
  body {
12
+ background-color: #f8fafc; /* Tailwind gray-50 */
 
 
13
  }
14
+ /* Custom style for smoother transitions */
15
+ * {
16
+ transition: all 0.2s ease-in-out;
17
  }
18
+ /* Style for the analysis output to preserve formatting */
19
+ #analysis-text {
20
+ white-space: pre-wrap; /* Preserves whitespace and line breaks */
21
+ word-wrap: break-word; /* Breaks long words */
22
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* Monospace font often looks good for code/analysis */
23
+ line-height: 1.6;
24
  }
25
+ /* Enhance radio button appearance */
26
+ input[type="radio"]:checked {
27
+ background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
28
+ border-color: transparent;
29
+ background-color: currentColor;
30
+ background-size: 100% 100%;
31
+ background-position: center;
32
+ background-repeat: no-repeat;
33
  }
34
+ input[type="radio"] {
35
+ appearance: none;
36
+ border-radius: 50%;
37
+ width: 1.25em;
38
+ height: 1.25em;
39
+ border: 2px solid #cbd5e1; /* gray-300 */
40
+ margin-right: 0.5em;
41
+ vertical-align: middle; /* Align with text */
42
  }
43
+ input[type="radio"]:checked {
44
+ border-color: #2563eb; /* blue-600 */
45
+ background-color: #2563eb; /* blue-600 */
 
 
 
46
  }
47
+ input[type="radio"]:focus {
48
+ outline: 2px solid transparent;
49
+ outline-offset: 2px;
50
+ box-shadow: 0 0 0 2px #fff, 0 0 0 4px #2563eb; /* blue-600 focus ring */
51
+ }
52
+
53
+ /* Loading Spinner Animation */
54
+ @keyframes spin {
55
+ to { transform: rotate(360deg); }
56
+ }
57
+ .spinner {
58
+ display: inline-block;
59
+ width: 1.5rem;
60
+ height: 1.5rem;
61
+ vertical-align: text-bottom;
62
+ border: .25em solid currentColor;
63
+ border-right-color: transparent;
64
+ border-radius: 50%;
65
+ animation: spin .75s linear infinite;
66
+ color: #3b82f6; /* blue-500 */
67
  }
68
 
69
+ /* File Input Styling */
70
+ input[type="file"]::file-selector-button {
71
+ margin-right: 1rem;
72
+ display: inline-block;
73
+ font-weight: 600; /* semibold */
74
+ color: #1d4ed8; /* blue-700 */
75
+ background-color: #eff6ff; /* blue-50 */
76
+ padding: 0.5rem 1rem; /* py-2 px-4 */
77
+ border-width: 1px;
78
+ border-color: transparent;
79
+ border-radius: 0.375rem; /* rounded-md */
80
+ cursor: pointer;
81
+ transition: background-color 0.2s ease-in-out;
82
  }
83
+
84
+ input[type="file"]::file-selector-button:hover {
85
+ background-color: #dbeafe; /* blue-100 */
 
86
  }
87
+
88
  </style>
89
  </head>
90
+ <body class="font-sans antialiased text-gray-800">
 
 
 
 
 
 
 
91
 
92
+ <div class="container mx-auto max-w-4xl p-6 md:p-10">
93
+
94
+ <header class="text-center mb-10">
95
+ <h1 class="text-3xl md:text-4xl font-bold text-gray-900 mb-2">Analyseur de Documents IA</h1>
96
+ <p class="text-md text-gray-600">Téléchargez une image ou un fichier texte pour obtenir une analyse détaillée.</p>
 
 
 
 
97
  </header>
98
 
99
+ <main>
100
+ <div class="bg-white p-6 md:p-8 rounded-xl shadow-lg border border-gray-200">
101
+ <form id="upload-form" enctype="multipart/form-data">
 
 
 
 
 
 
102
  <div class="mb-6">
103
+ <label for="file" class="block text-lg font-semibold text-gray-700 mb-2">1. Choisissez votre fichier :</label>
104
+ <input type="file" id="file" name="file" required
105
+ class="block w-full text-sm text-gray-600 bg-gray-50 border border-gray-300 rounded-lg cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent p-2">
106
+ <p class="mt-1 text-xs text-gray-500">Taille max : 16 Mo. Types supportés : Images (JPG, PNG, GIF), Texte (TXT).</p>
107
+ </div>
108
+
109
+ <div class="mb-8">
110
+ <label class="block text-lg font-semibold text-gray-700 mb-3">2. Quel type de document est-ce ?</label>
111
+ <div class="flex items-center space-x-6">
112
+ <label for="image" class="flex items-center cursor-pointer">
113
+ <input type="radio" id="image" name="fileType" value="image" checked
114
+ class="focus:ring-blue-500 text-blue-600">
115
+ <span class="ml-2 text-gray-700">Image Iconographique</span>
116
  </label>
117
+ <label for="text" class="flex items-center cursor-pointer">
118
+ <input type="radio" id="text" name="fileType" value="text"
119
+ class="focus:ring-blue-500 text-blue-600">
120
+ <span class="ml-2 text-gray-700">Texte</span>
 
 
121
  </label>
122
  </div>
123
  </div>
124
+
125
  <div class="text-center">
126
+ <button type="submit" id="submit-btn"
127
+ class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg shadow-md transition duration-300 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 disabled:opacity-50 disabled:cursor-not-allowed">
128
+ Lancer l'Analyse
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  </button>
130
  </div>
131
+ </form>
132
+
133
+ <!-- Loading Indicator -->
134
+ <div id="loading" class="hidden text-center my-8">
135
+ <div class="spinner" role="status">
136
+ <span class="sr-only">Loading...</span>
137
+ </div>
138
+ <p class="text-gray-600 mt-2 font-medium">Analyse en cours, veuillez patienter...</p>
139
+ </div>
140
+
141
+ <!-- Error Message Area -->
142
+ <div id="error-message" class="hidden mt-6 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg relative" role="alert">
143
+ <strong class="font-bold">Erreur :</strong>
144
+ <span class="block sm:inline" id="error-text"></span>
145
  </div>
146
+
147
  </div>
148
+
149
+ <!-- Results Area -->
150
+ <div id="results" class="hidden mt-10 bg-white p-6 md:p-8 rounded-xl shadow-lg border border-gray-200">
151
+ <h2 class="text-2xl font-semibold mb-6 text-gray-800 border-b pb-3">Résultats de l'Analyse</h2>
152
+
153
+ <!-- Container for optional image preview -->
154
+ <div id="uploaded-image-container" class="mb-6 hidden">
155
+ <h3 class="text-xl font-semibold text-gray-700 mb-3">Image Analysée :</h3>
156
+ <img id="uploaded-image" src="" alt="Image téléchargée" class="max-w-md mx-auto h-auto rounded-lg shadow-md border border-gray-200">
157
+ </div>
158
+
159
+ <!-- Container for the analysis text -->
160
+ <div id="analysis-container">
161
+ <h3 class="text-xl font-semibold text-gray-700 mb-3">Analyse Détaillée :</h3>
162
+ <div id="analysis-text" class="bg-gray-50 p-4 rounded-md border border-gray-200 text-sm md:text-base">
163
+ <!-- Analysis will be loaded here by JavaScript -->
164
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  </div>
166
  </div>
167
  </main>
168
+
169
+ <footer class="text-center mt-12 text-sm text-gray-500">
170
+ Propulsé par Google Gemini & Flask.
 
171
  </footer>
172
  </div>
173
 
 
174
  <script>
175
+ const uploadForm = document.getElementById('upload-form');
176
+ const submitButton = document.getElementById('submit-btn');
177
+ const loadingIndicator = document.getElementById('loading');
178
+ const resultsDiv = document.getElementById('results');
179
+ const analysisTextDiv = document.getElementById('analysis-text');
180
+ const uploadedImageContainer = document.getElementById('uploaded-image-container');
181
+ const uploadedImage = document.getElementById('uploaded-image');
182
+ const errorMessageDiv = document.getElementById('error-message');
183
+ const errorTextSpan = document.getElementById('error-text');
184
+
185
+ uploadForm.addEventListener('submit', async (event) => {
186
+ event.preventDefault(); // Prevent default form submission
187
+
188
+ // Reset UI
189
+ resultsDiv.classList.add('hidden');
190
+ errorMessageDiv.classList.add('hidden');
191
+ uploadedImageContainer.classList.add('hidden');
192
+ loadingIndicator.classList.remove('hidden');
193
+ submitButton.disabled = true;
194
+ submitButton.textContent = 'Analyse en cours...';
195
+
196
+ const formData = new FormData(uploadForm);
197
+ const fileInput = document.getElementById('file');
198
+ const fileType = formData.get('fileType'); // Get selected radio button value
199
+
200
+ // Basic client-side validation (optional, backend validation is key)
201
+ if (!fileInput.files || fileInput.files.length === 0) {
202
+ showError('Veuillez sélectionner un fichier.');
203
+ resetButton();
204
+ loadingIndicator.classList.add('hidden');
205
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  }
207
+
208
+
209
+ try {
210
+ const response = await fetch('/upload', {
211
+ method: 'POST',
212
+ body: formData // FormData handles headers automatically for multipart/form-data
213
+ });
214
+
215
+ loadingIndicator.classList.add('hidden'); // Hide loading indicator regardless of outcome
216
+
217
+ if (!response.ok) {
218
+ let errorMsg = `Erreur HTTP ${response.status}: ${response.statusText}`;
219
+ try {
220
+ const errorData = await response.json();
221
+ errorMsg = errorData.error || 'Une erreur inconnue est survenue côté serveur.';
222
+ } catch (e) {
223
+ // If response is not JSON, use the status text
 
 
 
224
  }
225
+ throw new Error(errorMsg);
226
  }
227
+
228
+ const result = await response.json();
229
+
230
+ if (result.success) {
231
+ analysisTextDiv.textContent = result.analysis; // Use textContent to prevent XSS with raw HTML
232
+
233
+ // Display image only if it was an image upload and a URL is provided
234
+ if (fileType === 'image' && result.file_url) {
235
+ uploadedImage.src = result.file_url + '?t=' + new Date().getTime(); // Add timestamp to bypass cache if needed
236
+ uploadedImageContainer.classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  } else {
238
+ uploadedImageContainer.classList.add('hidden'); // Ensure it's hidden for text uploads
 
 
239
  }
240
+
241
+ resultsDiv.classList.remove('hidden'); // Show results section
242
+ // Scroll to results smoothly
243
+ resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  } else {
245
+ throw new Error(result.error || 'Une erreur inattendue est survenue.');
 
246
  }
247
+
248
+ } catch (error) {
249
+ console.error('Upload Error:', error);
250
+ showError(error.message || 'Une erreur de communication est survenue.');
251
+ } finally {
252
+ resetButton();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  }
254
  });
255
+
256
+ function showError(message) {
257
+ errorTextSpan.textContent = message;
258
+ errorMessageDiv.classList.remove('hidden');
259
+ loadingIndicator.classList.add('hidden'); // Ensure loading is hidden on error
260
+ errorMessageDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
261
+ }
262
+
263
+ function resetButton() {
264
+ submitButton.disabled = false;
265
+ submitButton.textContent = "Lancer l'Analyse";
266
+ }
267
+
268
  </script>
269
+
270
  </body>
271
  </html>