Docfile commited on
Commit
e99fdac
·
verified ·
1 Parent(s): f5c0144

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +195 -97
app.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import os
2
  import logging
3
  import base64
@@ -22,9 +24,9 @@ api_key = os.environ.get("GEMINI_API_KEY")
22
  if not api_key:
23
  logger.warning("GEMINI_API_KEY not found in environment variables")
24
  else:
 
25
  logger.info("GEMINI_API_KEY found. API configured successfully.")
26
 
27
- genai.configure(api_key=api_key)
28
 
29
  # Initialize Flask app
30
  app = Flask(__name__)
@@ -45,140 +47,194 @@ def session_required(f):
45
  # Ensure upload directory exists
46
  os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
47
 
48
- # Configure Gemini model with specific parameters for better responses
49
- model = genai.GenerativeModel(
50
- model_name='gemini-2.0-flash',
51
- generation_config={
52
- 'temperature': 0.7, # Slightly creative but still focused
53
- 'top_p': 0.9, # Diverse output but not too random
54
- 'top_k': 40, # Reasonable range of tokens to consider
55
- 'max_output_tokens': 2048 # Allow longer responses
56
- }
57
- )
 
 
 
 
 
 
 
 
 
 
58
 
59
- # Configure Gemini vision model for image processing
60
- vision_model = genai.GenerativeModel('gemini-2.0-vision-flash')
61
 
62
  @app.route('/')
63
  @session_required
64
  def index():
65
  """Render the chat interface."""
 
 
66
  return render_template('index.html')
67
 
68
  @app.route('/api/chat', methods=['POST'])
69
  @session_required
70
  def chat():
71
  """Process chat messages and get responses from Gemini API."""
 
 
 
 
 
 
 
72
  try:
73
  data = request.json
74
  user_message = data.get('message', '')
75
  chat_history = data.get('history', [])
76
- image_data = data.get('image', None)
77
-
78
  if not user_message and not image_data:
79
  return jsonify({'error': 'Veuillez entrer un message ou joindre une image.'}), 400
80
-
81
  # Log the incoming request (but not full chat history for privacy)
82
  session_id = session.get('session_id')
83
- logger.info(f"Received chat request from session {session_id}. Message length: {len(user_message)}")
84
-
85
- # Format conversation history for context
86
- formatted_history = []
87
- for msg in chat_history[-15:]: # Use the last 15 messages for more context
88
- role = "user" if msg['sender'] == 'user' else "model"
89
- formatted_history.append({"role": role, "parts": [msg['text']]})
90
-
91
  # Handle image processing if an image is included
92
  if image_data:
 
 
 
93
  try:
94
- # Process image with vision model
95
- image_base64 = image_data.split(',')[1]
96
- image = genai.types.Part.from_data(
97
- data=base64.b64decode(image_base64),
98
- mime_type="image/jpeg"
99
- )
100
-
101
- # Save image with timestamp in the session directory
102
- session_id = session.get('session_id')
 
 
 
 
103
  session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id)
104
  os.makedirs(session_dir, exist_ok=True)
105
-
106
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
107
- filename = secure_filename(f"image_{timestamp}.jpg")
 
 
108
  filepath = os.path.join(session_dir, filename)
109
  with open(filepath, "wb") as f:
110
- f.write(base64.b64decode(image_base64))
111
-
112
- # Create message parts with both text and image
113
- parts = [image]
114
- if user_message:
115
- parts.append(user_message)
116
-
 
 
 
117
  # Generate response using vision model
 
118
  response = vision_model.generate_content(parts)
 
119
  return jsonify({'response': response.text})
120
-
 
 
 
121
  except Exception as img_error:
122
- logger.error(f"Error processing image: {str(img_error)}")
 
123
  return jsonify({
124
  'error': 'Désolé, une erreur est survenue lors du traitement de l\'image. Veuillez réessayer.'
125
  }), 500
126
  else:
127
  # Text-only processing
128
- # Create a chat session with history
129
- chat = model.start_chat(history=formatted_history)
130
-
131
- # Generate response
132
- response = chat.send_message(user_message)
133
-
134
- # Log successful response
135
- logger.info(f"Generated response successfully. Response length: {len(response.text)}")
136
-
137
- # Return the response
138
- return jsonify({'response': response.text})
139
-
140
- except genai.types.generation_types.BlockedPromptException as be:
141
- logger.warning(f"Content blocked: {str(be)}")
142
- return jsonify({
143
- 'error': 'Votre message ou la conversation ne peut pas être traitée car elle contient du contenu potentiellement inapproprié.'
144
- }), 400
145
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  except Exception as e:
147
- logger.error(f"Error in chat endpoint: {str(e)}")
 
148
  return jsonify({
149
- 'error': 'Désolé, j\'ai rencontré une erreur lors du traitement de votre demande. Veuillez réessayer.'
150
  }), 500
151
 
 
152
  @app.route('/api/save-chat', methods=['POST'])
153
  @session_required
154
  def save_chat():
155
  """Save the current chat history."""
156
  try:
157
  session_id = session.get('session_id')
158
-
 
 
159
  # Create session-specific directory
160
  session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id)
161
  os.makedirs(session_dir, exist_ok=True)
162
-
163
  data = request.json
164
  chat_history = data.get('history', [])
165
-
166
  if not chat_history:
167
  return jsonify({'error': 'Aucune conversation à sauvegarder.'}), 400
168
-
169
  # Generate filename with timestamp
170
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
171
  filename = f"chat_{timestamp}.json"
172
  filepath = os.path.join(session_dir, filename)
173
-
174
  # Save chat history to file
175
  with open(filepath, 'w', encoding='utf-8') as f:
176
  json.dump(chat_history, f, ensure_ascii=False, indent=2)
177
-
178
- return jsonify({'success': True, 'filename': filename})
179
-
 
180
  except Exception as e:
181
- logger.error(f"Error saving chat: {str(e)}")
182
  return jsonify({
183
  'error': 'Désolé, une erreur est survenue lors de la sauvegarde de la conversation.'
184
  }), 500
@@ -189,33 +245,42 @@ def load_chats():
189
  """Get a list of saved chat files for current session."""
190
  try:
191
  session_id = session.get('session_id')
192
-
 
 
193
  # Get session-specific directory
194
  session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id)
195
-
196
  # If the directory doesn't exist yet, return empty list
197
  if not os.path.exists(session_dir):
 
198
  return jsonify({'chats': []})
199
-
200
  chat_files = []
201
  for filename in os.listdir(session_dir):
 
202
  if filename.startswith('chat_') and filename.endswith('.json'):
203
- # Extract timestamp from filename
204
- timestamp = filename[5:-5] # Remove 'chat_' and '.json'
205
- # Add to list
206
- chat_files.append({
207
- 'filename': filename,
208
- 'timestamp': timestamp
209
- })
210
-
211
- # Sort by timestamp (newest first)
 
 
 
 
 
212
  chat_files.sort(key=lambda x: x['timestamp'], reverse=True)
213
-
214
  logger.info(f"Loaded {len(chat_files)} chats for session {session_id}")
215
  return jsonify({'chats': chat_files})
216
-
217
  except Exception as e:
218
- logger.error(f"Error loading chat list: {str(e)}")
219
  return jsonify({
220
  'error': 'Désolé, une erreur est survenue lors du chargement des conversations.'
221
  }), 500
@@ -226,25 +291,58 @@ def load_chat(filename):
226
  """Load a specific chat history file."""
227
  try:
228
  session_id = session.get('session_id')
229
-
 
 
 
 
 
 
 
 
230
  # Load from session-specific directory
231
  session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id)
232
- filepath = os.path.join(session_dir, secure_filename(filename))
233
-
234
  if not os.path.exists(filepath):
 
235
  return jsonify({'error': 'Conversation introuvable.'}), 404
236
-
 
 
 
 
 
237
  with open(filepath, 'r', encoding='utf-8') as f:
238
  chat_history = json.load(f)
239
-
240
- logger.info(f"Loaded chat {filename} for session {session_id}")
 
 
 
 
 
 
 
 
 
 
241
  return jsonify({'history': chat_history})
242
-
 
 
 
 
 
 
243
  except Exception as e:
244
- logger.error(f"Error loading chat file: {str(e)}")
245
  return jsonify({
246
  'error': 'Désolé, une erreur est survenue lors du chargement de la conversation.'
247
  }), 500
248
 
249
  if __name__ == '__main__':
250
- app.run(host='0.0.0.0', port=5000, debug=True)
 
 
 
 
1
+ # --- START OF FILE GeminiChatbot/app.py ---
2
+
3
  import os
4
  import logging
5
  import base64
 
24
  if not api_key:
25
  logger.warning("GEMINI_API_KEY not found in environment variables")
26
  else:
27
+ genai.configure(api_key=api_key)
28
  logger.info("GEMINI_API_KEY found. API configured successfully.")
29
 
 
30
 
31
  # Initialize Flask app
32
  app = Flask(__name__)
 
47
  # Ensure upload directory exists
48
  os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
49
 
50
+ # Initialize models only if API key is present
51
+ model = None
52
+ vision_model = None
53
+ if api_key:
54
+ # Configure Gemini model with specific parameters for better responses
55
+ model = genai.GenerativeModel(
56
+ model_name='gemini-1.0-pro', # Changed model name as 'gemini-2.0-flash' is not standard
57
+ generation_config={
58
+ 'temperature': 0.7, # Slightly creative but still focused
59
+ 'top_p': 0.9, # Diverse output but not too random
60
+ 'top_k': 40, # Reasonable range of tokens to consider
61
+ 'max_output_tokens': 2048 # Allow longer responses
62
+ }
63
+ )
64
+
65
+ # Configure Gemini vision model for image processing
66
+ # Using gemini-1.5-flash as it's generally available and supports vision
67
+ vision_model = genai.GenerativeModel('gemini-1.5-flash')
68
+ else:
69
+ logger.error("Cannot initialize Gemini models: API Key is missing.")
70
 
 
 
71
 
72
  @app.route('/')
73
  @session_required
74
  def index():
75
  """Render the chat interface."""
76
+ if not api_key:
77
+ return "Erreur: Clé API Gemini manquante. Veuillez configurer GEMINI_API_KEY.", 500
78
  return render_template('index.html')
79
 
80
  @app.route('/api/chat', methods=['POST'])
81
  @session_required
82
  def chat():
83
  """Process chat messages and get responses from Gemini API."""
84
+ if not api_key:
85
+ logger.error("Chat request failed: API Key is missing.")
86
+ return jsonify({'error': 'Configuration serveur incomplète (clé API manquante).'}), 500
87
+ if not model or not vision_model:
88
+ logger.error("Chat request failed: Models not initialized.")
89
+ return jsonify({'error': 'Configuration serveur incomplète (modèles non initialisés).'}), 500
90
+
91
  try:
92
  data = request.json
93
  user_message = data.get('message', '')
94
  chat_history = data.get('history', [])
95
+ image_data = data.get('image', None) # Expecting a single image base64 string for now
96
+
97
  if not user_message and not image_data:
98
  return jsonify({'error': 'Veuillez entrer un message ou joindre une image.'}), 400
99
+
100
  # Log the incoming request (but not full chat history for privacy)
101
  session_id = session.get('session_id')
102
+ logger.info(f"Received chat request from session {session_id}. Message length: {len(user_message)}. Image attached: {'Yes' if image_data else 'No'}")
103
+
 
 
 
 
 
 
104
  # Handle image processing if an image is included
105
  if image_data:
106
+ if not vision_model:
107
+ logger.error("Vision model not available.")
108
+ return jsonify({'error': 'Le modèle de vision n\'est pas configuré.'}), 500
109
  try:
110
+ # Decode image data
111
+ # Assuming image_data is "data:image/jpeg;base64,..."
112
+ image_info, image_base64 = image_data.split(',', 1)
113
+ mime_type = image_info.split(':')[1].split(';')[0] # Extract mime type like "image/jpeg"
114
+ image_bytes = base64.b64decode(image_base64) # Get raw bytes
115
+
116
+ # Create the image part as a dictionary (CORRECTED METHOD)
117
+ image_part = {
118
+ "mime_type": mime_type,
119
+ "data": image_bytes
120
+ }
121
+
122
+ # --- Save image (optional but good practice) ---
123
  session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id)
124
  os.makedirs(session_dir, exist_ok=True)
 
125
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
126
+ # Try to get extension from mime type
127
+ extension = mime_type.split('/')[-1] if '/' in mime_type else 'jpg'
128
+ filename = secure_filename(f"image_{timestamp}.{extension}")
129
  filepath = os.path.join(session_dir, filename)
130
  with open(filepath, "wb") as f:
131
+ f.write(image_bytes)
132
+ logger.info(f"Saved uploaded image to {filepath}")
133
+ # --- End Save image ---
134
+
135
+ # Create message parts list for generate_content (CORRECTED METHOD)
136
+ parts = []
137
+ if user_message: # Add text first if it exists
138
+ parts.append(user_message)
139
+ parts.append(image_part) # Add the image dictionary
140
+
141
  # Generate response using vision model
142
+ logger.debug(f"Sending parts to vision model: {[type(p) if not isinstance(p, dict) else 'dict(image)' for p in parts]}")
143
  response = vision_model.generate_content(parts)
144
+ logger.info(f"Generated vision response successfully. Response length: {len(response.text)}")
145
  return jsonify({'response': response.text})
146
+
147
+ except (ValueError, IndexError) as decode_error:
148
+ logger.error(f"Error decoding image data: {str(decode_error)}")
149
+ return jsonify({'error': 'Format de données d\'image invalide.'}), 400
150
  except Exception as img_error:
151
+ # Log the full traceback for better debugging
152
+ logger.exception(f"Error processing image: {str(img_error)}")
153
  return jsonify({
154
  'error': 'Désolé, une erreur est survenue lors du traitement de l\'image. Veuillez réessayer.'
155
  }), 500
156
  else:
157
  # Text-only processing
158
+ if not model:
159
+ logger.error("Text model not available.")
160
+ return jsonify({'error': 'Le modèle de texte n\'est pas configuré.'}), 500
161
+
162
+ # Format conversation history for context
163
+ formatted_history = []
164
+ for msg in chat_history[-15:]: # Use the last 15 messages for more context
165
+ role = "user" if msg['sender'] == 'user' else "model"
166
+ # Ensure message text is not None or empty before adding
167
+ if msg.get('text'):
168
+ formatted_history.append({"role": role, "parts": [msg['text']]})
169
+ # Note: History currently doesn't include images sent previously.
170
+ # Handling multimodal history requires storing image references/data
171
+ # and formatting them correctly for the API on subsequent turns.
172
+
173
+ try:
174
+ # Create a chat session with history
175
+ chat_session = model.start_chat(history=formatted_history)
176
+
177
+ # Generate response
178
+ response = chat_session.send_message(user_message)
179
+
180
+ # Log successful response
181
+ logger.info(f"Generated text response successfully. Response length: {len(response.text)}")
182
+
183
+ # Return the response
184
+ return jsonify({'response': response.text})
185
+
186
+ except genai.types.generation_types.BlockedPromptException as be:
187
+ logger.warning(f"Content blocked for session {session_id}: {str(be)}")
188
+ return jsonify({
189
+ 'error': 'Votre message ou la conversation contient du contenu potentiellement inapproprié et ne peut pas être traité.'
190
+ }), 400
191
+ except Exception as e:
192
+ logger.exception(f"Error during text generation for session {session_id}: {str(e)}")
193
+ return jsonify({
194
+ 'error': 'Désolé, une erreur est survenue lors de la génération de la réponse texte.'
195
+ }), 500
196
+
197
  except Exception as e:
198
+ # Catch-all for unexpected errors (like issues reading request JSON)
199
+ logger.exception(f"Unhandled error in chat endpoint: {str(e)}")
200
  return jsonify({
201
+ 'error': 'Désolé, j\'ai rencontré une erreur inattendue. Veuillez réessayer.'
202
  }), 500
203
 
204
+
205
  @app.route('/api/save-chat', methods=['POST'])
206
  @session_required
207
  def save_chat():
208
  """Save the current chat history."""
209
  try:
210
  session_id = session.get('session_id')
211
+ if not session_id:
212
+ return jsonify({'error': 'Session introuvable.'}), 400
213
+
214
  # Create session-specific directory
215
  session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id)
216
  os.makedirs(session_dir, exist_ok=True)
217
+
218
  data = request.json
219
  chat_history = data.get('history', [])
220
+
221
  if not chat_history:
222
  return jsonify({'error': 'Aucune conversation à sauvegarder.'}), 400
223
+
224
  # Generate filename with timestamp
225
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
226
  filename = f"chat_{timestamp}.json"
227
  filepath = os.path.join(session_dir, filename)
228
+
229
  # Save chat history to file
230
  with open(filepath, 'w', encoding='utf-8') as f:
231
  json.dump(chat_history, f, ensure_ascii=False, indent=2)
232
+
233
+ logger.info(f"Chat history saved for session {session_id} to {filename}")
234
+ return jsonify({'success': True, 'filename': filename, 'timestamp': timestamp}) # Return timestamp too
235
+
236
  except Exception as e:
237
+ logger.exception(f"Error saving chat for session {session_id}: {str(e)}")
238
  return jsonify({
239
  'error': 'Désolé, une erreur est survenue lors de la sauvegarde de la conversation.'
240
  }), 500
 
245
  """Get a list of saved chat files for current session."""
246
  try:
247
  session_id = session.get('session_id')
248
+ if not session_id:
249
+ return jsonify({'error': 'Session introuvable.'}), 400
250
+
251
  # Get session-specific directory
252
  session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id)
253
+
254
  # If the directory doesn't exist yet, return empty list
255
  if not os.path.exists(session_dir):
256
+ logger.info(f"No chat directory found for session {session_id}")
257
  return jsonify({'chats': []})
258
+
259
  chat_files = []
260
  for filename in os.listdir(session_dir):
261
+ # Ensure we only list chat files, not uploaded images etc.
262
  if filename.startswith('chat_') and filename.endswith('.json'):
263
+ try:
264
+ # Extract timestamp from filename 'chat_YYYYMMDD_HHMMSS.json'
265
+ timestamp_str = filename[5:-5] # Remove 'chat_' and '.json'
266
+ # Validate timestamp format (optional but good)
267
+ datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
268
+ chat_files.append({
269
+ 'filename': filename,
270
+ 'timestamp': timestamp_str # Keep original string for sorting/display
271
+ })
272
+ except ValueError:
273
+ logger.warning(f"Skipping file with unexpected format: {filename} in {session_dir}")
274
+
275
+
276
+ # Sort by timestamp string (lexicographical sort works for YYYYMMDD_HHMMSS)
277
  chat_files.sort(key=lambda x: x['timestamp'], reverse=True)
278
+
279
  logger.info(f"Loaded {len(chat_files)} chats for session {session_id}")
280
  return jsonify({'chats': chat_files})
281
+
282
  except Exception as e:
283
+ logger.exception(f"Error loading chat list for session {session_id}: {str(e)}")
284
  return jsonify({
285
  'error': 'Désolé, une erreur est survenue lors du chargement des conversations.'
286
  }), 500
 
291
  """Load a specific chat history file."""
292
  try:
293
  session_id = session.get('session_id')
294
+ if not session_id:
295
+ return jsonify({'error': 'Session introuvable.'}), 400
296
+
297
+ # Secure the filename before using it
298
+ safe_filename = secure_filename(filename)
299
+ if not safe_filename.startswith('chat_') or not safe_filename.endswith('.json'):
300
+ logger.warning(f"Attempt to load invalid chat filename: {filename} (secured: {safe_filename}) for session {session_id}")
301
+ return jsonify({'error': 'Nom de fichier de conversation invalide.'}), 400
302
+
303
  # Load from session-specific directory
304
  session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id)
305
+ filepath = os.path.join(session_dir, safe_filename)
306
+
307
  if not os.path.exists(filepath):
308
+ logger.warning(f"Chat file not found: {filepath} for session {session_id}")
309
  return jsonify({'error': 'Conversation introuvable.'}), 404
310
+
311
+ # Check if path is still within the intended directory (security measure)
312
+ if not os.path.abspath(filepath).startswith(os.path.abspath(session_dir)):
313
+ logger.error(f"Attempt to access file outside session directory: {filepath}")
314
+ return jsonify({'error': 'Accès non autorisé.'}), 403
315
+
316
  with open(filepath, 'r', encoding='utf-8') as f:
317
  chat_history = json.load(f)
318
+
319
+ # Basic validation of loaded history format (optional)
320
+ if not isinstance(chat_history, list):
321
+ raise ValueError("Invalid chat history format in file.")
322
+ for item in chat_history:
323
+ if not isinstance(item, dict) or 'sender' not in item or 'text' not in item:
324
+ # Allow for messages that might only have text or image data later
325
+ if 'sender' not in item:
326
+ raise ValueError("Invalid message format in chat history.")
327
+
328
+
329
+ logger.info(f"Loaded chat {safe_filename} for session {session_id}")
330
  return jsonify({'history': chat_history})
331
+
332
+ except json.JSONDecodeError:
333
+ logger.error(f"Error decoding JSON from chat file: {safe_filename} for session {session_id}")
334
+ return jsonify({'error': 'Le fichier de conversation est corrompu.'}), 500
335
+ except ValueError as ve:
336
+ logger.error(f"Invalid content in chat file {safe_filename}: {str(ve)}")
337
+ return jsonify({'error': f'Format invalide dans le fichier de conversation: {str(ve)}'}), 500
338
  except Exception as e:
339
+ logger.exception(f"Error loading chat file {safe_filename} for session {session_id}: {str(e)}")
340
  return jsonify({
341
  'error': 'Désolé, une erreur est survenue lors du chargement de la conversation.'
342
  }), 500
343
 
344
  if __name__ == '__main__':
345
+ # Use 0.0.0.0 to be accessible on the network, debug=False for production
346
+ # Port 8080 is often used as an alternative to 5000
347
+ app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080)), debug=os.environ.get('FLASK_DEBUG', 'False').lower() == 'true')
348
+ # --- END OF FILE GeminiChatbot/app.py ---