Docfile commited on
Commit
8071ff2
·
verified ·
1 Parent(s): 078c480

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +220 -68
app.py CHANGED
@@ -1,4 +1,4 @@
1
- from flask import Flask, render_template, request, jsonify, Response, stream_with_context
2
  from google import genai
3
  from google.genai import types
4
  import os
@@ -7,136 +7,288 @@ import io
7
  import base64
8
  import json
9
  import logging
 
 
10
 
11
  # Configuration du logging
12
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
 
 
13
  logger = logging.getLogger(__name__)
14
 
15
  app = Flask(__name__)
16
 
 
 
 
 
 
17
  # Récupération de la clé API depuis les variables d'environnement
18
  GOOGLE_API_KEY = os.environ.get("GEMINI_API_KEY")
19
  if not GOOGLE_API_KEY:
20
- logger.error("La clé API Gemini n'est pas configurée dans les variables d'environnement")
21
 
22
  # Initialisation du client Gemini
23
  try:
24
  client = genai.Client(api_key=GOOGLE_API_KEY)
 
25
  except Exception as e:
26
  logger.error(f"Erreur lors de l'initialisation du client Gemini: {e}")
 
27
 
28
- @app.route('/')
29
- def index():
30
- return render_template('index.html')
31
 
32
- @app.route('/free')
33
- def maintenance():
34
- return render_template('maj.html')
 
35
 
36
- def process_image(image_data):
37
- """Traite l'image et retourne sa représentation base64"""
38
  try:
39
- img = Image.open(io.BytesIO(image_data))
 
 
 
 
 
 
 
 
 
40
  buffered = io.BytesIO()
41
  img.save(buffered, format="PNG")
42
  img_str = base64.b64encode(buffered.getvalue()).decode()
43
  return img_str
44
  except Exception as e:
45
  logger.error(f"Erreur lors du traitement de l'image: {e}")
46
- raise
47
 
48
- def stream_gemini_response(model_name, image_str, thinking_budget=None):
49
- """Génère et diffuse la réponse du modèle Gemini"""
50
- mode = 'starting'
 
 
51
 
52
- config_kwargs = {}
 
 
 
 
 
53
  if thinking_budget:
54
- config_kwargs["thinking_config"] = types.ThinkingConfig(thinking_budget=thinking_budget)
 
 
55
 
56
  try:
57
- response = client.models.generate_content_stream(
58
- model=model_name,
59
- contents=[
60
- {'inline_data': {'mime_type': 'image/png', 'data': image_str}},
61
- "Résous ça en français with rendering latex"
62
- ],
63
- config=types.GenerateContentConfig(**config_kwargs)
64
  )
65
-
66
- for chunk in response:
 
 
 
 
 
 
 
 
 
67
  if not hasattr(chunk, 'candidates') or not chunk.candidates:
68
  continue
69
 
70
  for part in chunk.candidates[0].content.parts:
 
71
  if hasattr(part, 'thought') and part.thought:
72
  if mode != "thinking":
73
  yield f'data: {json.dumps({"mode": "thinking"})}\n\n'
74
  mode = "thinking"
 
75
  else:
76
  if mode != "answering":
77
  yield f'data: {json.dumps({"mode": "answering"})}\n\n'
78
  mode = "answering"
79
 
 
80
  if hasattr(part, 'text') and part.text:
81
  yield f'data: {json.dumps({"content": part.text})}\n\n'
82
 
83
  except Exception as e:
84
- logger.error(f"Erreur pendant la génération avec le modèle {model_name}: {e}")
85
  yield f'data: {json.dumps({"error": str(e)})}\n\n'
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  @app.route('/solve', methods=['POST'])
88
  def solve():
89
- """Endpoint utilisant le modèle Pro avec capacité de réflexion étendue"""
90
- if 'image' not in request.files:
91
- return jsonify({'error': 'Aucune image fournie'}), 400
92
-
93
  try:
94
- image_data = request.files['image'].read()
95
- img_str = process_image(image_data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- return Response(
98
- stream_with_context(stream_gemini_response(
99
- model_name="gemini-2.5-pro-exp-03-25",
100
- image_str=img_str,
 
 
101
  thinking_budget=8000
102
- )),
103
- mimetype='text/event-stream',
104
- headers={
105
- 'Cache-Control': 'no-cache',
106
- 'X-Accel-Buffering': 'no'
107
- }
108
- )
109
-
 
 
 
 
 
110
  except Exception as e:
111
  logger.error(f"Erreur dans /solve: {e}")
112
- return jsonify({'error': str(e)}), 500
 
113
 
114
  @app.route('/solved', methods=['POST'])
115
  def solved():
116
- """Endpoint utilisant le modèle Flash (plus rapide)"""
117
- if 'image' not in request.files:
118
- return jsonify({'error': 'Aucune image fournie'}), 400
119
-
120
  try:
121
- image_data = request.files['image'].read()
122
- img_str = process_image(image_data)
 
123
 
124
- return Response(
125
- stream_with_context(stream_gemini_response(
126
- model_name="gemini-2.5-flash-preview-04-17",
127
- image_str=img_str
128
- )),
129
- mimetype='text/event-stream',
130
- headers={
131
- 'Cache-Control': 'no-cache',
132
- 'X-Accel-Buffering': 'no'
133
- }
134
- )
135
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  except Exception as e:
137
  logger.error(f"Erreur dans /solved: {e}")
138
- return jsonify({'error': str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  if __name__ == '__main__':
141
- # En production, modifiez ces paramètres
142
- app.run(host='0.0.0.0', port=5000, debug=False)
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, Response, stream_with_context, abort
2
  from google import genai
3
  from google.genai import types
4
  import os
 
7
  import base64
8
  import json
9
  import logging
10
+ from werkzeug.utils import secure_filename
11
+ import mimetypes
12
 
13
  # Configuration du logging
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
17
+ )
18
  logger = logging.getLogger(__name__)
19
 
20
  app = Flask(__name__)
21
 
22
+ # Configuration
23
+ MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16 MB max de taille d'image
24
+ app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
25
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
26
+
27
  # Récupération de la clé API depuis les variables d'environnement
28
  GOOGLE_API_KEY = os.environ.get("GEMINI_API_KEY")
29
  if not GOOGLE_API_KEY:
30
+ logger.error("La clé API Gemini n'est pas configurée. Définissez la variable d'environnement GEMINI_API_KEY.")
31
 
32
  # Initialisation du client Gemini
33
  try:
34
  client = genai.Client(api_key=GOOGLE_API_KEY)
35
+ logger.info("Client Gemini initialisé avec succès")
36
  except Exception as e:
37
  logger.error(f"Erreur lors de l'initialisation du client Gemini: {e}")
38
+ client = None
39
 
 
 
 
40
 
41
+ def allowed_file(filename):
42
+ """Vérifie si le fichier a une extension autorisée"""
43
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
44
+
45
 
46
+ def process_image(file_data):
47
+ """Traite l'image téléchargée et retourne la chaîne base64"""
48
  try:
49
+ img = Image.open(io.BytesIO(file_data))
50
+
51
+ # Redimensionnement si l'image est trop grande (facultatif)
52
+ max_size = 1600 # pixels
53
+ if max(img.size) > max_size:
54
+ ratio = max_size / max(img.size)
55
+ new_size = (int(img.size[0] * ratio), int(img.size[1] * ratio))
56
+ img = img.resize(new_size, Image.LANCZOS)
57
+ logger.info(f"Image redimensionnée à {new_size}")
58
+
59
  buffered = io.BytesIO()
60
  img.save(buffered, format="PNG")
61
  img_str = base64.b64encode(buffered.getvalue()).decode()
62
  return img_str
63
  except Exception as e:
64
  logger.error(f"Erreur lors du traitement de l'image: {e}")
65
+ raise ValueError(f"Impossible de traiter l'image: {str(e)}")
66
 
67
+
68
+ def generate_ai_response(model_id, img_str, prompt, thinking_budget=None):
69
+ """Fonction générique pour la génération de contenu avec les modèles Gemini"""
70
+ if client is None:
71
+ raise ValueError("Le client Gemini n'est pas initialisé")
72
 
73
+ contents = [
74
+ {'inline_data': {'mime_type': 'image/png', 'data': img_str}},
75
+ prompt
76
+ ]
77
+
78
+ config_args = {}
79
  if thinking_budget:
80
+ config_args["thinking_config"] = types.ThinkingConfig(thinking_budget=thinking_budget)
81
+
82
+ config = types.GenerateContentConfig(**config_args)
83
 
84
  try:
85
+ return client.models.generate_content_stream(
86
+ model=model_id,
87
+ contents=contents,
88
+ config=config
 
 
 
89
  )
90
+ except Exception as e:
91
+ logger.error(f"Erreur lors de la génération de contenu avec {model_id}: {e}")
92
+ raise ValueError(f"Erreur lors de la génération: {str(e)}")
93
+
94
+
95
+ def stream_response(response_generator):
96
+ """Fonction qui gère le streaming des réponses"""
97
+ mode = 'starting'
98
+ try:
99
+ for chunk in response_generator:
100
+ # Vérification de validité du chunk
101
  if not hasattr(chunk, 'candidates') or not chunk.candidates:
102
  continue
103
 
104
  for part in chunk.candidates[0].content.parts:
105
+ # Gestion du mode de pensée
106
  if hasattr(part, 'thought') and part.thought:
107
  if mode != "thinking":
108
  yield f'data: {json.dumps({"mode": "thinking"})}\n\n'
109
  mode = "thinking"
110
+ # Mode de réponse
111
  else:
112
  if mode != "answering":
113
  yield f'data: {json.dumps({"mode": "answering"})}\n\n'
114
  mode = "answering"
115
 
116
+ # Envoi du contenu s'il existe
117
  if hasattr(part, 'text') and part.text:
118
  yield f'data: {json.dumps({"content": part.text})}\n\n'
119
 
120
  except Exception as e:
121
+ logger.error(f"Erreur pendant le streaming: {e}")
122
  yield f'data: {json.dumps({"error": str(e)})}\n\n'
123
 
124
+
125
+ @app.route('/')
126
+ def index():
127
+ """Page d'accueil principale"""
128
+ try:
129
+ return render_template('index.html')
130
+ except Exception as e:
131
+ logger.error(f"Erreur lors du rendu de index.html: {e}")
132
+ return "Une erreur est survenue. Veuillez réessayer plus tard.", 500
133
+
134
+
135
+ @app.route('/free')
136
+ def maintenance():
137
+ """Page de maintenance"""
138
+ try:
139
+ return render_template('maj.html')
140
+ except Exception as e:
141
+ logger.error(f"Erreur lors du rendu de maj.html: {e}")
142
+ return "Page en maintenance. Veuillez revenir plus tard.", 503
143
+
144
+
145
+ @app.route('/health')
146
+ def health_check():
147
+ """Endpoint de vérification de santé pour monitoring"""
148
+ status = {
149
+ "status": "ok",
150
+ "gemini_client": client is not None
151
+ }
152
+ return jsonify(status)
153
+
154
+
155
  @app.route('/solve', methods=['POST'])
156
  def solve():
157
+ """Endpoint utilisant le modèle Pro avec capacités de réflexion étendues"""
158
+ if not client:
159
+ return jsonify({"error": "Service non disponible - client Gemini non initialisé"}), 503
160
+
161
  try:
162
+ # Vérification de l'image
163
+ if 'image' not in request.files:
164
+ return jsonify({"error": "Aucune image n'a été envoyée"}), 400
165
+
166
+ file = request.files['image']
167
+
168
+ if file.filename == '':
169
+ return jsonify({"error": "Aucun fichier sélectionné"}), 400
170
+
171
+ if not allowed_file(file.filename):
172
+ return jsonify({"error": "Format de fichier non autorisé"}), 400
173
+
174
+ # Détection du type de contenu
175
+ file_data = file.read()
176
+ content_type = mimetypes.guess_type(file.filename)[0]
177
+ if not content_type or not content_type.startswith('image/'):
178
+ return jsonify({"error": "Le fichier envoyé n'est pas une image valide"}), 400
179
+
180
+ # Traitement de l'image
181
+ try:
182
+ img_str = process_image(file_data)
183
+ except ValueError as e:
184
+ return jsonify({"error": str(e)}), 400
185
 
186
+ # Génération de la réponse
187
+ try:
188
+ response_generator = generate_ai_response(
189
+ model_id="gemini-2.5-pro-exp-03-25",
190
+ img_str=img_str,
191
+ prompt="Résous ça en français with rendering latex",
192
  thinking_budget=8000
193
+ )
194
+
195
+ return Response(
196
+ stream_with_context(stream_response(response_generator)),
197
+ mimetype='text/event-stream',
198
+ headers={
199
+ 'Cache-Control': 'no-cache',
200
+ 'X-Accel-Buffering': 'no'
201
+ }
202
+ )
203
+ except ValueError as e:
204
+ return jsonify({"error": str(e)}), 500
205
+
206
  except Exception as e:
207
  logger.error(f"Erreur dans /solve: {e}")
208
+ return jsonify({"error": "Une erreur inconnue est survenue"}), 500
209
+
210
 
211
  @app.route('/solved', methods=['POST'])
212
  def solved():
213
+ """Endpoint utilisant le modèle Flash pour des réponses plus rapides"""
214
+ if not client:
215
+ return jsonify({"error": "Service non disponible - client Gemini non initialisé"}), 503
216
+
217
  try:
218
+ # Vérification de l'image
219
+ if 'image' not in request.files:
220
+ return jsonify({"error": "Aucune image n'a été envoyée"}), 400
221
 
222
+ file = request.files['image']
223
+
224
+ if file.filename == '':
225
+ return jsonify({"error": "Aucun fichier sélectionné"}), 400
226
+
227
+ if not allowed_file(file.filename):
228
+ return jsonify({"error": "Format de fichier non autorisé"}), 400
229
+
230
+ # Détection du type de contenu
231
+ file_data = file.read()
232
+ content_type = mimetypes.guess_type(file.filename)[0]
233
+ if not content_type or not content_type.startswith('image/'):
234
+ return jsonify({"error": "Le fichier envoyé n'est pas une image valide"}), 400
235
+
236
+ # Traitement de l'image
237
+ try:
238
+ img_str = process_image(file_data)
239
+ except ValueError as e:
240
+ return jsonify({"error": str(e)}), 400
241
+
242
+ # Génération de la réponse
243
+ try:
244
+ response_generator = generate_ai_response(
245
+ model_id="gemini-2.5-flash-preview-04-17",
246
+ img_str=img_str,
247
+ prompt="Résous ça en français with rendering latex"
248
+ )
249
+
250
+ return Response(
251
+ stream_with_context(stream_response(response_generator)),
252
+ mimetype='text/event-stream',
253
+ headers={
254
+ 'Cache-Control': 'no-cache',
255
+ 'X-Accel-Buffering': 'no'
256
+ }
257
+ )
258
+ except ValueError as e:
259
+ return jsonify({"error": str(e)}), 500
260
+
261
  except Exception as e:
262
  logger.error(f"Erreur dans /solved: {e}")
263
+ return jsonify({"error": "Une erreur inconnue est survenue"}), 500
264
+
265
+
266
+ @app.errorhandler(413)
267
+ def request_entity_too_large(error):
268
+ """Gestion de l'erreur de fichier trop volumineux"""
269
+ return jsonify({"error": f"Le fichier est trop volumineux. Taille maximale: {MAX_CONTENT_LENGTH/1024/1024} MB"}), 413
270
+
271
+
272
+ @app.errorhandler(404)
273
+ def page_not_found(error):
274
+ """Gestion de l'erreur 404"""
275
+ return jsonify({"error": "Page non trouvée"}), 404
276
+
277
+
278
+ @app.errorhandler(500)
279
+ def internal_server_error(error):
280
+ """Gestion de l'erreur 500"""
281
+ logger.error(f"Erreur 500: {error}")
282
+ return jsonify({"error": "Erreur interne du serveur"}), 500
283
+
284
 
285
  if __name__ == '__main__':
286
+ # Vérification des dépendances et configuration avant le démarrage
287
+ if not client:
288
+ logger.warning("L'application démarre sans client Gemini initialisé. Certaines fonctionnalités seront indisponibles.")
289
+
290
+ # Configuration pour le développement
291
+ debug_mode = os.environ.get("FLASK_DEBUG", "False").lower() == "true"
292
+
293
+ port = int(os.environ.get("PORT", 5000))
294
+ app.run(debug=debug_mode, host='0.0.0.0', port=port)