Marsouuu commited on
Commit
fcd82b5
·
1 Parent(s): 2688dc4

Mise à jour de l'application avec toutes les fonctionnalités et améliorations

Browse files
.gitattributes CHANGED
@@ -1,35 +1,4 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.jpg filter=lfs diff=lfs merge=lfs -text
2
+ *.jpeg filter=lfs diff=lfs merge=lfs -text
3
+ *.png filter=lfs diff=lfs merge=lfs -text
4
+ *.pdf filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -1,12 +1,30 @@
1
  ---
2
- title: General Elixir Demo
3
- emoji: 🐢
4
  colorFrom: indigo
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 5.25.1
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Elixir - Document Intelligence
3
+ emoji: 📄
4
  colorFrom: indigo
5
+ colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 5.25.0
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # Elixir - Document Intelligence
14
+
15
+ Cette application démontre les capacités d'un modèle d'IA générative pour interpréter, comprendre et classifier tout type de document SANS PERSONNALISATION.
16
+
17
+ ## Fonctionnalités
18
+
19
+ - Téléchargement et analyse de documents PDF (1-10 pages)
20
+ - Extraction d'entités, de valeurs, de dates et de tableaux
21
+ - Affichage des points clés et des références
22
+ - Prévisualisation des documents téléchargés
23
+
24
+ ## Utilisation
25
+
26
+ 1. Téléchargez un document PDF (1-10 pages) tel qu'une facture, un document réglementaire, un rapport...
27
+ 2. Cliquez sur "Analyser document" pour lancer le traitement
28
+ 3. Consultez les résultats dans les différents onglets
29
+
30
+ Pour le développement d'un pipeline complet, précis et défini, veuillez contacter [email protected].
app.py ADDED
@@ -0,0 +1,1216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import google.generativeai as genai
3
+ import os
4
+ import json
5
+ import time
6
+ import base64
7
+ from PIL import Image
8
+ from pdf2image import convert_from_path
9
+
10
+ # Configuration
11
+ GOOGLE_API_KEY = "AIzaSyA4ma5pE1pPCzHHn-i9tDWuKqQEgSltMtI"
12
+ genai.configure(api_key=GOOGLE_API_KEY)
13
+ model = genai.GenerativeModel('gemini-1.5-flash')
14
+
15
+ # Interface text (English only)
16
+ TEXT = {
17
+ "title": "Elixir - Document Intelligence",
18
+ "description": "This demo showcases the capabilities of a generative AI model to interpret, understand, and classify any type of document WITHOUT CUSTOMIZATION. For developing a complete, precise, and defined pipeline, please contact [email protected].",
19
+ "instructions": [
20
+ "1. Upload a PDF document (1-10 pages) such as an invoice, regulatory document, report...",
21
+ "2. Processing by Elixir",
22
+ "3. Transcription of identified sections and elements (without customization)"
23
+ ],
24
+ "upload": "📂 Upload your document",
25
+ "analyze": "🔍 Analyze document",
26
+ "preview": "📄 Preview",
27
+ "tabs": {
28
+ "overview": "📋 Overview",
29
+ "entities": "👥 Entities",
30
+ "values": "💰 Values",
31
+ "dates": "📅 Dates",
32
+ "tables": "📊 Tables",
33
+ "keypoints": "🔑 Key Points",
34
+ "references": "🔗 References",
35
+ "json": "📄 Complete JSON"
36
+ },
37
+ "no_data": "No information found",
38
+ "processing": "Processing...",
39
+ "error": {
40
+ "file_not_found": "File not found",
41
+ "pdf_conversion": "Unable to convert PDF to image",
42
+ "no_info": "No information extracted from PDF pages",
43
+ "too_many_pages": "The PDF has more than 10 pages. Please upload a document with 10 pages or less."
44
+ }
45
+ }
46
+
47
+ # Modern CSS - Style amélioré
48
+ CSS = """
49
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
50
+
51
+ :root {
52
+ --primary: #4f46e5;
53
+ --primary-light: #818cf8;
54
+ --primary-dark: #3730a3;
55
+ --secondary: #10b981;
56
+ --accent: #f59e0b;
57
+ --dark: #111827;
58
+ --light: #f9fafb;
59
+ --gray-50: #f8fafc;
60
+ --gray-100: #f1f5f9;
61
+ --gray-200: #e2e8f0;
62
+ --gray-300: #cbd5e1;
63
+ --gray-400: #94a3b8;
64
+ --gray-500: #64748b;
65
+ --text-primary: #1e293b;
66
+ --text-secondary: #475569;
67
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
68
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
69
+ --shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
70
+ --radius-sm: 0.25rem;
71
+ --radius: 0.5rem;
72
+ --radius-md: 0.75rem;
73
+ --radius-lg: 1rem;
74
+ }
75
+
76
+ body, .gradio-container {
77
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
78
+ color: var(--text-primary);
79
+ background-color: var(--light);
80
+ line-height: 1.6;
81
+ }
82
+
83
+ /* Layout principal */
84
+ .container {
85
+ max-width: 1300px;
86
+ margin: 0 auto;
87
+ padding: 0 1rem;
88
+ }
89
+
90
+ .main-content {
91
+ display: flex;
92
+ gap: 2rem;
93
+ align-items: flex-start;
94
+ }
95
+
96
+ .left-panel {
97
+ flex: 1;
98
+ }
99
+
100
+ .right-panel {
101
+ flex: 2;
102
+ }
103
+
104
+ /* En-tête */
105
+ .header {
106
+ margin-bottom: 2rem;
107
+ padding: 0.75rem 1.25rem;
108
+ background: linear-gradient(135deg, var(--primary-light), var(--primary-dark));
109
+ border-radius: var(--radius-lg);
110
+ box-shadow: var(--shadow-md);
111
+ position: relative;
112
+ overflow: hidden;
113
+ color: white;
114
+ height: 60px;
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ }
119
+
120
+ .header::before {
121
+ content: '';
122
+ position: absolute;
123
+ top: -50%;
124
+ left: -50%;
125
+ width: 200%;
126
+ height: 200%;
127
+ background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 60%);
128
+ animation: pulse 15s ease-in-out infinite;
129
+ z-index: 1;
130
+ }
131
+
132
+ @keyframes pulse {
133
+ 0% { transform: scale(1); opacity: 0.5; }
134
+ 50% { transform: scale(1.05); opacity: 0.8; }
135
+ 100% { transform: scale(1); opacity: 0.5; }
136
+ }
137
+
138
+ .header img {
139
+ max-height: 40px !important;
140
+ object-fit: contain;
141
+ position: relative;
142
+ z-index: 2;
143
+ }
144
+
145
+ /* Intro card */
146
+ .intro-card {
147
+ background: white;
148
+ border-radius: var(--radius);
149
+ box-shadow: var(--shadow);
150
+ border: 1px solid var(--gray-200);
151
+ overflow: hidden;
152
+ margin-bottom: 1.5rem;
153
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
154
+ }
155
+
156
+ .intro-card:hover {
157
+ transform: translateY(-3px);
158
+ box-shadow: var(--shadow-md);
159
+ }
160
+
161
+ .intro-header {
162
+ padding: 1.25rem;
163
+ border-bottom: 1px solid var(--gray-200);
164
+ background: linear-gradient(135deg, var(--primary-light), var(--primary-dark));
165
+ color: white;
166
+ font-weight: 600;
167
+ display: flex;
168
+ align-items: center;
169
+ gap: 0.5rem;
170
+ }
171
+
172
+ .intro-header h3 {
173
+ margin: 0;
174
+ font-size: 1.25rem;
175
+ font-weight: 600;
176
+ text-shadow: 0 1px 2px rgba(0,0,0,0.1);
177
+ }
178
+
179
+ .intro-body {
180
+ padding: 1.5rem;
181
+ }
182
+
183
+ .intro-description {
184
+ color: var(--text-primary);
185
+ line-height: 1.7;
186
+ font-size: 1.05rem;
187
+ margin-bottom: 1.5rem;
188
+ }
189
+
190
+ .contact-links {
191
+ display: flex;
192
+ flex-wrap: wrap;
193
+ gap: 1rem;
194
+ margin-top: 1.5rem;
195
+ background: linear-gradient(to right, rgba(79, 70, 229, 0.05), rgba(79, 70, 229, 0.1));
196
+ padding: 1.25rem;
197
+ border-radius: var(--radius);
198
+ border: 1px solid var(--gray-200);
199
+ }
200
+
201
+ .contact-link {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 0.5rem;
205
+ padding: 0.75rem 1rem;
206
+ background: white;
207
+ border-radius: var(--radius);
208
+ color: var(--primary);
209
+ text-decoration: none;
210
+ font-weight: 500;
211
+ transition: all 0.2s ease;
212
+ box-shadow: var(--shadow-sm);
213
+ border: 1px solid var(--gray-200);
214
+ }
215
+
216
+ .contact-link:hover {
217
+ transform: translateY(-2px);
218
+ box-shadow: var(--shadow);
219
+ color: var(--primary-dark);
220
+ border-color: var(--primary-light);
221
+ }
222
+
223
+ /* Accordéon pour workflow */
224
+ .accordion {
225
+ border-radius: var(--radius);
226
+ overflow: hidden;
227
+ margin-bottom: 1.5rem;
228
+ }
229
+
230
+ .accordion-header {
231
+ background: var(--gray-50);
232
+ padding: 1.25rem;
233
+ cursor: pointer;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: space-between;
237
+ font-weight: 600;
238
+ color: var(--primary);
239
+ border: 1px solid var(--gray-200);
240
+ border-radius: var(--radius);
241
+ transition: all 0.3s ease;
242
+ }
243
+
244
+ .accordion-header:hover {
245
+ background: var(--gray-100);
246
+ }
247
+
248
+ .accordion-header::after {
249
+ content: "↓";
250
+ transition: transform 0.3s ease;
251
+ }
252
+
253
+ .accordion.active .accordion-header::after {
254
+ transform: rotate(180deg);
255
+ }
256
+
257
+ .accordion-content {
258
+ max-height: 0;
259
+ overflow: hidden;
260
+ transition: max-height 0.3s ease;
261
+ background: white;
262
+ border: 1px solid var(--gray-200);
263
+ border-top: 0;
264
+ border-radius: 0 0 var(--radius) var(--radius);
265
+ padding: 0 1.25rem;
266
+ }
267
+
268
+ .accordion.active .accordion-content {
269
+ max-height: 1000px;
270
+ padding: 1.25rem;
271
+ }
272
+
273
+ .workflow-container {
274
+ text-align: center;
275
+ }
276
+
277
+ .workflow-container img {
278
+ max-width: 100%;
279
+ border-radius: var(--radius);
280
+ box-shadow: var(--shadow);
281
+ margin-top: 1rem;
282
+ }
283
+
284
+ /* Instructions */
285
+ .instructions {
286
+ background: white;
287
+ padding: 1.5rem;
288
+ border-radius: var(--radius);
289
+ border: 1px solid var(--gray-200);
290
+ box-shadow: var(--shadow);
291
+ margin-bottom: 2rem;
292
+ }
293
+
294
+ .instructions h3 {
295
+ color: var(--primary);
296
+ margin-top: 0;
297
+ margin-bottom: 1rem;
298
+ font-weight: 600;
299
+ font-size: 1.25rem;
300
+ display: flex;
301
+ align-items: center;
302
+ gap: 0.5rem;
303
+ }
304
+
305
+ .instructions h3::before {
306
+ content: '📋';
307
+ }
308
+
309
+ .instructions ol {
310
+ margin: 0;
311
+ padding-left: 1.5rem;
312
+ }
313
+
314
+ .instructions li {
315
+ margin-bottom: 0.75rem;
316
+ position: relative;
317
+ padding-left: 0.5rem;
318
+ }
319
+
320
+ .instructions li:last-child {
321
+ margin-bottom: 0;
322
+ }
323
+
324
+ /* Upload section */
325
+ .upload-section {
326
+ background: white;
327
+ border-radius: var(--radius);
328
+ box-shadow: var(--shadow);
329
+ border: 1px solid var(--gray-200);
330
+ padding: 1.5rem;
331
+ }
332
+
333
+ /* File input styling */
334
+ .file-container {
335
+ border: 2px dashed var(--primary-light) !important;
336
+ border-radius: var(--radius) !important;
337
+ padding: 2rem !important;
338
+ text-align: center !important;
339
+ transition: all 0.3s ease !important;
340
+ background-color: rgba(79, 70, 229, 0.05) !important;
341
+ cursor: pointer !important;
342
+ position: relative;
343
+ }
344
+
345
+ .file-container:hover {
346
+ background-color: rgba(79, 70, 229, 0.1) !important;
347
+ }
348
+
349
+ .file-container::before {
350
+ content: "📄";
351
+ font-size: 2rem;
352
+ display: block;
353
+ margin-bottom: 0.5rem;
354
+ }
355
+
356
+ button.primary {
357
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark)) !important;
358
+ color: white !important;
359
+ border: none !important;
360
+ padding: 0.75rem 1.5rem !important;
361
+ font-weight: 600 !important;
362
+ border-radius: var(--radius) !important;
363
+ transition: all 0.3s ease !important;
364
+ box-shadow: 0 4px 6px rgba(79, 70, 229, 0.25) !important;
365
+ width: 100% !important;
366
+ margin-top: 1rem !important;
367
+ }
368
+
369
+ button.primary:hover {
370
+ transform: translateY(-2px) !important;
371
+ box-shadow: 0 7px 14px rgba(79, 70, 229, 0.3) !important;
372
+ }
373
+
374
+ /* Results tabs */
375
+ .tabs .tab-nav {
376
+ background-color: var(--gray-50) !important;
377
+ padding: 0.5rem !important;
378
+ border-radius: var(--radius) var(--radius) 0 0 !important;
379
+ border: 1px solid var(--gray-200) !important;
380
+ border-bottom: none !important;
381
+ }
382
+
383
+ .tabs .tab-nav button {
384
+ margin: 0 !important;
385
+ padding: 0.75rem 1rem !important;
386
+ font-weight: 500 !important;
387
+ color: var(--text-secondary) !important;
388
+ position: relative !important;
389
+ transition: all 0.3s ease !important;
390
+ }
391
+
392
+ .tabs .tab-nav button.selected {
393
+ color: var(--primary) !important;
394
+ font-weight: 600 !important;
395
+ }
396
+
397
+ .tabs .tab-nav button.selected::after {
398
+ content: '';
399
+ position: absolute;
400
+ bottom: -0.5rem;
401
+ left: 0;
402
+ width: 100%;
403
+ height: 3px;
404
+ background: var(--primary);
405
+ border-radius: 3px 3px 0 0;
406
+ }
407
+
408
+ .tabs .tabitem {
409
+ background: white !important;
410
+ padding: 1.5rem !important;
411
+ border-radius: 0 0 var(--radius) var(--radius) !important;
412
+ border: 1px solid var(--gray-200) !important;
413
+ box-shadow: var(--shadow) !important;
414
+ }
415
+
416
+ /* Card components */
417
+ .info-card {
418
+ background: white;
419
+ padding: 0;
420
+ border-radius: var(--radius);
421
+ margin-bottom: 1.5rem;
422
+ border: 1px solid var(--gray-200);
423
+ box-shadow: var(--shadow);
424
+ overflow: hidden;
425
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
426
+ }
427
+
428
+ .info-card:hover {
429
+ transform: translateY(-2px);
430
+ box-shadow: var(--shadow-md);
431
+ }
432
+
433
+ .info-card h3 {
434
+ margin: 0;
435
+ color: white;
436
+ font-size: 1.1rem;
437
+ font-weight: 600;
438
+ padding: 1rem 1.5rem;
439
+ background: linear-gradient(135deg, var(--primary-light), var(--primary-dark));
440
+ position: relative;
441
+ }
442
+
443
+ .info-card .content {
444
+ padding: 1.25rem;
445
+ }
446
+
447
+ /* Formatage des listes dans les cartes */
448
+ .list-container {
449
+ display: flex;
450
+ flex-direction: column;
451
+ gap: 1rem;
452
+ }
453
+
454
+ .list-item {
455
+ padding: 1rem;
456
+ background: var(--gray-50);
457
+ border-radius: var(--radius);
458
+ border: 1px solid var(--gray-200);
459
+ transition: all 0.2s ease;
460
+ }
461
+
462
+ .list-item:hover {
463
+ background: white;
464
+ border-color: var(--primary-light);
465
+ box-shadow: var(--shadow-sm);
466
+ }
467
+
468
+ .list-item-header {
469
+ font-weight: 600;
470
+ color: var(--primary);
471
+ margin-bottom: 0.5rem;
472
+ display: flex;
473
+ align-items: center;
474
+ gap: 0.5rem;
475
+ }
476
+
477
+ .list-item-header::before {
478
+ content: '•';
479
+ color: var(--primary);
480
+ font-size: 1.5rem;
481
+ line-height: 1;
482
+ }
483
+
484
+ .list-item-content {
485
+ color: var(--text-secondary);
486
+ font-size: 0.95rem;
487
+ }
488
+
489
+ /* Améliorations tables */
490
+ .tables-container {
491
+ display: flex;
492
+ flex-direction: column;
493
+ gap: 2rem;
494
+ }
495
+
496
+ .table-wrapper {
497
+ overflow: hidden;
498
+ border-radius: var(--radius);
499
+ box-shadow: var(--shadow);
500
+ background: white;
501
+ }
502
+
503
+ .table-wrapper h4 {
504
+ padding: 1rem;
505
+ margin: 0;
506
+ background: linear-gradient(to right, var(--primary-light), var(--primary));
507
+ color: white;
508
+ font-weight: 600;
509
+ }
510
+
511
+ .table-description {
512
+ margin: 0;
513
+ padding: 0.75rem 1rem;
514
+ background: var(--gray-50);
515
+ color: var(--text-secondary);
516
+ border-bottom: 1px solid var(--gray-200);
517
+ font-size: 0.9rem;
518
+ font-style: italic;
519
+ }
520
+
521
+ .data-table {
522
+ width: 100%;
523
+ border-collapse: collapse;
524
+ font-size: 0.95rem;
525
+ }
526
+
527
+ .data-table th {
528
+ background: var(--gray-100);
529
+ padding: 0.75rem 1rem;
530
+ text-align: left;
531
+ font-weight: 600;
532
+ color: var(--primary-dark);
533
+ border-bottom: 2px solid var(--primary-light);
534
+ }
535
+
536
+ .data-table td {
537
+ padding: 0.75rem 1rem;
538
+ border-bottom: 1px solid var(--gray-200);
539
+ color: var(--text-secondary);
540
+ }
541
+
542
+ .data-table tr:last-child td {
543
+ border-bottom: none;
544
+ }
545
+
546
+ .data-table tr:nth-child(even) {
547
+ background-color: var(--gray-50);
548
+ }
549
+
550
+ .data-table tr:hover {
551
+ background-color: rgba(79, 70, 229, 0.05);
552
+ }
553
+
554
+ /* Metadata grid */
555
+ .metadata-grid {
556
+ display: grid;
557
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
558
+ gap: 1rem;
559
+ }
560
+
561
+ .metadata-item {
562
+ background: var(--gray-50);
563
+ padding: 1rem;
564
+ border-radius: var(--radius);
565
+ border: 1px solid var(--gray-200);
566
+ transition: all 0.2s ease;
567
+ }
568
+
569
+ .metadata-item:hover {
570
+ background: white;
571
+ border-color: var(--primary-light);
572
+ box-shadow: var(--shadow-sm);
573
+ }
574
+
575
+ .metadata-item h4 {
576
+ margin: 0 0 0.5rem 0;
577
+ color: var(--primary);
578
+ font-weight: 600;
579
+ font-size: 0.9rem;
580
+ text-transform: uppercase;
581
+ letter-spacing: 0.5px;
582
+ }
583
+
584
+ .metadata-item p {
585
+ margin: 0;
586
+ color: var(--text-primary);
587
+ font-weight: 500;
588
+ }
589
+
590
+ /* JSON viewer */
591
+ .json-viewer {
592
+ background: var(--dark);
593
+ color: #e2e8f0;
594
+ padding: 1.25rem;
595
+ border-radius: var(--radius);
596
+ overflow: auto;
597
+ font-family: 'Fira Code', 'Courier New', monospace;
598
+ font-size: 0.9rem;
599
+ line-height: 1.5;
600
+ max-height: 400px;
601
+ white-space: pre-wrap;
602
+ }
603
+
604
+ /* Loading animation */
605
+ .loading-spinner {
606
+ display: inline-block;
607
+ width: 50px;
608
+ height: 50px;
609
+ border: 3px solid rgba(79, 70, 229, 0.3);
610
+ border-radius: 50%;
611
+ border-top-color: var(--primary);
612
+ animation: spin 1s ease-in-out infinite;
613
+ }
614
+
615
+ @keyframes spin {
616
+ to { transform: rotate(360deg); }
617
+ }
618
+
619
+ /* Error message */
620
+ .error {
621
+ padding: 1rem;
622
+ background-color: #fee2e2;
623
+ border: 1px solid #fecaca;
624
+ border-radius: var(--radius);
625
+ color: #b91c1c;
626
+ font-weight: 500;
627
+ }
628
+
629
+ /* Responsive design */
630
+ @media (max-width: 1024px) {
631
+ .main-content {
632
+ flex-direction: column;
633
+ }
634
+
635
+ .left-panel, .right-panel {
636
+ flex: none;
637
+ width: 100%;
638
+ }
639
+ }
640
+ """
641
+
642
+ # Prompt pour Gemini avec instruction améliorée pour les tableaux
643
+ GEMINI_PROMPT = """
644
+ Analyze this document and extract relevant information in JSON format. Adapt the extraction based on the document type (invoice, contract, report, KID, etc.).
645
+
646
+ Expected response structure:
647
+ {
648
+ "metadata": {
649
+ "title": "Document title",
650
+ "date": "Document date",
651
+ "type": "Document type",
652
+ "author": "Document author or issuer"
653
+ },
654
+ "entities": [
655
+ {
656
+ "name": "Entity name",
657
+ "type": "Entity type (person, organization, etc.)",
658
+ "role": "Role in the document"
659
+ }
660
+ ],
661
+ "values": [
662
+ {
663
+ "description": "Value description",
664
+ "value": "Exact value",
665
+ "unit": "Unit if applicable"
666
+ }
667
+ ],
668
+ "dates": [
669
+ {
670
+ "description": "Date description",
671
+ "date": "Exact date",
672
+ "importance": "Importance (high, medium, low)"
673
+ }
674
+ ],
675
+ "tables": [
676
+ {
677
+ "title": "Table title",
678
+ "description": "Table description",
679
+ "data": [
680
+ {
681
+ "column1": "Value in row 1, column 1",
682
+ "column2": "Value in row 1, column 2",
683
+ "column3": "Value in row 1, column 3"
684
+ },
685
+ {
686
+ "column1": "Value in row 2, column 1",
687
+ "column2": "Value in row 2, column 2",
688
+ "column3": "Value in row 2, column 3"
689
+ }
690
+ ]
691
+ }
692
+ ],
693
+ "key_points": [
694
+ {
695
+ "category": "Key point category",
696
+ "description": "Detailed description",
697
+ "importance": "Importance (high, medium, low)"
698
+ }
699
+ ],
700
+ "references": [
701
+ {
702
+ "type": "Reference type",
703
+ "value": "Reference value"
704
+ }
705
+ ]
706
+ }
707
+
708
+ Important instructions:
709
+ 1. First identify the document type and adapt the extraction accordingly
710
+ 2. For tables (this is EXTREMELY important):
711
+ - Pay special attention to detect and extract ALL tables in the document
712
+ - Carefully identify tables even if they don't have visible borders or lines
713
+ - Identify column headers correctly (first row or separate header row)
714
+ - Extract all rows and all columns with exact cell values
715
+ - Maintain the same number of columns for each row
716
+ - Preserve the exact structure of each table
717
+ - For each table, provide a descriptive title based on content
718
+ - For each table, include a brief description explaining what the table contains
719
+ - If a table spans multiple pages, try to reconstruct it as one table
720
+ - Include ALL data from the table, don't omit any rows or columns
721
+ 3. For values:
722
+ - Extract amounts, percentages, numbers
723
+ - Include units when present
724
+ 4. For dates:
725
+ - Extract all important dates
726
+ - Include the context of each date
727
+ 5. For entities:
728
+ - Identify people, organizations, locations
729
+ - Include their role in the document
730
+ 6. For references:
731
+ - Extract reference numbers, codes, identifiers
732
+ 7. For key points:
733
+ - Identify important information based on document type
734
+ - Categorize them appropriately
735
+
736
+ General rules:
737
+ - Respond only with JSON, without any additional text
738
+ - Extract only factual and verifiable information
739
+ - Be precise with values and dates
740
+ - If a category is not relevant for the document, leave an empty array
741
+ - Adapt categories based on document type
742
+ - Do not make assumptions about missing data
743
+ """
744
+
745
+ def create_info_card(title, content):
746
+ """Create a formatted information card"""
747
+ if not content:
748
+ return f"""
749
+ <div class="info-card">
750
+ <h3>{title}</h3>
751
+ <div class="content">
752
+ <p>{TEXT["no_data"]}</p>
753
+ </div>
754
+ </div>
755
+ """
756
+ return f"""
757
+ <div class="info-card">
758
+ <h3>{title}</h3>
759
+ <div class="content">
760
+ {content}
761
+ </div>
762
+ </div>
763
+ """
764
+
765
+ def format_list(items, key1, key2):
766
+ """Format a list of items with two keys"""
767
+ if not items:
768
+ return TEXT["no_data"]
769
+ html = "<div class='list-container'>"
770
+ for item in items:
771
+ html += f"""
772
+ <div class='list-item'>
773
+ <div class='list-item-header'>{item[key1]}</div>
774
+ <div class='list-item-content'>{item[key2]}</div>
775
+ </div>
776
+ """
777
+ html += "</div>"
778
+ return html
779
+
780
+ def format_table(table_data):
781
+ """Format a table in HTML"""
782
+ if not table_data:
783
+ return TEXT["no_data"]
784
+
785
+ html = "<div class='tables-container'>"
786
+
787
+ try:
788
+ for table in table_data:
789
+ # Vérifier si la table a des données
790
+ if not table.get('data') or len(table['data']) == 0:
791
+ continue
792
+
793
+ title = table.get('title', 'Tableau sans titre')
794
+ description = table.get('description', '')
795
+
796
+ html += f"""
797
+ <div class='table-wrapper'>
798
+ <h4>{title}</h4>
799
+ <p class='table-description'>{description}</p>
800
+ <table class='data-table'>
801
+ """
802
+
803
+ # Vérifier le format des données
804
+ first_row = table['data'][0]
805
+ if isinstance(first_row, dict):
806
+ # Extraire les en-têtes du premier élément
807
+ headers = list(first_row.keys())
808
+
809
+ # Ajouter les en-têtes
810
+ html += "<tr>"
811
+ for header in headers:
812
+ html += f"<th>{header}</th>"
813
+ html += "</tr>"
814
+
815
+ # Ajouter les lignes de données
816
+ for row in table['data']:
817
+ html += "<tr>"
818
+ for key in headers:
819
+ value = row.get(key, "")
820
+ html += f"<td>{value}</td>"
821
+ html += "</tr>"
822
+ elif isinstance(first_row, list):
823
+ # Traiter les données au format liste
824
+ for row in table['data']:
825
+ html += "<tr>"
826
+ for cell in row:
827
+ html += f"<td>{cell}</td>"
828
+ html += "</tr>"
829
+
830
+ html += "</table></div>"
831
+ except Exception as e:
832
+ print(f"Erreur lors du formatage des tableaux: {str(e)}")
833
+ html += f"""
834
+ <div class='error'>
835
+ Erreur lors de l'affichage des tableaux. Veuillez vérifier le format JSON.
836
+ </div>
837
+ """
838
+
839
+ html += "</div>"
840
+
841
+ if html == "<div class='tables-container'></div>":
842
+ return TEXT["no_data"]
843
+
844
+ return html
845
+
846
+ def process_single_image(image):
847
+ """Process a single image and extract information"""
848
+ response = model.generate_content(
849
+ [GEMINI_PROMPT, image],
850
+ generation_config={
851
+ "temperature": 0.1,
852
+ "top_p": 0.8,
853
+ "top_k": 40,
854
+ "max_output_tokens": 2048,
855
+ }
856
+ )
857
+
858
+ try:
859
+ response_text = response.text.strip()
860
+ if response_text.startswith("```json"):
861
+ response_text = response_text.replace("```json", "").replace("```", "").strip()
862
+ elif response_text.startswith("```"):
863
+ response_text = response_text.replace("```", "").strip()
864
+
865
+ json_data = json.loads(response_text)
866
+
867
+ # Vérifier et corriger le format des tableaux si nécessaire
868
+ if "tables" in json_data and json_data["tables"]:
869
+ for i, table in enumerate(json_data["tables"]):
870
+ if "data" not in table or not table["data"]:
871
+ table["data"] = []
872
+
873
+ # S'assurer que la table a un titre
874
+ if "title" not in table or not table["title"]:
875
+ table["title"] = f"Tableau {i+1}"
876
+
877
+ # S'assurer que la table a une description
878
+ if "description" not in table:
879
+ table["description"] = ""
880
+
881
+ return json_data
882
+ except Exception as e:
883
+ print(f"Error parsing JSON: {str(e)}")
884
+ return {"error": str(e)}
885
+
886
+ def merge_results(results):
887
+ """Merge multiple results into one"""
888
+ if not results:
889
+ return None
890
+
891
+ merged = {
892
+ "metadata": {},
893
+ "entities": [],
894
+ "values": [],
895
+ "dates": [],
896
+ "tables": [],
897
+ "key_points": [],
898
+ "references": []
899
+ }
900
+
901
+ # Merge metadata (take from first result with data)
902
+ for result in results:
903
+ if "metadata" in result and result["metadata"]:
904
+ merged["metadata"] = result["metadata"]
905
+ break
906
+
907
+ # Merge lists
908
+ for result in results:
909
+ for category in ["entities", "values", "dates", "tables", "key_points", "references"]:
910
+ if category in result and result[category]:
911
+ merged[category].extend(result[category])
912
+
913
+ return merged
914
+
915
+ def process_document(file, progress=gr.Progress()):
916
+ """Process a document and extract information"""
917
+ if not file:
918
+ return {"error": TEXT["error"]["file_not_found"]}
919
+
920
+ try:
921
+ if file.name.lower().endswith('.pdf'):
922
+ images = convert_from_path(file.name)
923
+
924
+ if len(images) > 10:
925
+ return {"error": TEXT["error"]["too_many_pages"]}
926
+
927
+ results = []
928
+ for i, image in enumerate(images):
929
+ progress(i / len(images), desc=TEXT["processing"])
930
+ result = process_single_image(image)
931
+ if result and "error" not in result:
932
+ results.append(result)
933
+
934
+ if results:
935
+ return merge_results(results)
936
+ else:
937
+ return {"error": TEXT["error"]["no_info"]}
938
+
939
+ elif file.name.lower().endswith(('.png', '.jpg', '.jpeg')):
940
+ image = Image.open(file.name)
941
+ return process_single_image(image)
942
+
943
+ else:
944
+ return {"error": TEXT["error"]["file_not_found"]}
945
+
946
+ except Exception as e:
947
+ print(f"Error processing document: {str(e)}")
948
+ return {"error": str(e)}
949
+
950
+ def update_preview(file):
951
+ """Update the preview with the uploaded file"""
952
+ if not file:
953
+ return []
954
+
955
+ if file.name.lower().endswith('.pdf'):
956
+ try:
957
+ # Just show first 3 pages
958
+ images = convert_from_path(file.name, first_page=1, last_page=3)
959
+ image_paths = []
960
+
961
+ for i, img in enumerate(images):
962
+ temp_filename = f"temp_preview_{i}.jpg"
963
+ img.save(temp_filename)
964
+ image_paths.append(temp_filename)
965
+
966
+ return image_paths
967
+ except:
968
+ return []
969
+ elif file.name.lower().endswith(('.png', '.jpg', '.jpeg')):
970
+ return [file.name]
971
+ else:
972
+ return []
973
+
974
+ def process_and_display(file):
975
+ """Process document and display results in the interface"""
976
+ if not file:
977
+ return [f"<div class='error'>{TEXT['error']['file_not_found']}</div>"] * 8
978
+
979
+ result = process_document(file)
980
+
981
+ if "error" in result:
982
+ error_msg = result["error"]
983
+ if error_msg in TEXT["error"]:
984
+ error_msg = TEXT["error"][error_msg]
985
+ return [f"<div class='error'>{error_msg}</div>"] * 8
986
+
987
+ # Format metadata as HTML
988
+ metadata_html = "<div class='metadata-grid'>"
989
+ if "metadata" in result and result["metadata"]:
990
+ for key, value in result["metadata"].items():
991
+ metadata_html += f"""
992
+ <div class='metadata-item'>
993
+ <h4>{key}</h4>
994
+ <p>{value}</p>
995
+ </div>
996
+ """
997
+ else:
998
+ metadata_html += f"<p>{TEXT['no_data']}</p>"
999
+ metadata_html += "</div>"
1000
+
1001
+ # Format JSON data
1002
+ json_html = f"<pre class='json-viewer'>{json.dumps(result, indent=2, ensure_ascii=False)}</pre>"
1003
+
1004
+ # Initialize all tabs with default values
1005
+ outputs = [
1006
+ metadata_html,
1007
+ create_info_card(TEXT["tabs"]["entities"], format_list(result.get("entities", []), "name", "role")),
1008
+ create_info_card(TEXT["tabs"]["values"], format_list(result.get("values", []), "description", "value")),
1009
+ create_info_card(TEXT["tabs"]["dates"], format_list(result.get("dates", []), "description", "date")),
1010
+ create_info_card(TEXT["tabs"]["tables"], format_table(result.get("tables", []))),
1011
+ create_info_card(TEXT["tabs"]["keypoints"], format_list(result.get("key_points", []), "category", "description")),
1012
+ create_info_card(TEXT["tabs"]["references"], format_list(result.get("references", []), "type", "value")),
1013
+ json_html
1014
+ ]
1015
+
1016
+ return outputs
1017
+
1018
+ # Fonction pour encoder les images en base64
1019
+ def get_image_base64(file_path):
1020
+ try:
1021
+ with open(file_path, "rb") as image_file:
1022
+ encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
1023
+ return encoded_string
1024
+ except Exception as e:
1025
+ print(f"Erreur lors de l'encodage de l'image {file_path}: {str(e)}")
1026
+ return ""
1027
+
1028
+ # Chemins vers les images
1029
+ logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static", "elixir-logo-typo.png")
1030
+ workflow_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static", "Editor _ Mermaid Chart-2025-04-15-142548.png")
1031
+
1032
+ # Encoder les images en base64
1033
+ logo_base64 = get_image_base64(logo_path)
1034
+ workflow_base64 = get_image_base64(workflow_path)
1035
+
1036
+ # Logo et workflow HTML
1037
+ logo_html = f"""<div class="header">
1038
+ <img src="data:image/png;base64,{logo_base64}" alt="Elixir Logo" style="max-height: 40px; position: relative; z-index: 2;">
1039
+ </div>"""
1040
+
1041
+ workflow_html = f"""<div class="workflow-container">
1042
+ <img src="data:image/png;base64,{workflow_base64}" alt="Elixir Workflow" style="max-width: 100%; border-radius: 0.5rem;">
1043
+ </div>"""
1044
+
1045
+ # Ajouter du JavaScript pour l'accordéon et autres interactivités
1046
+ js_code = """
1047
+ <script>
1048
+ document.addEventListener('DOMContentLoaded', function() {
1049
+ // Accordéon
1050
+ const accordions = document.querySelectorAll('.accordion-header');
1051
+ accordions.forEach(accordion => {
1052
+ accordion.addEventListener('click', function() {
1053
+ this.parentElement.classList.toggle('active');
1054
+ });
1055
+ });
1056
+
1057
+ // Animation des cartes au survol
1058
+ const cards = document.querySelectorAll('.card');
1059
+ cards.forEach(card => {
1060
+ card.addEventListener('mouseenter', function() {
1061
+ this.style.transform = 'translateY(-5px)';
1062
+ this.style.boxShadow = '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)';
1063
+ });
1064
+ card.addEventListener('mouseleave', function() {
1065
+ this.style.transform = 'translateY(0)';
1066
+ this.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)';
1067
+ });
1068
+ });
1069
+ });
1070
+ </script>
1071
+ """
1072
+
1073
+ # Interface Gradio améliorée
1074
+ with gr.Blocks(css=CSS, theme=gr.themes.Soft()) as demo:
1075
+ gr.HTML(js_code) # Ajouter le JavaScript
1076
+
1077
+ # En-tête avec logo
1078
+ header = gr.HTML(logo_html)
1079
+
1080
+ # Première rangée: Document Intelligence + How Elixir Works
1081
+ with gr.Row(equal_height=True):
1082
+ # Document Intelligence à gauche
1083
+ with gr.Column(scale=1):
1084
+ gr.HTML(f"""
1085
+ <div class="intro-card">
1086
+ <div class="intro-header">
1087
+ <h3>📄 Document Intelligence</h3>
1088
+ </div>
1089
+ <div class="intro-body">
1090
+ <div class="intro-description">
1091
+ {TEXT["description"]}
1092
+ </div>
1093
+
1094
+ <div class="contact-links">
1095
+ <a href="https://lexiapro.fr/" target="_blank" class="contact-link">
1096
+ 🌐 Visit lexiapro.fr
1097
+ </a>
1098
+ <a href="mailto:[email protected]" class="contact-link">
1099
+ ✉️ Contact us
1100
+ </a>
1101
+ </div>
1102
+ </div>
1103
+ </div>
1104
+ """)
1105
+
1106
+ # How Elixir Works à droite
1107
+ with gr.Column(scale=1):
1108
+ gr.HTML(f"""
1109
+ <div class="intro-card">
1110
+ <div class="intro-header">
1111
+ <h3>🔄 How Elixir Works</h3>
1112
+ </div>
1113
+ <div class="intro-body">
1114
+ {workflow_html}
1115
+ </div>
1116
+ </div>
1117
+ """)
1118
+
1119
+ # Deuxième rangée: Interface d'utilisation avec input à gauche et output à droite
1120
+ with gr.Row():
1121
+ # Colonne de gauche: Instructions et upload
1122
+ with gr.Column(scale=1):
1123
+ # Instructions
1124
+ gr.HTML("""
1125
+ <div class="instructions">
1126
+ <h3>How to use Elixir</h3>
1127
+ <ol>
1128
+ <li>Upload a PDF document (1-10 pages) such as an invoice, regulatory document, report...</li>
1129
+ <li>Processing by Elixir</li>
1130
+ <li>Transcription of identified sections and elements (without customization)</li>
1131
+ </ol>
1132
+ </div>
1133
+ """)
1134
+
1135
+ # Section de téléchargement
1136
+ with gr.Group(elem_classes=["upload-section"]):
1137
+ file_input = gr.File(label=TEXT["upload"], file_types=[".pdf", ".png", ".jpg", ".jpeg"], elem_classes=["file-container"])
1138
+ submit_btn = gr.Button(TEXT["analyze"], variant="primary", elem_classes=["primary"])
1139
+ preview = gr.Gallery(label=TEXT["preview"], show_label=True, elem_id="preview-gallery")
1140
+
1141
+ # Colonne de droite: Résultats et JSON
1142
+ with gr.Column(scale=1):
1143
+ # Onglets de r��sultats
1144
+ with gr.Tabs(elem_classes=["tabs"]) as tabs:
1145
+ with gr.TabItem(TEXT["tabs"]["overview"]):
1146
+ metadata_view = gr.HTML()
1147
+ with gr.TabItem(TEXT["tabs"]["entities"]):
1148
+ entities_view = gr.HTML()
1149
+ with gr.TabItem(TEXT["tabs"]["values"]):
1150
+ values_view = gr.HTML()
1151
+ with gr.TabItem(TEXT["tabs"]["dates"]):
1152
+ dates_view = gr.HTML()
1153
+ with gr.TabItem(TEXT["tabs"]["tables"]):
1154
+ tables_view = gr.HTML()
1155
+ with gr.TabItem(TEXT["tabs"]["keypoints"]):
1156
+ keypoints_view = gr.HTML()
1157
+ with gr.TabItem(TEXT["tabs"]["references"]):
1158
+ references_view = gr.HTML()
1159
+
1160
+ # JSON complet en dessous des onglets
1161
+ gr.HTML("""
1162
+ <div class="intro-card" style="margin-top: 1.5rem;">
1163
+ <div class="intro-header">
1164
+ <h3>📄 Complete JSON</h3>
1165
+ </div>
1166
+ <div class="intro-body" style="padding: 0.75rem;">
1167
+ """)
1168
+ json_view = gr.HTML()
1169
+ gr.HTML("</div></div>")
1170
+
1171
+ # Animation de chargement
1172
+ loading_indicator = gr.HTML(f"""
1173
+ <div id="loading" style="display:none; text-align:center; padding: 2rem;">
1174
+ <div class="loading-spinner"></div>
1175
+ <p style="margin-top: 1rem; color: var(--primary);">{TEXT['processing']}</p>
1176
+ </div>
1177
+ <script>
1178
+ document.addEventListener('DOMContentLoaded', function() {{
1179
+ const btn = document.querySelector("button.primary");
1180
+ const loading = document.getElementById("loading");
1181
+ if (btn && loading) {{
1182
+ btn.addEventListener("click", function() {{
1183
+ loading.style.display = "block";
1184
+ const observer = new MutationObserver(function(mutations) {{
1185
+ mutations.forEach(function(mutation) {{
1186
+ if (mutation.addedNodes.length) {{
1187
+ loading.style.display = "none";
1188
+ observer.disconnect();
1189
+ }}
1190
+ }});
1191
+ }});
1192
+
1193
+ const resultsContainer = document.querySelector(".tabs");
1194
+ if (resultsContainer) {{
1195
+ observer.observe(resultsContainer, {{ childList: true, subtree: true }});
1196
+ }}
1197
+ }});
1198
+ }}
1199
+ }});
1200
+ </script>
1201
+ """)
1202
+
1203
+ file_input.change(
1204
+ fn=update_preview,
1205
+ inputs=file_input,
1206
+ outputs=preview
1207
+ )
1208
+
1209
+ submit_btn.click(
1210
+ fn=process_and_display,
1211
+ inputs=file_input,
1212
+ outputs=[metadata_view, entities_view, values_view, dates_view, tables_view, keypoints_view, references_view, json_view]
1213
+ )
1214
+
1215
+ if __name__ == "__main__":
1216
+ demo.launch(share=True, server_name="0.0.0.0", server_port=7860)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio>=4.44.1
2
+ google-generativeai>=0.3.0
3
+ Pillow>=10.0.0
4
+ pdf2image>=1.16.3
static/Editor _ Mermaid Chart-2025-04-15-142548.png ADDED

Git LFS Details

  • SHA256: 89fba15369282ec7f8c235aabeccc16d568b7e9de11acbdb9b7a997f4afa6f74
  • Pointer size: 131 Bytes
  • Size of remote file: 157 kB
static/elixir-logo-typo.png ADDED

Git LFS Details

  • SHA256: 83b4d0e48dcdbc635896049efa5920c1aa3888782f425b4e07d7f4ec0e3cfc68
  • Pointer size: 130 Bytes
  • Size of remote file: 43.4 kB