Docfile commited on
Commit
ca9f82a
·
verified ·
1 Parent(s): 4ea31b4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -262
app.py CHANGED
@@ -1,4 +1,4 @@
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
@@ -6,289 +6,167 @@ from PIL import Image
6
  import io
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)
 
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
 
6
  import io
7
  import base64
8
  import json
 
 
 
 
 
 
 
 
 
 
9
 
10
  app = Flask(__name__)
11
 
 
 
 
 
 
 
12
  GOOGLE_API_KEY = os.environ.get("GEMINI_API_KEY")
 
 
 
 
 
 
 
 
 
 
13
 
14
+ client = genai.Client(
15
+ api_key=GOOGLE_API_KEY,
16
+ )
17
 
18
+ @app.route('/')
19
+ def index():
20
+ return render_template('index.html')
21
 
22
+ @app.route('/free')
23
+ def indexx():
24
+ return render_template('maj.html')
25
 
26
+ @app.route('/solve', methods=['POST'])
27
+ def solve():
28
  try:
29
+ image_data = request.files['image'].read()
30
+ img = Image.open(io.BytesIO(image_data))
31
+
 
 
 
 
 
 
 
32
  buffered = io.BytesIO()
33
  img.save(buffered, format="PNG")
34
  img_str = base64.b64encode(buffered.getvalue()).decode()
 
 
 
 
35
 
36
+ def generate():
37
+ mode = 'starting'
38
+ try:
39
+ response = client.models.generate_content_stream(
40
+ model="gemini-2.5-pro-exp-03-25",
41
+ contents=[
42
+ {'inline_data': {'mime_type': 'image/png', 'data': img_str}},
43
+ """Résous cet exercice en français avec du LaTeX.
44
+ Si nécessaire, utilise du code Python pour effectuer les calculs complexes.
45
+ Présente ta solution de façon claire et espacée."""
46
+ ],
47
+ config=types.GenerateContentConfig(
48
+ thinking_config=types.ThinkingConfig(
49
+ thinking_budget=8000
50
+ ),
51
+ tools=[types.Tool(
52
+ code_execution=types.ToolCodeExecution()
53
+ )]
54
+ )
55
+ )
56
+
57
+ for chunk in response:
58
+ for part in chunk.candidates[0].content.parts:
59
+ if hasattr(part, 'thought') and part.thought:
60
+ if mode != "thinking":
61
+ yield 'data: ' + json.dumps({"mode": "thinking"}) + '\n\n'
62
+ mode = "thinking"
63
+ elif hasattr(part, 'executable_code') and part.executable_code:
64
+ if mode != "executing_code":
65
+ yield 'data: ' + json.dumps({"mode": "executing_code"}) + '\n\n'
66
+ mode = "executing_code"
67
+ code_block_open = "```python\n"
68
+ code_block_close = "\n```"
69
+ yield 'data: ' + json.dumps({"content": code_block_open + part.executable_code.code + code_block_close}) + '\n\n'
70
+ elif hasattr(part, 'code_execution_result') and part.code_execution_result:
71
+ if mode != "code_result":
72
+ yield 'data: ' + json.dumps({"mode": "code_result"}) + '\n\n'
73
+ mode = "code_result"
74
+ result_block_open = "Résultat d'exécution:\n```\n"
75
+ result_block_close = "\n```"
76
+ yield 'data: ' + json.dumps({"content": result_block_open + part.code_execution_result.output + result_block_close}) + '\n\n'
77
+ else:
78
+ if mode != "answering":
79
+ yield 'data: ' + json.dumps({"mode": "answering"}) + '\n\n'
80
+ mode = "answering"
81
+ if hasattr(part, 'text') and part.text:
82
+ yield 'data: ' + json.dumps({"content": part.text}) + '\n\n'
83
+
84
+ except Exception as e:
85
+ print(f"Error during generation: {e}")
86
+ yield 'data: ' + json.dumps({"error": str(e)}) + '\n\n'
87
+
88
+ return Response(
89
+ stream_with_context(generate()),
90
+ mimetype='text/event-stream',
91
+ headers={
92
+ 'Cache-Control': 'no-cache',
93
+ 'X-Accel-Buffering': 'no'
94
+ }
95
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
 
 
 
 
 
97
  except Exception as e:
98
+ return jsonify({'error': str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  @app.route('/solved', methods=['POST'])
101
  def solved():
 
 
 
 
102
  try:
103
+ image_data = request.files['image'].read()
104
+ img = Image.open(io.BytesIO(image_data))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
+ buffered = io.BytesIO()
107
+ img.save(buffered, format="PNG")
108
+ img_str = base64.b64encode(buffered.getvalue()).decode()
109
 
110
+ def generate():
111
+ mode = 'starting'
112
+ try:
113
+ response = client.models.generate_content_stream(
114
+ model="gemini-2.5-flash-preview-04-17",
115
+ contents=[
116
+ {'inline_data': {'mime_type': 'image/png', 'data': img_str}},
117
+ """Résous cet exercice en français avec du LaTeX.
118
+ Si nécessaire, utilise du code Python pour effectuer les calculs complexes.
119
+ Présente ta solution de façon claire et espacée."""
120
+ ],
121
+ config=types.GenerateContentConfig(
122
+ tools=[types.Tool(
123
+ code_execution=types.ToolCodeExecution()
124
+ )]
125
+ )
126
+ )
127
+
128
+ for chunk in response:
129
+ for part in chunk.candidates[0].content.parts:
130
+ if hasattr(part, 'thought') and part.thought:
131
+ if mode != "thinking":
132
+ yield 'data: ' + json.dumps({"mode": "thinking"}) + '\n\n'
133
+ mode = "thinking"
134
+ elif hasattr(part, 'executable_code') and part.executable_code:
135
+ if mode != "executing_code":
136
+ yield 'data: ' + json.dumps({"mode": "executing_code"}) + '\n\n'
137
+ mode = "executing_code"
138
+ code_block_open = "```python\n"
139
+ code_block_close = "\n```"
140
+ yield 'data: ' + json.dumps({"content": code_block_open + part.executable_code.code + code_block_close}) + '\n\n'
141
+ elif hasattr(part, 'code_execution_result') and part.code_execution_result:
142
+ if mode != "code_result":
143
+ yield 'data: ' + json.dumps({"mode": "code_result"}) + '\n\n'
144
+ mode = "code_result"
145
+ result_block_open = "Résultat d'exécution:\n```\n"
146
+ result_block_close = "\n```"
147
+ yield 'data: ' + json.dumps({"content": result_block_open + part.code_execution_result.output + result_block_close}) + '\n\n'
148
+ else:
149
+ if mode != "answering":
150
+ yield 'data: ' + json.dumps({"mode": "answering"}) + '\n\n'
151
+ mode = "answering"
152
+ if hasattr(part, 'text') and part.text:
153
+ yield 'data: ' + json.dumps({"content": part.text}) + '\n\n'
154
+
155
+ except Exception as e:
156
+ print(f"Error during generation: {e}")
157
+ yield 'data: ' + json.dumps({"error": str(e)}) + '\n\n'
158
+
159
+ return Response(
160
+ stream_with_context(generate()),
161
+ mimetype='text/event-stream',
162
+ headers={
163
+ 'Cache-Control': 'no-cache',
164
+ 'X-Accel-Buffering': 'no'
165
+ }
166
+ )
167
 
168
+ except Exception as e:
169
+ return jsonify({'error': str(e)}), 500
170
 
171
  if __name__ == '__main__':
172
+ app.run(debug=True)