# python app.py --port 9090 from flask import Flask, render_template, request, jsonify, session import os import sys import argparse from github_companion import GitHubCompanion from dotenv import load_dotenv import uuid # Using Python's built-in uuid module import logging import time # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Load environment variables load_dotenv() app = Flask(__name__) # More explicit session configuration # We set SESSION_COOKIE_SECURE=False because Hugging Face Spaces handles HTTPS termination externally. # If you were running HTTPS directly in Flask, you'd set this to True. app.config.update( SECRET_KEY=os.environ.get("SECRET_KEY", os.urandom(24).hex()), # Ensure SECRET_KEY is loaded SESSION_COOKIE_HTTPONLY=True, # Prevent client-side JS access to the cookie SESSION_COOKIE_SAMESITE='Lax', # Recommended setting for CSRF protection SESSION_COOKIE_SECURE=False, # Set to False as HTTPS is handled externally by HF ) app.secret_key = app.config['SECRET_KEY'] # Make sure app.secret_key is set from config # Store active sessions sessions = {} # Rate limiting request_timestamps = {} RATE_LIMIT_WINDOW = 60 # seconds MAX_REQUESTS_PER_WINDOW = 5 @app.route("/") def index(): """Render the main page""" # Generate a unique session ID if one doesn't exist if "session_id" not in session: session["session_id"] = str(uuid.uuid4()) logger.info(f"New session created: {session['session_id']}") # Add logging return render_template("index.html") def is_rate_limited(session_id): """Check if the session is rate limited""" current_time = time.time() # Initialize timestamps for this session if not exists if session_id not in request_timestamps: request_timestamps[session_id] = [] # Remove timestamps outside the window request_timestamps[session_id] = [ ts for ts in request_timestamps[session_id] if ts > current_time - RATE_LIMIT_WINDOW ] # Check if too many requests in the window if len(request_timestamps[session_id]) >= MAX_REQUESTS_PER_WINDOW: return True # Add current timestamp request_timestamps[session_id].append(current_time) return False @app.route("/chat", methods=["POST"]) def chat(): """Handle chat requests""" data = request.json message = data.get("message", "") # Add logging to see the session state logger.info(f"Chat request received. Current session keys: {list(session.keys())}") session_id = session.get("session_id") if not session_id: logger.error("No valid session ID found in session object.") # Add error logging return jsonify({"error": "No valid session"}), 400 logger.info(f"Valid session ID found: {session_id}") # Add success logging # Check rate limiting if is_rate_limited(session_id): return jsonify({ "response": "You're sending requests too quickly. Please wait a moment before trying again.", "token_count": 0, "rate_limited": True }) try: # Initialize companion if not exists for this session if session_id not in sessions: requesty_api_key = os.environ.get("REQUESTY_API_KEY") logger.info(f"Using API key: {requesty_api_key[:5]}...{requesty_api_key[-5:] if requesty_api_key else ''}") if not requesty_api_key: return jsonify({"error": "Requesty API key not configured. Please check your .env file."}), 500 sessions[session_id] = GitHubCompanion(requesty_api_key=requesty_api_key) companion = sessions[session_id] response = companion.chat(message) # Check if the response contains an error message about rate limiting rate_limited = "rate limit" in response.lower() or "quota" in response.lower() return jsonify({ "response": response, "token_count": companion.token_count if companion.repo_info else 0, "rate_limited": rate_limited }) except Exception as e: logger.error(f"Error in chat: {str(e)}") return jsonify({"error": f"An error occurred: {str(e)}"}), 500 @app.route("/reset", methods=["POST"]) def reset(): """Reset the conversation""" session_id = session.get("session_id") if session_id and session_id in sessions: try: # Save conversation before resetting sessions[session_id].save_conversation(f"conversation_{session_id}.json") # Remove the session del sessions[session_id] except Exception as e: logger.error(f"Error in reset: {str(e)}") # Create new session ID session["session_id"] = str(uuid.uuid4()) return jsonify({"status": "success", "message": "Conversation reset"}) @app.route("/health", methods=["GET"]) def health(): """Health check endpoint""" return jsonify({"status": "ok"}) if __name__ == "__main__": # Parse command line arguments parser = argparse.ArgumentParser(description="GitHub Navigator Web App") parser.add_argument("--port", type=int, help="Port to run the server on") args = parser.parse_args() # Ensure the templates directory exists os.makedirs("templates", exist_ok=True) # Priority: 1. Command line argument, 2. Environment variable, 3. Default (8080) port = args.port if args.port else int(os.environ.get("PORT", 8080)) logger.info(f"Starting GitHub Navigator on port {port}") # Run the app app.run(host="0.0.0.0", port=port, debug=True)