Docfile commited on
Commit
481cfe6
·
verified ·
1 Parent(s): 2863c40

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +245 -147
app.py CHANGED
@@ -1,176 +1,274 @@
1
- import logging
2
- from flask import Flask, render_template, request, jsonify, session
3
- from google import genai
4
  import os
5
- from datetime import datetime
6
  import uuid
 
 
 
 
 
 
 
 
 
 
 
7
  import io
8
- import httpx
9
-
10
- # Configuration du logging
11
- logging.basicConfig(
12
- level=logging.DEBUG,
13
- format="%(asctime)s [%(levelname)s] %(message)s",
14
- datefmt="%Y-%m-%d %H:%M:%S"
15
- )
16
 
17
  app = Flask(__name__)
18
- app.secret_key = os.urandom(24) # Requis pour utiliser les sessions Flask
19
-
20
- # Configuration de l'API Gemini (idéalement à déplacer dans un fichier de configuration)
21
- # os.environ["GOOGLE_API_KEY"] = "VOTRE_GEMINI_API_KEY"
22
- client = genai.Client(api_key=os.environ.get("GOOGLE_API_KEY", "GEMINI_API_KEY"))
23
 
24
- # URL du PDF à analyser (fixe dans cette version)
25
- PDF_URL = "http://www.ddl.cnrs.fr/projets/clhass/PageWeb/ressources/fang.pdf"
 
 
 
26
 
27
- # Variable globale pour stocker le fichier PDF une fois téléchargé
28
- pdf_file = None
29
 
30
- def load_pdf():
31
- """Télécharge et prépare le fichier PDF pour l'analyse"""
32
- global pdf_file
33
- if pdf_file is None:
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  try:
35
- logging.info(f"Début du téléchargement du PDF depuis {PDF_URL}")
36
- response = httpx.get(PDF_URL)
37
- response.raise_for_status()
38
- pdf_content = response.content
39
- pdf_io = io.BytesIO(pdf_content)
 
 
 
40
 
41
- logging.info("Téléchargement réussi. Envoi du PDF à l'API Gemini...")
42
- # Télécharger le fichier via l'API Gemini
43
- pdf_file = client.files.upload(
44
- file=pdf_io,
45
- config=dict(mime_type='application/pdf')
46
  )
47
- logging.info("PDF téléchargé et préparé avec succès par Gemini!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  except Exception as e:
49
- logging.error(f"Erreur lors du téléchargement ou de la préparation du PDF: {str(e)}")
50
- raise
51
- else:
52
- logging.debug("PDF déjà chargé, utilisation du cache.")
53
- return pdf_file
54
-
55
- @app.route('/')
56
- def index():
57
- # Créer un ID de session unique s'il n'existe pas déjà
58
- if 'session_id' not in session:
59
- session['session_id'] = str(uuid.uuid4())
60
- logging.info(f"Nouvelle session créée avec l'ID : {session['session_id']}")
61
- else:
62
- logging.debug(f"Session existante détectée : {session['session_id']}")
63
 
64
- # Précharger le PDF au démarrage de l'application
 
65
  try:
66
- load_pdf()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  except Exception as e:
68
- error_msg = f"Erreur de chargement du PDF: {str(e)}"
69
- logging.error(error_msg)
70
- return render_template('index.html', error=error_msg)
71
-
72
- logging.info("Chargement de la page d'accueil réussi.")
73
- return render_template('index.html', pdf_url=PDF_URL)
74
-
75
- @app.route('/chat', methods=['POST'])
76
- def chat():
77
- data = request.json
78
- user_message = data.get('message', '')
79
- logging.info(f"Message utilisateur reçu: {user_message}")
80
-
81
- # Récupérer ou créer une session de chat pour cet utilisateur
82
- session_id = session.get('session_id')
83
- chat_session = get_or_create_chat_session(session_id)
84
- logging.debug(f"Session de chat pour l'ID {session_id}: {chat_session}")
85
 
 
 
86
  try:
87
- # S'assurer que le PDF est chargé
88
- pdf_document = load_pdf()
89
- logging.debug("PDF document récupéré pour le traitement du chat.")
90
-
91
- # Construire le prompt pour Gemini avec référence au PDF
92
- prompt = f"En vous basant sur le contenu du PDF sur la langue fang, veuillez répondre à: {user_message}"
93
- logging.info(f"Prompt construit pour Gemini: {prompt}")
94
-
95
- # Envoyer le message à Gemini avec le PDF comme contexte et obtenir la réponse
96
- logging.info("Envoi du prompt à Gemini...")
97
- response = client.models.generate_content(
98
- model="gemini-2.0-pro-exp-02-05",
99
- contents=[pdf_document, prompt]
100
- )
101
- logging.info("Réponse reçue de Gemini.")
102
-
103
- # Ajouter cette paire question/réponse à l'historique local
104
- if not hasattr(app, 'chat_histories'):
105
- app.chat_histories = {}
106
- logging.debug("Création de l'historique global des chats.")
107
-
108
- if session_id not in app.chat_histories:
109
- app.chat_histories[session_id] = []
110
- logging.debug(f"Création d'un historique de chat pour la session {session_id}.")
111
-
112
- timestamp = datetime.now().strftime("%H:%M")
113
- app.chat_histories[session_id].append({
114
- 'role': 'user',
115
- 'content': user_message,
116
- 'timestamp': timestamp
117
- })
118
- logging.debug("Message utilisateur ajouté à l'historique du chat.")
119
-
120
- app.chat_histories[session_id].append({
121
- 'role': 'model',
122
- 'content': response.text,
123
- 'timestamp': timestamp
124
- })
125
- logging.debug("Réponse du modèle ajoutée à l'historique du chat.")
 
 
 
 
 
126
 
127
- # Formater l'historique pour l'interface
128
- history = app.chat_histories[session_id]
129
- logging.info("Historique du chat formaté et prêt à être envoyé.")
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  return jsonify({
132
- 'response': response.text,
133
- 'history': history
 
134
  })
 
135
  except Exception as e:
136
- logging.error(f"Erreur lors du traitement du chat: {str(e)}")
137
  return jsonify({'error': str(e)}), 500
138
 
139
- def get_or_create_chat_session(session_id):
140
- """
141
- Récupère une session de chat existante ou en crée une nouvelle.
142
- Dans une application réelle, vous pourriez stocker cela dans une base de données.
143
- """
144
- if not hasattr(app, 'chat_sessions'):
145
- app.chat_sessions = {}
146
- logging.debug("Création du dictionnaire global des sessions de chat.")
147
-
148
- if session_id not in app.chat_sessions:
149
- logging.info(f"Aucune session de chat existante pour {session_id}. Création d'une nouvelle session.")
150
- app.chat_sessions[session_id] = client.chats.create(model="gemini-2.0-flash")
151
- else:
152
- logging.debug(f"Session de chat existante retrouvée pour {session_id}.")
153
 
154
- return app.chat_sessions[session_id]
155
-
156
- @app.route('/reset', methods=['POST'])
157
- def reset_chat():
158
- """Réinitialise la session de chat actuelle"""
159
- session_id = session.get('session_id')
160
- logging.info(f"Réinitialisation de la session de chat pour {session_id}")
161
-
162
- # Réinitialiser l'historique stocké
163
- if hasattr(app, 'chat_histories') and session_id in app.chat_histories:
164
- app.chat_histories[session_id] = []
165
- logging.debug("Historique de chat réinitialisé.")
166
 
167
- # Réinitialiser la session de chat
168
- if hasattr(app, 'chat_sessions') and session_id in app.chat_sessions:
169
- app.chat_sessions[session_id] = client.chats.create(model="gemini-2.0-flash")
170
- logging.debug("Session de chat réinitialisée dans le dictionnaire des sessions.")
 
 
 
 
 
 
 
 
 
 
 
171
 
172
- return jsonify({'status': 'success'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  if __name__ == '__main__':
175
- logging.info("Démarrage de l'application Flask avec le mode debug activé.")
176
- app.run(debug=True)
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, send_file
 
 
2
  import os
3
+ import json
4
  import uuid
5
+ import threading
6
+ import time
7
+ from datetime import datetime
8
+ import base64
9
+ import mimetypes
10
+ from google import genai
11
+ from google.genai import types
12
+ from reportlab.lib.pagesizes import A4
13
+ from reportlab.platypus import SimpleDocTemplate, Image
14
+ from reportlab.lib.utils import ImageReader
15
+ from PIL import Image as PILImage
16
  import io
 
 
 
 
 
 
 
 
17
 
18
  app = Flask(__name__)
 
 
 
 
 
19
 
20
+ # Configuration
21
+ UPLOAD_FOLDER = 'generated_pages'
22
+ PDF_FOLDER = 'generated_pdfs'
23
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
24
+ os.makedirs(PDF_FOLDER, exist_ok=True)
25
 
26
+ # Stockage des tâches en cours
27
+ active_tasks = {}
28
 
29
+ class MangaGenerator:
30
+ def __init__(self, api_key):
31
+ self.client = genai.Client(api_key=api_key)
32
+ self.model = "gemini-2.5-flash-image-preview"
33
+
34
+ def save_binary_file(self, file_name, data):
35
+ """Sauvegarde un fichier binaire"""
36
+ try:
37
+ with open(file_name, "wb") as f:
38
+ f.write(data)
39
+ return True
40
+ except Exception as e:
41
+ print(f"Erreur lors de la sauvegarde: {e}")
42
+ return False
43
+
44
+ def generate_page(self, prompt, page_number, task_id):
45
+ """Génère une page de manga avec le prompt donné"""
46
  try:
47
+ contents = [
48
+ types.Content(
49
+ role="user",
50
+ parts=[
51
+ types.Part.from_text(text=prompt),
52
+ ],
53
+ ),
54
+ ]
55
 
56
+ generate_content_config = types.GenerateContentConfig(
57
+ response_modalities=["IMAGE", "TEXT"],
 
 
 
58
  )
59
+
60
+ generated_files = []
61
+
62
+ for chunk in self.client.models.generate_content_stream(
63
+ model=self.model,
64
+ contents=contents,
65
+ config=generate_content_config,
66
+ ):
67
+ if (
68
+ chunk.candidates is None
69
+ or chunk.candidates[0].content is None
70
+ or chunk.candidates[0].content.parts is None
71
+ ):
72
+ continue
73
+
74
+ if (chunk.candidates[0].content.parts[0].inline_data and
75
+ chunk.candidates[0].content.parts[0].inline_data.data):
76
+
77
+ file_name = f"{UPLOAD_FOLDER}/page_{page_number}_{task_id}"
78
+ inline_data = chunk.candidates[0].content.parts[0].inline_data
79
+ data_buffer = inline_data.data
80
+ file_extension = mimetypes.guess_extension(inline_data.mime_type) or '.png'
81
+
82
+ full_path = f"{file_name}{file_extension}"
83
+ if self.save_binary_file(full_path, data_buffer):
84
+ generated_files.append(full_path)
85
+ else:
86
+ if hasattr(chunk, 'text') and chunk.text:
87
+ print(f"Texte généré pour page {page_number}: {chunk.text}")
88
+
89
+ return generated_files
90
+
91
  except Exception as e:
92
+ print(f"Erreur lors de la génération de la page {page_number}: {e}")
93
+ return []
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ def create_pdf(image_paths, output_path):
96
+ """Crée un PDF à partir des images générées"""
97
  try:
98
+ doc = SimpleDocTemplate(output_path, pagesize=A4)
99
+ story = []
100
+
101
+ for img_path in sorted(image_paths):
102
+ if os.path.exists(img_path):
103
+ # Redimensionner l'image pour s'adapter à la page A4
104
+ with PILImage.open(img_path) as img:
105
+ img_buffer = io.BytesIO()
106
+ img.save(img_buffer, format='PNG')
107
+ img_buffer.seek(0)
108
+
109
+ # Calculer les dimensions pour s'adapter à A4
110
+ page_width, page_height = A4
111
+ img_width, img_height = img.size
112
+
113
+ # Maintenir le ratio d'aspect
114
+ ratio = min(page_width/img_width, page_height/img_height) * 0.9
115
+ new_width = img_width * ratio
116
+ new_height = img_height * ratio
117
+
118
+ story.append(Image(ImageReader(img_buffer),
119
+ width=new_width, height=new_height))
120
+
121
+ doc.build(story)
122
+ return True
123
+
124
  except Exception as e:
125
+ print(f"Erreur lors de la création du PDF: {e}")
126
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ def generate_manga_task(manga_data, task_id):
129
+ """Tâche de génération de manga qui s'exécute en arrière-plan"""
130
  try:
131
+ api_key = os.environ.get("GEMINI_API_KEY")
132
+ if not api_key:
133
+ active_tasks[task_id]['status'] = 'error'
134
+ active_tasks[task_id]['error'] = 'API Key Gemini non trouvée'
135
+ return
136
+
137
+ generator = MangaGenerator(api_key)
138
+ active_tasks[task_id]['status'] = 'generating'
139
+ active_tasks[task_id]['total_pages'] = len(manga_data)
140
+ active_tasks[task_id]['current_page'] = 0
141
+
142
+ generated_files = []
143
+
144
+ # Trier les parties par ordre numérique
145
+ sorted_parts = sorted(manga_data.items(), key=lambda x: int(x[0].split('-')[1]))
146
+
147
+ for i, (part_key, prompt) in enumerate(sorted_parts, 1):
148
+ active_tasks[task_id]['current_page'] = i
149
+ active_tasks[task_id]['current_part'] = part_key
150
+
151
+ print(f"Génération de la page {i}/{len(sorted_parts)} - {part_key}")
152
+
153
+ page_files = generator.generate_page(prompt, i, task_id)
154
+ generated_files.extend(page_files)
155
+
156
+ # Attendre un peu entre les générations pour éviter les limites de taux
157
+ time.sleep(2)
158
+
159
+ # Créer le PDF
160
+ active_tasks[task_id]['status'] = 'creating_pdf'
161
+ pdf_path = f"{PDF_FOLDER}/manga_{task_id}.pdf"
162
+
163
+ if create_pdf(generated_files, pdf_path):
164
+ active_tasks[task_id]['status'] = 'completed'
165
+ active_tasks[task_id]['pdf_path'] = pdf_path
166
+ active_tasks[task_id]['completed_at'] = datetime.now().isoformat()
167
+ else:
168
+ active_tasks[task_id]['status'] = 'error'
169
+ active_tasks[task_id]['error'] = 'Erreur lors de la création du PDF'
170
+
171
+ except Exception as e:
172
+ active_tasks[task_id]['status'] = 'error'
173
+ active_tasks[task_id]['error'] = str(e)
174
+ print(f"Erreur dans la tâche {task_id}: {e}")
175
 
176
+ @app.route('/')
177
+ def index():
178
+ return render_template('index.html')
179
 
180
+ @app.route('/generate', methods=['POST'])
181
+ def generate():
182
+ try:
183
+ data = request.get_json()
184
+
185
+ if not data:
186
+ return jsonify({'error': 'Aucune donnée JSON fournie'}), 400
187
+
188
+ # Valider que les données contiennent des parties
189
+ manga_parts = {k: v for k, v in data.items() if k.startswith('partie-')}
190
+
191
+ if not manga_parts:
192
+ return jsonify({'error': 'Aucune partie trouvée dans les données'}), 400
193
+
194
+ # Créer une nouvelle tâche
195
+ task_id = str(uuid.uuid4())
196
+ active_tasks[task_id] = {
197
+ 'status': 'queued',
198
+ 'created_at': datetime.now().isoformat(),
199
+ 'manga_data': manga_parts
200
+ }
201
+
202
+ # Lancer la génération en arrière-plan
203
+ thread = threading.Thread(target=generate_manga_task, args=(manga_parts, task_id))
204
+ thread.daemon = True
205
+ thread.start()
206
+
207
  return jsonify({
208
+ 'task_id': task_id,
209
+ 'status': 'queued',
210
+ 'message': 'Génération démarrée'
211
  })
212
+
213
  except Exception as e:
 
214
  return jsonify({'error': str(e)}), 500
215
 
216
+ @app.route('/status/<task_id>')
217
+ def get_status(task_id):
218
+ if task_id not in active_tasks:
219
+ return jsonify({'error': 'Tâche non trouvée'}), 404
 
 
 
 
 
 
 
 
 
 
220
 
221
+ task = active_tasks[task_id].copy()
222
+ # Ne pas renvoyer les données du manga dans le status pour économiser la bande passante
223
+ if 'manga_data' in task:
224
+ del task['manga_data']
225
+
226
+ return jsonify(task)
 
 
 
 
 
 
227
 
228
+ @app.route('/download/<task_id>')
229
+ def download_pdf(task_id):
230
+ if task_id not in active_tasks:
231
+ return jsonify({'error': 'Tâche non trouvée'}), 404
232
+
233
+ task = active_tasks[task_id]
234
+ if task['status'] != 'completed' or 'pdf_path' not in task:
235
+ return jsonify({'error': 'PDF non disponible'}), 400
236
+
237
+ pdf_path = task['pdf_path']
238
+ if not os.path.exists(pdf_path):
239
+ return jsonify({'error': 'Fichier PDF non trouvé'}), 404
240
+
241
+ return send_file(pdf_path, as_attachment=True,
242
+ download_name=f'manga_{task_id}.pdf')
243
 
244
+ @app.route('/tasks')
245
+ def list_tasks():
246
+ # Nettoyer les tâches anciennes (plus de 24h)
247
+ current_time = datetime.now()
248
+ to_remove = []
249
+
250
+ for task_id, task in active_tasks.items():
251
+ created_at = datetime.fromisoformat(task['created_at'])
252
+ if (current_time - created_at).total_seconds() > 86400: # 24 heures
253
+ to_remove.append(task_id)
254
+
255
+ for task_id in to_remove:
256
+ del active_tasks[task_id]
257
+
258
+ # Retourner la liste des tâches sans les données manga
259
+ tasks_summary = {}
260
+ for task_id, task in active_tasks.items():
261
+ task_copy = task.copy()
262
+ if 'manga_data' in task_copy:
263
+ del task_copy['manga_data']
264
+ tasks_summary[task_id] = task_copy
265
+
266
+ return jsonify(tasks_summary)
267
 
268
  if __name__ == '__main__':
269
+ # Vérifier que l'API key est configurée
270
+ if not os.environ.get("GEMINI_API_KEY"):
271
+ print("⚠️ ATTENTION: La variable d'environnement GEMINI_API_KEY n'est pas définie!")
272
+ print(" Définissez-la avec: export GEMINI_API_KEY=votre_clé_api")
273
+
274
+ app.run(debug=True, host='0.0.0.0', port=5000)