FatimaGr commited on
Commit
b678e5e
·
verified ·
1 Parent(s): 8d464d0
Files changed (1) hide show
  1. app.py +88 -197
app.py CHANGED
@@ -1,225 +1,116 @@
1
- from fastapi import FastAPI, File, UploadFile, Form
2
- from fastapi.responses import JSONResponse, RedirectResponse
3
  from fastapi.staticfiles import StaticFiles
4
- from transformers import pipeline, M2M100ForConditionalGeneration, M2M100Tokenizer
5
- import shutil
 
 
 
 
 
 
6
  import os
7
- import logging
8
  from fastapi.middleware.cors import CORSMiddleware
9
- from PyPDF2 import PdfReader
10
- import docx
11
- from PIL import Image # Pour ouvrir les images avant analyse
12
- from transformers import MarianMTModel, MarianTokenizer
13
- import os
14
- import fitz
15
- from transformers import M2M100ForConditionalGeneration, M2M100Tokenizer
16
-
17
- import logging
18
- import openpyxl
19
- import io
20
- from docx import Document
21
- from pptx import Presentation
22
- from fastapi.responses import JSONResponse
23
-
24
-
25
- # Configuration du logging
26
- logging.basicConfig(level=logging.INFO)
27
-
28
  app = FastAPI()
29
-
30
- # Configuration CORS
31
  app.add_middleware(
32
  CORSMiddleware,
33
- allow_origins=["*"],
34
  allow_credentials=True,
35
- allow_methods=["*"],
36
- allow_headers=["*"],
37
  )
38
 
39
- UPLOAD_DIR = "uploads"
40
- os.makedirs(UPLOAD_DIR, exist_ok=True)
41
-
42
- # 🔹 Charger les modèles avec gestion des erreurs
43
- try:
44
- summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
45
- logging.info("✅ Modèle de résumé chargé avec succès !")
46
- except Exception as e:
47
- summarizer = None
48
- logging.error(f"❌ Erreur chargement modèle résumé : {e}")
49
-
50
- try:
51
- image_captioning = pipeline("image-to-text", model="nlpconnect/vit-gpt2-image-captioning")
52
- logging.info("✅ Modèle d'image chargé avec succès !")
53
- except Exception as e:
54
- image_captioning = None
55
- logging.error(f"❌ Erreur chargement modèle image : {e}")
56
-
57
- # 🔹 Chargement du modèle de traduction
58
-
59
- # ✅ Déclare les routes AVANT le montage des fichiers statiques
60
- @app.post("/summarize/")
61
- async def summarize_document(file: UploadFile = File(...)):
62
- logging.info(f"📂 Requête reçue - Fichier : {file.filename}")
63
-
64
- file_path = os.path.join(UPLOAD_DIR, file.filename)
65
- with open(file_path, "wb") as buffer:
66
- shutil.copyfileobj(file.file, buffer)
67
-
68
- text = ""
69
- if file.filename.endswith(".txt"):
70
- with open(file_path, "r", encoding="utf-8") as f:
71
- text = f.read()
72
- elif file.filename.endswith(".pdf"):
73
- try:
74
- reader = PdfReader(file_path)
75
- text = "\n".join([page.extract_text() or "" for page in reader.pages]).strip()
76
- except Exception as e:
77
- logging.error(f"❌ Erreur lecture PDF : {e}")
78
- return JSONResponse(content={"error": "Impossible de lire le PDF"}, status_code=400)
79
- elif file.filename.endswith(".docx"):
80
- try:
81
- doc = docx.Document(file_path)
82
- text = "\n".join([para.text for para in doc.paragraphs]).strip()
83
- except Exception as e:
84
- logging.error(f"❌ Erreur lecture DOCX : {e}")
85
- return JSONResponse(content={"error": "Impossible de lire le fichier DOCX"}, status_code=400)
86
- else:
87
- return JSONResponse(content={"error": "Format de fichier non supporté"}, status_code=400)
88
-
89
- if not text:
90
- logging.error("❌ Le fichier ne contient pas de texte lisible")
91
- return JSONResponse(content={"error": "Le fichier est vide ou non lisible"}, status_code=400)
92
-
93
- # Tronquer le texte pour éviter l'erreur "IndexError: index out of range"
94
- max_input_length = 1024 # Limite du modèle
95
- text = text[:max_input_length] # Tronquer le texte s'il est trop long
96
-
97
- try:
98
- summary = summarizer(text, max_length=150, min_length=50, do_sample=False)[0]["summary_text"]
99
- return JSONResponse(content={"summary": summary})
100
- except Exception as e:
101
- logging.error(f"❌ Erreur lors du résumé : {e}")
102
- return JSONResponse(content={"error": "Échec du résumé. Texte trop long ou format invalide."}, status_code=500)
103
 
 
 
104
 
105
- @app.post("/interpret/")
106
- async def interpret_image(file: UploadFile = File(...)):
107
- logging.info(f"📂 Requête reçue - Image : {file.filename}")
108
-
109
- file_path = os.path.join(UPLOAD_DIR, file.filename)
110
- with open(file_path, "wb") as buffer:
111
- shutil.copyfileobj(file.file, buffer)
112
 
 
 
113
  try:
114
- with Image.open(file_path) as img: # Charger l'image correctement
115
- caption = image_captioning(img)[0]["generated_text"]
116
- return JSONResponse(content={"caption": caption})
117
- except Exception as e:
118
- logging.error(f"❌ Erreur interprétation image : {e}")
119
- return JSONResponse(content={"error": "Échec de l'analyse de l'image"}, status_code=400)
120
-
121
 
 
122
 
 
 
 
123
 
124
- # 🔹 Chargement du modèle de traduction
125
- try:
126
- model_name = "facebook/m2m100_418M"
127
- tokenizer = M2M100Tokenizer.from_pretrained(model_name)
128
- model = M2M100ForConditionalGeneration.from_pretrained(model_name)
129
- logging.info("✅ Modèle de traduction chargé avec succès !")
130
- except Exception as e:
131
- logging.error(f"❌ Erreur chargement modèle de traduction : {e}")
132
- model, tokenizer = None, None
133
 
134
-
135
- def extract_text_from_pdf(file):
136
- """Extrait le texte d'un fichier PDF."""
137
- doc = fitz.open(stream=file.file.read(), filetype="pdf")
138
- text = "\n".join([page.get_text() for page in doc])
139
- return text.strip()
140
-
141
-
142
- def extract_text_from_docx(file):
143
- """Extrait le texte d'un fichier DOCX."""
144
- doc = Document(io.BytesIO(file.file.read()))
145
- text = "\n".join([para.text for para in doc.paragraphs])
146
- return text.strip()
147
-
148
-
149
- def extract_text_from_pptx(file):
150
- """Extrait le texte d'un fichier PPTX."""
151
- prs = Presentation(io.BytesIO(file.file.read()))
152
- text = []
153
- for slide in prs.slides:
154
- for shape in slide.shapes:
155
- if hasattr(shape, "text"):
156
- text.append(shape.text)
157
- return "\n".join(text).strip()
158
-
159
-
160
- def extract_text_from_excel(file):
161
- """Extrait le texte d'un fichier Excel (XLSX)."""
162
- try:
163
- print("📥 Début extraction texte depuis Excel...")
164
- wb = openpyxl.load_workbook(io.BytesIO(file.file.read()), data_only=True)
165
- print("✅ Fichier Excel chargé avec succès !")
166
-
167
- text = []
168
- for sheet in wb.worksheets:
169
- print(f"📄 Feuille trouvée : {sheet.title}")
170
- for row in sheet.iter_rows(values_only=True):
171
- text.extend([str(cell) for cell in row if cell])
172
-
173
- extracted_text = "\n".join(text).strip()
174
- print(f"✅ Texte extrait (début) : {extracted_text[:100]}...")
175
- return extracted_text
176
- except Exception as e:
177
- print(f"❌ Erreur lors de l'extraction du fichier Excel : {e}")
178
- return None
179
-
180
-
181
-
182
- @app.post("/translate/")
183
- async def translate_document(file: UploadFile = File(...), target_lang: str = Form(...)):
184
- """API pour traduocire un dument."""
185
- try:
186
- logging.info(f"📥 Fichier reçu : {file.filename}")
187
- logging.info(f"🌍 Langue cible reçue : {target_lang}")
188
-
189
- if model is None or tokenizer is None:
190
- return JSONResponse(status_code=500, content={"error": "Modèle de traduction non chargé"})
191
-
192
- # Extraction du texte en fonction du type de fichier
193
- if file.filename.endswith(".pdf"):
194
- text = extract_text_from_pdf(file)
195
- elif file.filename.endswith(".docx"):
196
- text = extract_text_from_docx(file)
197
- elif file.filename.endswith(".pptx"):
198
- text = extract_text_from_pptx(file)
199
- elif file.filename.endswith(".xlsx"):
200
- text = extract_text_from_excel(file)
201
  else:
202
- return JSONResponse(status_code=400, content={"error": "Format non supporté"})
203
-
204
- logging.info(f"📜 Texte extrait : {text[:50]}...") # Affiche un extrait du texte
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
- if not text:
207
- return JSONResponse(status_code=400, content={"error": "Aucun texte trouvé dans le document"})
 
 
208
 
209
- # Traduire le texte
210
- tokenizer.src_lang = "fr"
211
- encoded_text = tokenizer(text, return_tensors="pt", padding=True, truncation=True)
212
 
213
- generated_tokens = model.generate(**encoded_text, forced_bos_token_id=tokenizer.get_lang_id(target_lang))
214
- translated_text = tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
 
215
 
216
- logging.info(f"✅ Traduction réussie : {translated_text[:50]}...") # Affiche un extrait de la traduction
 
 
 
 
 
217
 
218
- return {"translated_text": translated_text}
 
219
 
220
  except Exception as e:
221
- logging.error(f"Erreur lors de la traduction : {e}")
222
- return JSONResponse(status_code=500, content={"error": "Échec de la traduction"})
223
 
224
 
225
  # ✅ Déplace ici le montage des fichiers statiques
 
1
+
 
2
  from fastapi.staticfiles import StaticFiles
3
+ import re
4
+ import torch
5
+ import pandas as pd
6
+ import matplotlib.pyplot as plt
7
+ import seaborn as sns
8
+ from transformers import AutoTokenizer, AutoModelForCausalLM
9
+ from fastapi import FastAPI, File, UploadFile, Form
10
+ from fastapi.responses import FileResponse
11
  import os
 
12
  from fastapi.middleware.cors import CORSMiddleware
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  app = FastAPI()
 
 
14
  app.add_middleware(
15
  CORSMiddleware,
16
+ allow_origins=["*"], # Autorise toutes les origines (à sécuriser en prod)
17
  allow_credentials=True,
18
+ allow_methods=["*"], # Autorise toutes les méthodes (GET, POST, etc.)
19
+ allow_headers=["*"], # Autorise tous les headers
20
  )
21
 
22
+ # Charger le modèle Hugging Face
23
+ model_name = "Salesforce/codegen-350M-mono"
24
+ device = "cuda" if torch.cuda.is_available() else "cpu"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
27
+ model = AutoModelForCausalLM.from_pretrained(model_name).to(device)
28
 
29
+ VALID_PLOTS = {"histplot", "scatterplot", "barplot", "lineplot", "boxplot"}
 
 
 
 
 
 
30
 
31
+ @app.post("/generate_viz/")
32
+ async def generate_viz(file: UploadFile = File(...), query: str = Form(...)):
33
  try:
34
+ if query not in VALID_PLOTS:
35
+ return {"error": f"Type de graphique invalide. Choisissez parmi : {', '.join(VALID_PLOTS)}"}
 
 
 
 
 
36
 
37
+ df = pd.read_excel(file.file)
38
 
39
+ numeric_cols = df.select_dtypes(include=["number"]).columns
40
+ if len(numeric_cols) < 2:
41
+ return {"error": "Le fichier doit contenir au moins deux colonnes numériques."}
42
 
43
+ x_col, y_col = numeric_cols[:2]
 
 
 
 
 
 
 
 
44
 
45
+ # Contraintes spécifiques pour éviter l'erreur avec histplot
46
+ if query == "histplot":
47
+ prompt_y = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  else:
49
+ prompt_y = f', y="{y_col}"'
50
+
51
+ # Générer l'invite pour le modèle
52
+ prompt = f"""
53
+ ### Génère uniquement du code Python fonctionnel pour tracer un {query} avec Matplotlib et Seaborn ###
54
+ # Contraintes :
55
+ # - Utilise 'df' sans recréer de nouvelles données
56
+ # - Axe X : '{x_col}'
57
+ # - Enregistre le graphique sous 'plot.png'
58
+ # - Ne génère que du code Python valide, sans texte explicatif
59
+ # Contraintes spécifiques pour sns.histplot :
60
+ # - N'inclut pas "y=" car histplot ne supporte qu'un axe
61
+
62
+ import matplotlib.pyplot as plt
63
+ import seaborn as sns
64
+
65
+ plt.figure(figsize=(8,6))
66
+ sns.{query}(data=df, x="{x_col}"{prompt_y})
67
+
68
+ plt.savefig("plot.png")
69
+ plt.close()
70
+ """
71
+
72
+ # Génération du code
73
+ inputs = tokenizer(prompt, return_tensors="pt").to(device)
74
+ outputs = model.generate(**inputs, max_new_tokens=120, pad_token_id=tokenizer.eos_token_id)
75
+ generated_code = tokenizer.decode(outputs[0], skip_special_tokens=True).strip()
76
+
77
+ # Nettoyage du code
78
+ generated_code = re.sub(r"(import matplotlib.pyplot as plt\nimport seaborn as sns\n)+", "import matplotlib.pyplot as plt\nimport seaborn as sns\n", generated_code)
79
+ if generated_code.strip().endswith("sns."):
80
+ generated_code = generated_code.rsplit("\n", 1)[0] # Supprime la dernière ligne incomplète
81
+
82
+ print("🔹 Code généré par l'IA :\n", generated_code)
83
+
84
+ # Vérification syntaxique avant exécution
85
+ try:
86
+ compile(generated_code, "<string>", "exec")
87
+ except SyntaxError as e:
88
+ return {"error": f"Erreur de syntaxe détectée : {e}\nCode généré :\n{generated_code}"}
89
 
90
+ # Vérification des données
91
+ print(df.head()) # Affiche les premières lignes du dataframe
92
+ print(df.dtypes) # Vérifie les types de colonnes
93
+ print(f"Colonne '{x_col}' - Valeurs uniques:", df[x_col].unique())
94
 
95
+ if df.empty or x_col not in df.columns or df[x_col].isnull().all():
96
+ return {"error": f"La colonne '{x_col}' est absente ou ne contient pas de données valides."}
 
97
 
98
+ # Exécution du code généré
99
+ exec_env = {"df": df, "plt": plt, "sns": sns, "pd": pd}
100
+ exec(generated_code, exec_env)
101
 
102
+ # Vérification de l'image générée
103
+ img_path = "plot.png"
104
+ if not os.path.exists(img_path):
105
+ return {"error": "Le fichier plot.png n'a pas été généré."}
106
+ if os.path.getsize(img_path) == 0:
107
+ return {"error": "Le fichier plot.png est vide."}
108
 
109
+ plt.close()
110
+ return FileResponse(img_path, media_type="image/png")
111
 
112
  except Exception as e:
113
+ return {"error": f"Erreur lors de la génération du graphique : {str(e)}"}
 
114
 
115
 
116
  # ✅ Déplace ici le montage des fichiers statiques