Dabococo commited on
Commit
6a9bf67
·
verified ·
1 Parent(s): cb2fbc3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -25
app.py CHANGED
@@ -6,7 +6,7 @@ from io import BytesIO
6
  import pandas as pd
7
  import re
8
  import tempfile
9
- import unicodedata # Pour sanitizer le nom de fichier
10
  from openpyxl import Workbook
11
  from openpyxl.drawing.image import Image as OpenpyxlImage
12
  from openpyxl.styles import Font, Alignment
@@ -27,22 +27,21 @@ def parse_markdown_table_to_df(table_text):
27
 
28
  lines = table_text.split('\n')
29
 
30
- # Ignorer les lignes vides et la ligne des séparateurs (|---|)
31
  data_lines = []
32
- separator_found = False
33
  for line in lines:
34
  stripped = line.strip()
35
  if not stripped:
36
  continue
37
- if re.match(r'\|[-| :]+\|', stripped):
38
- separator_found = True
39
  continue
40
  data_lines.append(stripped)
41
 
42
- if not data_lines:
43
  return pd.DataFrame({"Erreur": ["Aucun tableau Markdown trouvé dans la réponse"]})
44
 
45
- # Extraire les en-têtes (première ligne)
46
  header_line = data_lines[0]
47
  headers = [h.strip() for h in header_line.split('|')[1:-1]]
48
  num_columns = len(headers)
@@ -59,25 +58,24 @@ def parse_markdown_table_to_df(table_text):
59
  if len(cleaned_cells) < num_columns:
60
  cleaned_cells.extend([''] * (num_columns - len(cleaned_cells)))
61
  elif len(cleaned_cells) > num_columns:
62
- cleaned_cells = cleaned_cells[:num_columns] # Tronquer si trop de colonnes
63
  rows.append(cleaned_cells)
64
 
65
- # Filtrer les lignes entièrement vides (tous '' ou vides)
66
- rows = [row for row in rows if any(cell.strip() != '' for cell in row)]
67
-
68
  # Créer le DataFrame
69
  df = pd.DataFrame(rows, columns=headers)
 
 
70
  return df if not df.empty else pd.DataFrame({"Erreur": ["Aucune donnée valide extraite"]})
71
 
72
  def extract_filename_additional_and_table(response):
73
  """Extraire le nom de fichier, le texte additionnel et le tableau Markdown de la réponse structurée."""
74
- filename = "tableau_extrait" # Default
75
  additional_text = ""
76
  table_text = ""
77
 
78
  if 'Filename:' in response:
79
  parts = response.split('Filename:', 1)[1].split('\n', 1)
80
- filename = parts[0].strip().replace('.xlsx', '') # Enlever extension si présente
81
  remaining = parts[1] if len(parts) > 1 else ""
82
  else:
83
  remaining = response
@@ -89,10 +87,10 @@ def extract_filename_additional_and_table(response):
89
  else:
90
  table_text = remaining.strip()
91
 
92
- # Sanitizer le nom de fichier : enlever accents, caractères spéciaux, limiter à alphanum + _ -
93
  filename = ''.join(c for c in unicodedata.normalize('NFD', filename) if unicodedata.category(c) != 'Mn')
94
  filename = re.sub(r'[^a-zA-Z0-9_-]', '_', filename)
95
- filename = filename[:50] # Limiter la longueur
96
 
97
  return filename, additional_text, table_text
98
 
@@ -106,15 +104,16 @@ def process_image_and_get_response(image):
106
 
107
  # Prompt optimisé pour structure, précision et inclusion des infos supplémentaires
108
  prompt = (
109
- "Analyse l'image et extrait tout le contenu. "
110
- "D'abord, suggère un nom de fichier descriptif et précis pour l'Excel basé sur le contenu principal de l'image (inclue des éléments clés comme dates, thèmes ou identifiants uniques ; court, sans extension, ex: 'Tableau_Dossiers_Dentistes_2022_2024'). "
111
- "Ensuite, extrait TOUT texte additionnel autour, au-dessus, en-dessous ou à côté du tableau (titres, en-têtes, notes, pieds de page, logos, etc.), en le recopiant mot pour mot, même si c'est dispersé. "
112
  "Si aucun texte additionnel, laisse vide. "
113
- "Enfin, extrait le tableau en entier, en recopiant TOUTES les lignes et colonnes à l'identique, y compris les lignes vides ou partielles si elles existent dans l'image. "
 
114
  "Utilise un format Markdown pour le tableau avec des | pour les colonnes et une ligne |---|---| pour les séparateurs. "
115
- "Assure-toi que CHAQUE ligne (en-têtes, séparateurs, données) a EXACTEMENT le même nombre de colonnes (compte les | : il doit y en avoir 10 pour 9 colonnes, incluant les | de début et fin). "
116
  "Pour les sauts de ligne dans une cellule, utilise \n au lieu de <br>. "
117
- "Remplis les cellules vides avec '' si nécessaire pour maintenir l'alignement, mais ne filtre pas les lignes vides si elles sont présentes. "
118
  "N'ajoute aucun texte explicatif dans le tableau. "
119
  "Structure ta réponse exactement comme suit :\n"
120
  "Filename: [nom_suggéré_précis]\n"
@@ -125,7 +124,7 @@ def process_image_and_get_response(image):
125
 
126
  try:
127
  completion = client.chat.completions.create(
128
- model="meta-llama/llama-4-maverick-17b-128e-instruct",
129
  messages=[
130
  {
131
  "role": "user",
@@ -138,8 +137,8 @@ def process_image_and_get_response(image):
138
  ]
139
  }
140
  ],
141
- temperature=0.5, # Réduit pour plus de précision et respect de la structure
142
- max_completion_tokens=8096, # Augmenté pour tableaux complexes
143
  top_p=1,
144
  stream=False,
145
  stop=None
@@ -170,15 +169,21 @@ def process_image_and_get_response(image):
170
  cell = ws_table.cell(row=1, column=col_num, value=value)
171
  cell.font = Font(bold=True)
172
  cell.alignment = Alignment(wrap_text=True, vertical='top')
 
 
173
 
174
  # Écrire les données avec wrap text
175
  for row_num, row in enumerate(df.values, start=2):
176
  for col_num, value in enumerate(row, start=1):
177
  cell = ws_table.cell(row=row_num, column=col_num, value=value)
178
  cell.alignment = Alignment(wrap_text=True, vertical='top')
 
 
179
 
180
  else:
181
  ws_table.cell(row=1, column=1, value="Erreur lors du parsing du tableau")
 
 
182
 
183
  # Feuille pour le texte additionnel, si présent
184
  if additional_text:
@@ -187,6 +192,8 @@ def process_image_and_get_response(image):
187
  for row_num, line in enumerate(lines, start=1):
188
  cell = ws_additional.cell(row=row_num, column=1, value=line)
189
  cell.alignment = Alignment(wrap_text=True, vertical='top')
 
 
190
 
191
  # Feuille pour l'image originale
192
  ws_image = wb.create_sheet(title='Image_Originale')
@@ -212,7 +219,7 @@ iface = gr.Interface(
212
  gr.Textbox(label="Réponse de l'IA (texte additionnel + tableau Markdown pour copier-coller)"),
213
  gr.File(label="Télécharger le fichier Excel (avec tableau, infos supp. et image originale)")
214
  ],
215
- title="Extraction de Tableau depuis Image puis Export Excel",
216
  description="Uploader une image avec un tableau. L'IA extrait le texte additionnel et le tableau, puis génère un Excel avec des feuilles séparées, y compris l'image originale. Le résultat n'est pas parfait, veuillez à vous relire pour vérifier l'exactitude des réponses. Les données sont privées et ne sont pas sauvegardées."
217
  )
218
 
 
6
  import pandas as pd
7
  import re
8
  import tempfile
9
+ import unicodedata
10
  from openpyxl import Workbook
11
  from openpyxl.drawing.image import Image as OpenpyxlImage
12
  from openpyxl.styles import Font, Alignment
 
27
 
28
  lines = table_text.split('\n')
29
 
30
+ # Ignorer les lignes vides, mais garder toutes les lignes non-séparateurs
31
  data_lines = []
32
+ separator_pattern = r'\|[-| :]+\|'
33
  for line in lines:
34
  stripped = line.strip()
35
  if not stripped:
36
  continue
37
+ if re.match(separator_pattern, stripped):
 
38
  continue
39
  data_lines.append(stripped)
40
 
41
+ if len(data_lines) < 1:
42
  return pd.DataFrame({"Erreur": ["Aucun tableau Markdown trouvé dans la réponse"]})
43
 
44
+ # Extraire les en-têtes (première ligne non vide)
45
  header_line = data_lines[0]
46
  headers = [h.strip() for h in header_line.split('|')[1:-1]]
47
  num_columns = len(headers)
 
58
  if len(cleaned_cells) < num_columns:
59
  cleaned_cells.extend([''] * (num_columns - len(cleaned_cells)))
60
  elif len(cleaned_cells) > num_columns:
61
+ cleaned_cells = cleaned_cells[:num_columns]
62
  rows.append(cleaned_cells)
63
 
 
 
 
64
  # Créer le DataFrame
65
  df = pd.DataFrame(rows, columns=headers)
66
+ # Filtrer les lignes entièrement vides (optionnel, mais conservé pour éviter les lignes inutiles)
67
+ df = df.loc[df.apply(lambda row: any(cell.strip() != '' for cell in row), axis=1)]
68
  return df if not df.empty else pd.DataFrame({"Erreur": ["Aucune donnée valide extraite"]})
69
 
70
  def extract_filename_additional_and_table(response):
71
  """Extraire le nom de fichier, le texte additionnel et le tableau Markdown de la réponse structurée."""
72
+ filename = "tableau_extrait"
73
  additional_text = ""
74
  table_text = ""
75
 
76
  if 'Filename:' in response:
77
  parts = response.split('Filename:', 1)[1].split('\n', 1)
78
+ filename = parts[0].strip().replace('.xlsx', '')
79
  remaining = parts[1] if len(parts) > 1 else ""
80
  else:
81
  remaining = response
 
87
  else:
88
  table_text = remaining.strip()
89
 
90
+ # Sanitizer le nom de fichier
91
  filename = ''.join(c for c in unicodedata.normalize('NFD', filename) if unicodedata.category(c) != 'Mn')
92
  filename = re.sub(r'[^a-zA-Z0-9_-]', '_', filename)
93
+ filename = filename[:50]
94
 
95
  return filename, additional_text, table_text
96
 
 
104
 
105
  # Prompt optimisé pour structure, précision et inclusion des infos supplémentaires
106
  prompt = (
107
+ "Analyse l'image et extrait tout le contenu avec précision. "
108
+ "D'abord, suggère un nom de fichier descriptif et précis pour l'Excel basé sur le contenu principal de l'image (inclue des éléments clés comme le mois, l'année, ou des identifiants uniques, ex: 'Registre_Heures_Juin_2016'). "
109
+ "Ensuite, extrait TOUT texte additionnel autour, au-dessus, en-dessous ou à côté du tableau (titres, en-têtes de page, notes, pieds de page, logos, etc.), en le recopiant mot pour mot, même si c'est dispersé. "
110
  "Si aucun texte additionnel, laisse vide. "
111
+ "Enfin, extrait le tableau COMPLET en entier, en recopiant TOUTES les lignes et colonnes à l'identique, y compris les lignes vides ou partielles si elles existent dans l'image. "
112
+ "Les en-têtes du tableau (première ligne avec les titres des colonnes) doivent TOUJOURS être inclus dans la section Table, JAMAIS dans Additional text. "
113
  "Utilise un format Markdown pour le tableau avec des | pour les colonnes et une ligne |---|---| pour les séparateurs. "
114
+ "Assure-toi que CHAQUE ligne (en-têtes, séparateurs, données) a EXACTEMENT le même nombre de colonnes (compte les | : il doit y en avoir 13 pour 12 colonnes, incluant les | de début et fin). "
115
  "Pour les sauts de ligne dans une cellule, utilise \n au lieu de <br>. "
116
+ "Remplis les cellules vides avec '' si nécessaire pour maintenir l'alignement. "
117
  "N'ajoute aucun texte explicatif dans le tableau. "
118
  "Structure ta réponse exactement comme suit :\n"
119
  "Filename: [nom_suggéré_précis]\n"
 
124
 
125
  try:
126
  completion = client.chat.completions.create(
127
+ model="meta-llama/llama-4-scout-17b-16e-instruct",
128
  messages=[
129
  {
130
  "role": "user",
 
137
  ]
138
  }
139
  ],
140
+ temperature=0.2,
141
+ max_completion_tokens=4096,
142
  top_p=1,
143
  stream=False,
144
  stop=None
 
169
  cell = ws_table.cell(row=1, column=col_num, value=value)
170
  cell.font = Font(bold=True)
171
  cell.alignment = Alignment(wrap_text=True, vertical='top')
172
+ # Ajuster la largeur des colonnes (auto-ajustement approximatif)
173
+ ws_table.column_dimensions[chr(64 + col_num)].width = max(len(str(value)) * 1.2, 15)
174
 
175
  # Écrire les données avec wrap text
176
  for row_num, row in enumerate(df.values, start=2):
177
  for col_num, value in enumerate(row, start=1):
178
  cell = ws_table.cell(row=row_num, column=col_num, value=value)
179
  cell.alignment = Alignment(wrap_text=True, vertical='top')
180
+ # Ajuster la hauteur des lignes
181
+ ws_table.row_dimensions[row_num].height = max(len(str(value).split('\n')) * 15, 20)
182
 
183
  else:
184
  ws_table.cell(row=1, column=1, value="Erreur lors du parsing du tableau")
185
+ ws_table.column_dimensions['A'].width = 50
186
+ ws_table.row_dimensions[1].height = 20
187
 
188
  # Feuille pour le texte additionnel, si présent
189
  if additional_text:
 
192
  for row_num, line in enumerate(lines, start=1):
193
  cell = ws_additional.cell(row=row_num, column=1, value=line)
194
  cell.alignment = Alignment(wrap_text=True, vertical='top')
195
+ ws_additional.column_dimensions['A'].width = max(len(line) * 1.2, 50)
196
+ ws_additional.row_dimensions[row_num].height = max(len(line.split('\n')) * 15, 20)
197
 
198
  # Feuille pour l'image originale
199
  ws_image = wb.create_sheet(title='Image_Originale')
 
219
  gr.Textbox(label="Réponse de l'IA (texte additionnel + tableau Markdown pour copier-coller)"),
220
  gr.File(label="Télécharger le fichier Excel (avec tableau, infos supp. et image originale)")
221
  ],
222
+ title="Extraction de Tableau depuis Image avec Groq et Export Excel",
223
  description="Uploader une image avec un tableau. L'IA extrait le texte additionnel et le tableau, puis génère un Excel avec des feuilles séparées, y compris l'image originale. Le résultat n'est pas parfait, veuillez à vous relire pour vérifier l'exactitude des réponses. Les données sont privées et ne sont pas sauvegardées."
224
  )
225