import os import logging import base64 import json import uuid import google.generativeai as genai from datetime import datetime from functools import wraps from flask import Flask, render_template, request, jsonify, session, redirect from dotenv import load_dotenv from werkzeug.utils import secure_filename # Configure logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) # Load environment variables load_dotenv() # Configure Google Gemini API api_key = os.environ.get("GEMINI_API_KEY") if not api_key: logger.warning("GEMINI_API_KEY not found in environment variables") else: logger.info("GEMINI_API_KEY found. API configured successfully.") genai.configure(api_key=api_key) # Initialize Flask app app = Flask(__name__) app.secret_key = os.environ.get("SESSION_SECRET", "default-dev-secret-key") app.config['UPLOAD_FOLDER'] = 'static/uploads' app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024 # 10 MB max # Middleware to ensure user has a session_id def session_required(f): @wraps(f) def decorated_function(*args, **kwargs): if 'session_id' not in session: session['session_id'] = str(uuid.uuid4()) logger.info(f"Created new session: {session['session_id']}") return f(*args, **kwargs) return decorated_function # Ensure upload directory exists os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) # Configure Gemini model with specific parameters for better responses model = genai.GenerativeModel( model_name='gemini-2.0-flash', generation_config={ 'temperature': 0.7, # Slightly creative but still focused 'top_p': 0.9, # Diverse output but not too random 'top_k': 40, # Reasonable range of tokens to consider 'max_output_tokens': 2048 # Allow longer responses } ) # Configure Gemini vision model for image processing vision_model = genai.GenerativeModel('gemini-2.0-vision-flash') @app.route('/') @session_required def index(): """Render the chat interface.""" return render_template('index.html') @app.route('/api/chat', methods=['POST']) @session_required def chat(): """Process chat messages and get responses from Gemini API.""" try: data = request.json user_message = data.get('message', '') chat_history = data.get('history', []) image_data = data.get('image', None) if not user_message and not image_data: return jsonify({'error': 'Veuillez entrer un message ou joindre une image.'}), 400 # Log the incoming request (but not full chat history for privacy) session_id = session.get('session_id') logger.info(f"Received chat request from session {session_id}. Message length: {len(user_message)}") # Format conversation history for context formatted_history = [] for msg in chat_history[-15:]: # Use the last 15 messages for more context role = "user" if msg['sender'] == 'user' else "model" formatted_history.append({"role": role, "parts": [msg['text']]}) # Handle image processing if an image is included if image_data: try: # Process image with vision model image_base64 = image_data.split(',')[1] image = genai.types.Part.from_data( data=base64.b64decode(image_base64), mime_type="image/jpeg" ) # Save image with timestamp in the session directory session_id = session.get('session_id') session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id) os.makedirs(session_dir, exist_ok=True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = secure_filename(f"image_{timestamp}.jpg") filepath = os.path.join(session_dir, filename) with open(filepath, "wb") as f: f.write(base64.b64decode(image_base64)) # Create message parts with both text and image parts = [image] if user_message: parts.append(user_message) # Generate response using vision model response = vision_model.generate_content(parts) return jsonify({'response': response.text}) except Exception as img_error: logger.error(f"Error processing image: {str(img_error)}") return jsonify({ 'error': 'Désolé, une erreur est survenue lors du traitement de l\'image. Veuillez réessayer.' }), 500 else: # Text-only processing # Create a chat session with history chat = model.start_chat(history=formatted_history) # Generate response response = chat.send_message(user_message) # Log successful response logger.info(f"Generated response successfully. Response length: {len(response.text)}") # Return the response return jsonify({'response': response.text}) except genai.types.generation_types.BlockedPromptException as be: logger.warning(f"Content blocked: {str(be)}") return jsonify({ 'error': 'Votre message ou la conversation ne peut pas être traitée car elle contient du contenu potentiellement inapproprié.' }), 400 except Exception as e: logger.error(f"Error in chat endpoint: {str(e)}") return jsonify({ 'error': 'Désolé, j\'ai rencontré une erreur lors du traitement de votre demande. Veuillez réessayer.' }), 500 @app.route('/api/save-chat', methods=['POST']) @session_required def save_chat(): """Save the current chat history.""" try: session_id = session.get('session_id') # Create session-specific directory session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id) os.makedirs(session_dir, exist_ok=True) data = request.json chat_history = data.get('history', []) if not chat_history: return jsonify({'error': 'Aucune conversation à sauvegarder.'}), 400 # Generate filename with timestamp timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"chat_{timestamp}.json" filepath = os.path.join(session_dir, filename) # Save chat history to file with open(filepath, 'w', encoding='utf-8') as f: json.dump(chat_history, f, ensure_ascii=False, indent=2) return jsonify({'success': True, 'filename': filename}) except Exception as e: logger.error(f"Error saving chat: {str(e)}") return jsonify({ 'error': 'Désolé, une erreur est survenue lors de la sauvegarde de la conversation.' }), 500 @app.route('/api/load-chats', methods=['GET']) @session_required def load_chats(): """Get a list of saved chat files for current session.""" try: session_id = session.get('session_id') # Get session-specific directory session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id) # If the directory doesn't exist yet, return empty list if not os.path.exists(session_dir): return jsonify({'chats': []}) chat_files = [] for filename in os.listdir(session_dir): if filename.startswith('chat_') and filename.endswith('.json'): # Extract timestamp from filename timestamp = filename[5:-5] # Remove 'chat_' and '.json' # Add to list chat_files.append({ 'filename': filename, 'timestamp': timestamp }) # Sort by timestamp (newest first) chat_files.sort(key=lambda x: x['timestamp'], reverse=True) logger.info(f"Loaded {len(chat_files)} chats for session {session_id}") return jsonify({'chats': chat_files}) except Exception as e: logger.error(f"Error loading chat list: {str(e)}") return jsonify({ 'error': 'Désolé, une erreur est survenue lors du chargement des conversations.' }), 500 @app.route('/api/load-chat/', methods=['GET']) @session_required def load_chat(filename): """Load a specific chat history file.""" try: session_id = session.get('session_id') # Load from session-specific directory session_dir = os.path.join(app.config['UPLOAD_FOLDER'], session_id) filepath = os.path.join(session_dir, secure_filename(filename)) if not os.path.exists(filepath): return jsonify({'error': 'Conversation introuvable.'}), 404 with open(filepath, 'r', encoding='utf-8') as f: chat_history = json.load(f) logger.info(f"Loaded chat {filename} for session {session_id}") return jsonify({'history': chat_history}) except Exception as e: logger.error(f"Error loading chat file: {str(e)}") return jsonify({ 'error': 'Désolé, une erreur est survenue lors du chargement de la conversation.' }), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)