Docfile commited on
Commit
26e295b
·
verified ·
1 Parent(s): 19d03a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +257 -104
app.py CHANGED
@@ -2,11 +2,12 @@ from flask import Flask, render_template, request, jsonify, send_file
2
  import threading
3
  import time
4
  import os
 
5
  from datetime import datetime
6
  from google import genai
7
- from google.genai import types # Explicitly import types
 
8
  import uuid
9
- import json # Import json module
10
 
11
  app = Flask(__name__)
12
 
@@ -39,6 +40,19 @@ safety_settings = [
39
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
40
  os.makedirs(RESULTS_FOLDER, exist_ok=True)
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  class TaskManager:
43
  def __init__(self):
44
  self.tasks = {}
@@ -47,21 +61,24 @@ class TaskManager:
47
  self.tasks[task_id] = {
48
  'status': 'running',
49
  'progress': 0,
50
- 'total': 470, # Nombre total de requêtes
51
- 'results_file': f'results_{task_id}.json', # Changed to .json
52
  'start_time': datetime.now(),
53
  'errors': [],
54
- 'last_update': datetime.now()
 
55
  }
56
 
57
- def update_progress(self, task_id, progress):
58
  if task_id in self.tasks:
59
  self.tasks[task_id]['progress'] = progress
60
  self.tasks[task_id]['last_update'] = datetime.now()
 
 
61
 
62
- def add_error(self, task_id, error_details): # error_details can be a dict
63
  if task_id in self.tasks:
64
- self.tasks[task_id]['errors'].append(error_details)
65
 
66
  def complete_task(self, task_id):
67
  if task_id in self.tasks:
@@ -73,102 +90,182 @@ class TaskManager:
73
 
74
  task_manager = TaskManager()
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  def generate_synthetic_data(file_path, task_id):
77
- """Fonction qui exécute les requêtes en arrière-plan et produit du JSON."""
78
  try:
 
79
  client = genai.Client(api_key=GOOGLE_API_KEY)
 
 
80
  file_ref = client.files.upload(file=file_path)
81
 
82
- prompt = (
83
- "J'aimerais générer des nouvelles données synthétiques à partir de ça. "
84
- "Une entrée en fang, une entrée en français. Je veux 400 phrases longues (respecte strictement cela).\n\n"
85
- "Réponds directement avec un objet JSON contenant les clés 'fang' et 'francais' pour chaque paire. "
86
- "Par exemple: {\"fang\": \"Texte en fang...\", \"francais\": \"Texte en français...\"}"
87
- )
 
 
 
 
 
 
 
88
 
89
- results_file_path = os.path.join(RESULTS_FOLDER, task_manager.get_task(task_id)['results_file'])
 
90
 
91
- all_generated_data = [] # Liste pour stocker tous les objets JSON générés
92
-
93
- for i in range(task_manager.get_task(task_id)['total']):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  try:
95
- print(f"Task {task_id}: Requête {i+1}/{task_manager.get_task(task_id)['total']}...")
96
  response = client.models.generate_content(
97
  model=MODEL_ID,
98
  contents=[file_ref, prompt],
99
- generation_config=types.GenerationConfig( # Use GenerationConfig
 
100
  response_mime_type='application/json',
101
- # Vous pourriez ajouter response_schema ici si vous avez un modèle > 1.5 et une classe TypedDict définie
102
- ),
103
- safety_settings=safety_settings # safety_settings renommé ici aussi
104
  )
105
 
106
- # L'API devrait retourner du texte qui est une chaîne JSON
107
  try:
108
- json_response = json.loads(response.text)
109
- all_generated_data.append(json_response)
110
- except json.JSONDecodeError as je:
111
- error_msg = f"Erreur de décodage JSON pour requête {i+1}: {str(je)}. Réponse reçue: {response.text[:200]}..."
112
- print(error_msg)
113
- task_manager.add_error(task_id, {"request_index": i+1, "error": error_msg, "raw_response": response.text})
114
- # Optionnel: ajouter un placeholder ou l'erreur dans all_generated_data
115
- all_generated_data.append({"error": "JSONDecodeError", "request_index": i+1, "details": response.text})
116
-
117
-
118
- # Sauvegarder la liste complète des données JSON après chaque ajout
119
- with open(results_file_path, 'w', encoding='utf-8') as f:
120
- json.dump(all_generated_data, f, ensure_ascii=False, indent=4)
 
 
 
 
 
 
 
 
 
 
 
 
121
 
 
122
  task_manager.update_progress(task_id, i + 1)
123
- print(f"Task {task_id}: Requête {i+1} complétée et sauvegardée.")
124
 
125
- time.sleep(4) # Pause
 
 
 
126
 
127
  except Exception as e:
128
- error_msg = f"Erreur pendant la requête {i+1}: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
129
  print(error_msg)
130
- task_manager.add_error(task_id, {"request_index": i+1, "error": error_msg})
131
- # Optionnel: ajouter un placeholder ou l'erreur dans all_generated_data
132
- all_generated_data.append({"error": "RequestException", "request_index": i+1, "details": str(e)})
133
- # Sauvegarder même en cas d'erreur pour ne pas perdre les données précédentes
134
- with open(results_file_path, 'w', encoding='utf-8') as f:
135
- json.dump(all_generated_data, f, ensure_ascii=False, indent=4)
136
- # Continuer avec la prochaine requête si possible, ou décider d'arrêter.
137
- # Ici, on continue.
138
 
139
  task_manager.complete_task(task_id)
140
- print(f"Tâche {task_id} terminée. Résultats JSON dans {results_file_path}")
141
 
142
  except Exception as e:
143
- error_msg = f"Erreur générale dans la tâche {task_id}: {str(e)}"
 
144
  print(error_msg)
145
- task_manager.add_error(task_id, {"request_index": "general", "error": error_msg})
146
- # Si une erreur générale se produit, marquer la tâche comme échouée ou la compléter avec erreurs
147
- if task_manager.get_task(task_id):
148
- task_manager.get_task(task_id)['status'] = 'failed_or_completed_with_errors'
149
-
150
 
151
  @app.route('/')
152
  def index():
153
  return render_template('index.html')
154
 
155
  @app.route('/upload', methods=['POST'])
156
- def upload_file_route(): # Renommé pour éviter conflit avec variable 'file'
157
  if 'file' not in request.files:
158
  return jsonify({'error': 'Aucun fichier sélectionné'}), 400
159
 
160
- file_obj = request.files['file'] # Renommé pour éviter conflit
161
- if file_obj.filename == '':
162
  return jsonify({'error': 'Aucun fichier sélectionné'}), 400
163
 
164
- if file_obj:
 
165
  task_id = str(uuid.uuid4())
166
- filename = f"input_{task_id}{os.path.splitext(file_obj.filename)[1]}" # Conserver l'extension originale pour le fichier d'input
 
 
167
  file_path = os.path.join(UPLOAD_FOLDER, filename)
168
- file_obj.save(file_path)
169
 
 
170
  task_manager.create_task(task_id)
171
 
 
172
  thread = threading.Thread(
173
  target=generate_synthetic_data,
174
  args=(file_path, task_id)
@@ -178,7 +275,7 @@ def upload_file_route(): # Renommé pour éviter conflit avec variable 'file'
178
 
179
  return jsonify({
180
  'task_id': task_id,
181
- 'message': 'Traitement démarré. Les résultats seront au format JSON.'
182
  })
183
 
184
  @app.route('/status/<task_id>')
@@ -187,20 +284,14 @@ def get_status(task_id):
187
  if not task:
188
  return jsonify({'error': 'Tâche non trouvée'}), 404
189
 
190
- # S'assurer que les datetime sont bien des objets datetime
191
- start_time_str = task['start_time'].strftime('%Y-%m-%d %H:%M:%S') if isinstance(task['start_time'], datetime) else str(task['start_time'])
192
- last_update_str = task['last_update'].strftime('%Y-%m-%d %H:%M:%S') if isinstance(task['last_update'], datetime) else str(task['last_update'])
193
-
194
  return jsonify({
195
  'status': task['status'],
196
  'progress': task['progress'],
197
  'total': task['total'],
198
- 'percentage': round((task['progress'] / task['total']) * 100, 2) if task['total'] > 0 else 0,
199
  'errors_count': len(task['errors']),
200
- 'errors_details': task['errors'][-5:], # Afficher les 5 dernières erreurs par exemple
201
- 'start_time': start_time_str,
202
- 'last_update': last_update_str,
203
- 'results_file': task.get('results_file', f'results_{task_id}.json') # S'assurer qu'il y a une valeur par défaut
204
  })
205
 
206
  @app.route('/download/<task_id>')
@@ -209,60 +300,122 @@ def download_results(task_id):
209
  if not task:
210
  return jsonify({'error': 'Tâche non trouvée'}), 404
211
 
212
- # Le nom du fichier est maintenant stocké dans les détails de la tâche
213
- results_filename = task.get('results_file', f'results_{task_id}.json')
214
- results_file_path = os.path.join(RESULTS_FOLDER, results_filename)
 
 
 
 
215
 
216
- if not os.path.exists(results_file_path):
217
- # Si le fichier n'existe pas encore mais que la tâche est en cours, c'est normal
218
- if task['status'] == 'running' and task['progress'] == 0:
219
- return jsonify({'message': 'La génération vient de commencer, aucun résultat à télécharger pour le moment.'}), 202 # Accepted
220
- return jsonify({'error': f'Fichier de résultats {results_filename} non trouvé. La génération est peut-être encore en cours ou a échoué avant de créer le fichier.'}), 404
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
- # Pour JSON, un "partiel" est juste l'état actuel du fichier JSON.
223
- # Le nom du fichier téléchargé indiquera s'il est complet ou partiel.
224
- status_indicator = "complet" if task['status'] == 'completed' else "partiel_en_cours"
225
- download_name = f'donnees_synthetiques_{status_indicator}_{task_id}.json'
226
 
227
  return send_file(
228
- results_file_path,
229
  as_attachment=True,
230
- download_name=download_name,
231
- mimetype='application/json' # Spécifier le mimetype JSON
232
  )
233
 
234
  @app.route('/tasks')
235
  def list_tasks():
 
236
  task_list = []
237
  for task_id, task_info in task_manager.tasks.items():
238
- start_time_str = task_info['start_time'].strftime('%Y-%m-%d %H:%M:%S') if isinstance(task_info['start_time'], datetime) else str(task_info['start_time'])
239
- last_update_str = task_info['last_update'].strftime('%Y-%m-%d %H:%M:%S') if isinstance(task_info['last_update'], datetime) else str(task_info['last_update'])
240
-
241
  task_list.append({
242
  'id': task_id,
243
  'status': task_info['status'],
244
  'progress': task_info['progress'],
245
  'total': task_info['total'],
246
- 'percentage': round((task_info['progress'] / task_info['total']) * 100, 2) if task_info['total'] > 0 else 0,
247
- 'start_time': start_time_str,
248
- 'last_update': last_update_str,
249
- 'errors_count': len(task_info['errors']),
250
- 'results_file': task_info.get('results_file', f'results_{task_id}.json')
251
  })
252
 
 
253
  task_list.sort(key=lambda x: x['start_time'], reverse=True)
 
254
  return jsonify(task_list)
255
 
256
  @app.route('/cleanup')
257
  def cleanup_temp_files():
258
- # Cette fonction n'est plus nécessaire si nous n'utilisons plus de fichiers 'temp_results_*.txt'
259
- # Elle pourrait être adaptée pour nettoyer de vieux fichiers de résultats ou d'upload si besoin.
260
- # Pour l'instant, on peut la laisser comme no-op ou la supprimer.
261
- return jsonify({'message': 'Nettoyage des fichiers temporaires (JSON partiels) non applicable avec la nouvelle stratégie.'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
 
263
  if __name__ == '__main__':
264
- print("🚀 Démarrage du serveur Flask...")
265
- print(f"📂 Dossier d'upload: {os.path.abspath(UPLOAD_FOLDER)}")
266
- print(f"📂 Dossier de résultats: {os.path.abspath(RESULTS_FOLDER)}")
267
- print("🌐 Application disponible sur: http://localhost:5000 (ou l'IP de votre machine si accessible de l'extérieur)")
268
- app.run(debug=True, threaded=True, host='0.0.0.0') # host='0.0.0.0' pour rendre accessible sur le réseau local
 
2
  import threading
3
  import time
4
  import os
5
+ import json
6
  from datetime import datetime
7
  from google import genai
8
+ from google.genai import types
9
+ import typing_extensions as typing
10
  import uuid
 
11
 
12
  app = Flask(__name__)
13
 
 
40
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
41
  os.makedirs(RESULTS_FOLDER, exist_ok=True)
42
 
43
+ # Définition du schéma JSON pour les réponses
44
+ class TranslationPair(typing.TypedDict):
45
+ fang: str
46
+ francais: str
47
+
48
+ class SyntheticDataResponse(typing.TypedDict):
49
+ request_number: int
50
+ generated_pairs: list[TranslationPair]
51
+ timestamp: str
52
+
53
+ # Stockage des tâches en cours
54
+ tasks = {}
55
+
56
  class TaskManager:
57
  def __init__(self):
58
  self.tasks = {}
 
61
  self.tasks[task_id] = {
62
  'status': 'running',
63
  'progress': 0,
64
+ 'total': 470,
65
+ 'results_file': f'results_{task_id}.json', # Changé en .json
66
  'start_time': datetime.now(),
67
  'errors': [],
68
+ 'last_update': datetime.now(),
69
+ 'all_data': [] # Stocker toutes les données JSON
70
  }
71
 
72
+ def update_progress(self, task_id, progress, data=None):
73
  if task_id in self.tasks:
74
  self.tasks[task_id]['progress'] = progress
75
  self.tasks[task_id]['last_update'] = datetime.now()
76
+ if data:
77
+ self.tasks[task_id]['all_data'].append(data)
78
 
79
+ def add_error(self, task_id, error):
80
  if task_id in self.tasks:
81
+ self.tasks[task_id]['errors'].append(error)
82
 
83
  def complete_task(self, task_id):
84
  if task_id in self.tasks:
 
90
 
91
  task_manager = TaskManager()
92
 
93
+ def parse_response_to_pairs(response_text, request_num):
94
+ """Parse la réponse textuelle pour extraire les paires fang/français"""
95
+ pairs = []
96
+ lines = response_text.strip().split('\n')
97
+
98
+ current_fang = ""
99
+ current_francais = ""
100
+
101
+ for line in lines:
102
+ line = line.strip()
103
+ if line.lower().startswith('fang :') or line.lower().startswith('fang:'):
104
+ current_fang = line.split(':', 1)[1].strip() if ':' in line else line
105
+ elif line.lower().startswith('français :') or line.lower().startswith('francais:') or line.lower().startswith('français:'):
106
+ current_francais = line.split(':', 1)[1].strip() if ':' in line else line
107
+
108
+ # Si on a une paire complète, l'ajouter
109
+ if current_fang and current_francais:
110
+ pairs.append({
111
+ "fang": current_fang,
112
+ "francais": current_francais
113
+ })
114
+ current_fang = ""
115
+ current_francais = ""
116
+
117
+ return {
118
+ "request_number": request_num,
119
+ "generated_pairs": pairs,
120
+ "timestamp": datetime.now().isoformat()
121
+ }
122
+
123
  def generate_synthetic_data(file_path, task_id):
124
+ """Fonction qui exécute les 470 requêtes en arrière-plan avec sortie JSON"""
125
  try:
126
+ # Initialiser le client Google AI
127
  client = genai.Client(api_key=GOOGLE_API_KEY)
128
+
129
+ # Uploader le fichier
130
  file_ref = client.files.upload(file=file_path)
131
 
132
+ # Prompt modifié pour avoir une sortie plus structurée
133
+ prompt = """J'aimerais générer des nouvelles données synthétiques à partir de ça.
134
+ Une en fang, une en français. Je veux 400 phrases longues (respecte strictement cela).
135
+
136
+ Réponds au format JSON suivant :
137
+ {
138
+ "pairs": [
139
+ {"fang": "phrase en fang", "francais": "phrase en français"},
140
+ {"fang": "phrase en fang", "francais": "phrase en français"}
141
+ ]
142
+ }
143
+
144
+ Génère exactement 400 paires de phrases."""
145
 
146
+ # Fichier de résultats JSON
147
+ results_file = os.path.join(RESULTS_FOLDER, f'results_{task_id}.json')
148
 
149
+ # Structure pour stocker toutes les données
150
+ all_results = {
151
+ "metadata": {
152
+ "task_id": task_id,
153
+ "start_time": datetime.now().isoformat(),
154
+ "total_requests": 470,
155
+ "model_used": MODEL_ID
156
+ },
157
+ "requests": [],
158
+ "summary": {
159
+ "total_pairs": 0,
160
+ "completed_requests": 0,
161
+ "errors": []
162
+ }
163
+ }
164
+
165
+ for i in range(470):
166
  try:
167
+ # Faire la requête avec schéma JSON
168
  response = client.models.generate_content(
169
  model=MODEL_ID,
170
  contents=[file_ref, prompt],
171
+ config=types.GenerateContentConfig(
172
+ safety_settings=safety_settings,
173
  response_mime_type='application/json',
174
+ response_schema=SyntheticDataResponse,
175
+ )
 
176
  )
177
 
 
178
  try:
179
+ # Parser la réponse JSON
180
+ response_data = json.loads(response.text)
181
+
182
+ # Structurer la réponse
183
+ request_data = {
184
+ "request_number": i + 1,
185
+ "timestamp": datetime.now().isoformat(),
186
+ "response": response_data,
187
+ "pairs_count": len(response_data.get("pairs", []))
188
+ }
189
+
190
+ all_results["requests"].append(request_data)
191
+ all_results["summary"]["total_pairs"] += request_data["pairs_count"]
192
+ all_results["summary"]["completed_requests"] += 1
193
+
194
+ except json.JSONDecodeError:
195
+ # Si la réponse n'est pas du JSON valide, essayer de parser manuellement
196
+ parsed_data = parse_response_to_pairs(response.text, i + 1)
197
+ all_results["requests"].append(parsed_data)
198
+ all_results["summary"]["total_pairs"] += len(parsed_data.get("generated_pairs", []))
199
+ all_results["summary"]["completed_requests"] += 1
200
+
201
+ # Sauvegarder après chaque requête
202
+ with open(results_file, 'w', encoding='utf-8') as f:
203
+ json.dump(all_results, f, ensure_ascii=False, indent=2)
204
 
205
+ # Mettre à jour le progrès
206
  task_manager.update_progress(task_id, i + 1)
 
207
 
208
+ print(f"Requête {i+1}/470 complétée")
209
+
210
+ # Pause pour éviter de surcharger l'API
211
+ time.sleep(40)
212
 
213
  except Exception as e:
214
+ error_msg = f"Erreur requête {i+1}: {str(e)}"
215
+ task_manager.add_error(task_id, error_msg)
216
+ all_results["summary"]["errors"].append({
217
+ "request_number": i + 1,
218
+ "error": error_msg,
219
+ "timestamp": datetime.now().isoformat()
220
+ })
221
+
222
+ # Sauvegarder même en cas d'erreur
223
+ with open(results_file, 'w', encoding='utf-8') as f:
224
+ json.dump(all_results, f, ensure_ascii=False, indent=2)
225
+
226
  print(error_msg)
227
+
228
+ # Finaliser le fichier JSON
229
+ all_results["metadata"]["end_time"] = datetime.now().isoformat()
230
+ all_results["metadata"]["duration_minutes"] = (datetime.now() - datetime.fromisoformat(all_results["metadata"]["start_time"])).total_seconds() / 60
231
+
232
+ with open(results_file, 'w', encoding='utf-8') as f:
233
+ json.dump(all_results, f, ensure_ascii=False, indent=2)
 
234
 
235
  task_manager.complete_task(task_id)
236
+ print(f"Tâche {task_id} terminée avec succès")
237
 
238
  except Exception as e:
239
+ error_msg = f"Erreur générale: {str(e)}"
240
+ task_manager.add_error(task_id, error_msg)
241
  print(error_msg)
 
 
 
 
 
242
 
243
  @app.route('/')
244
  def index():
245
  return render_template('index.html')
246
 
247
  @app.route('/upload', methods=['POST'])
248
+ def upload_file():
249
  if 'file' not in request.files:
250
  return jsonify({'error': 'Aucun fichier sélectionné'}), 400
251
 
252
+ file = request.files['file']
253
+ if file.filename == '':
254
  return jsonify({'error': 'Aucun fichier sélectionné'}), 400
255
 
256
+ if file:
257
+ # Générer un ID unique pour cette tâche
258
  task_id = str(uuid.uuid4())
259
+
260
+ # Sauvegarder le fichier
261
+ filename = f"input_{task_id}.txt"
262
  file_path = os.path.join(UPLOAD_FOLDER, filename)
263
+ file.save(file_path)
264
 
265
+ # Créer la tâche
266
  task_manager.create_task(task_id)
267
 
268
+ # Démarrer le traitement en arrière-plan
269
  thread = threading.Thread(
270
  target=generate_synthetic_data,
271
  args=(file_path, task_id)
 
275
 
276
  return jsonify({
277
  'task_id': task_id,
278
+ 'message': 'Traitement démarré en arrière-plan'
279
  })
280
 
281
  @app.route('/status/<task_id>')
 
284
  if not task:
285
  return jsonify({'error': 'Tâche non trouvée'}), 404
286
 
 
 
 
 
287
  return jsonify({
288
  'status': task['status'],
289
  'progress': task['progress'],
290
  'total': task['total'],
291
+ 'percentage': round((task['progress'] / task['total']) * 100, 2),
292
  'errors_count': len(task['errors']),
293
+ 'start_time': task['start_time'].strftime('%Y-%m-%d %H:%M:%S'),
294
+ 'last_update': task['last_update'].strftime('%Y-%m-%d %H:%M:%S')
 
 
295
  })
296
 
297
  @app.route('/download/<task_id>')
 
300
  if not task:
301
  return jsonify({'error': 'Tâche non trouvée'}), 404
302
 
303
+ results_file = os.path.join(RESULTS_FOLDER, f'results_{task_id}.json')
304
+
305
+ if not os.path.exists(results_file):
306
+ return jsonify({'error': 'Fichier de résultats non trouvé'}), 404
307
+
308
+ # Vérifier si c'est un téléchargement partiel
309
+ is_partial = request.args.get('partial', 'false').lower() == 'true'
310
 
311
+ if is_partial and task['status'] == 'running':
312
+ # Créer un fichier temporaire avec les données actuelles
313
+ temp_file = os.path.join(RESULTS_FOLDER, f'temp_results_{task_id}.json')
314
+
315
+ try:
316
+ # Charger les données actuelles
317
+ with open(results_file, 'r', encoding='utf-8') as f:
318
+ current_data = json.load(f)
319
+
320
+ # Ajouter des métadonnées pour le téléchargement partiel
321
+ current_data["partial_download"] = {
322
+ "downloaded_at": datetime.now().isoformat(),
323
+ "is_partial": True,
324
+ "progress": f"{task['progress']}/{task['total']}",
325
+ "percentage": round((task['progress'] / task['total']) * 100, 2)
326
+ }
327
+
328
+ # Sauvegarder le fichier temporaire
329
+ with open(temp_file, 'w', encoding='utf-8') as f:
330
+ json.dump(current_data, f, ensure_ascii=False, indent=2)
331
+
332
+ return send_file(
333
+ temp_file,
334
+ as_attachment=True,
335
+ download_name=f'donnees_synthetiques_partiel_{task_id}.json'
336
+ )
337
+ except Exception as e:
338
+ return jsonify({'error': f'Erreur lors de la création du fichier partiel: {str(e)}'}), 500
339
 
340
+ # Téléchargement normal (complet)
341
+ download_name = f'donnees_synthetiques_{"complet" if task["status"] == "completed" else "actuel"}_{task_id}.json'
 
 
342
 
343
  return send_file(
344
+ results_file,
345
  as_attachment=True,
346
+ download_name=download_name
 
347
  )
348
 
349
  @app.route('/tasks')
350
  def list_tasks():
351
+ """Liste toutes les tâches"""
352
  task_list = []
353
  for task_id, task_info in task_manager.tasks.items():
 
 
 
354
  task_list.append({
355
  'id': task_id,
356
  'status': task_info['status'],
357
  'progress': task_info['progress'],
358
  'total': task_info['total'],
359
+ 'percentage': round((task_info['progress'] / task_info['total']) * 100, 2),
360
+ 'start_time': task_info['start_time'].strftime('%Y-%m-%d %H:%M:%S'),
361
+ 'last_update': task_info['last_update'].strftime('%Y-%m-%d %H:%M:%S'),
362
+ 'errors_count': len(task_info['errors'])
 
363
  })
364
 
365
+ # Trier par heure de début (plus récent en premier)
366
  task_list.sort(key=lambda x: x['start_time'], reverse=True)
367
+
368
  return jsonify(task_list)
369
 
370
  @app.route('/cleanup')
371
  def cleanup_temp_files():
372
+ """Nettoyer les fichiers temporaires (optionnel)"""
373
+ try:
374
+ temp_files_deleted = 0
375
+ for filename in os.listdir(RESULTS_FOLDER):
376
+ if filename.startswith('temp_results_') and filename.endswith('.json'):
377
+ file_path = os.path.join(RESULTS_FOLDER, filename)
378
+ os.remove(file_path)
379
+ temp_files_deleted += 1
380
+
381
+ return jsonify({
382
+ 'message': f'{temp_files_deleted} fichiers temporaires supprimés'
383
+ })
384
+ except Exception as e:
385
+ return jsonify({'error': f'Erreur lors du nettoyage: {str(e)}'}), 500
386
+
387
+ @app.route('/preview/<task_id>')
388
+ def preview_results(task_id):
389
+ """Aperçu des résultats JSON pour debug"""
390
+ task = task_manager.get_task(task_id)
391
+ if not task:
392
+ return jsonify({'error': 'Tâche non trouvée'}), 404
393
+
394
+ results_file = os.path.join(RESULTS_FOLDER, f'results_{task_id}.json')
395
+
396
+ if not os.path.exists(results_file):
397
+ return jsonify({'error': 'Fichier de résultats non trouvé'}), 404
398
+
399
+ try:
400
+ with open(results_file, 'r', encoding='utf-8') as f:
401
+ data = json.load(f)
402
+
403
+ # Retourner un aperçu des données
404
+ preview = {
405
+ "metadata": data.get("metadata", {}),
406
+ "summary": data.get("summary", {}),
407
+ "sample_requests": data.get("requests", [])[:3], # 3 premiers échantillons
408
+ "total_requests": len(data.get("requests", []))
409
+ }
410
+
411
+ return jsonify(preview)
412
+
413
+ except Exception as e:
414
+ return jsonify({'error': f'Erreur lors de la lecture du fichier: {str(e)}'}), 500
415
 
416
  if __name__ == '__main__':
417
+ print("🚀 Démarrage du serveur...")
418
+ print("📂 Dossiers créés:", UPLOAD_FOLDER, RESULTS_FOLDER)
419
+ print("🌐 Application disponible sur: http://localhost:5000")
420
+ print("📊 Sortie JSON activée")
421
+ app.run(debug=True, threaded=True)