Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| import re | |
| import time | |
| import logging | |
| from datetime import datetime | |
| from flask import Flask, render_template, request, jsonify, session, make_response | |
| from google import genai | |
| from dotenv import load_dotenv | |
| from cachelib import SimpleCache | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Load environment variables | |
| load_dotenv() | |
| # Configure the Gemini API - get key from env or Hugging Face Spaces secrets | |
| api_key = os.getenv("GEMINI_API_KEY") | |
| if not api_key: | |
| logger.warning("GEMINI_API_KEY not found in environment variables. Make sure to set it in Hugging Face Spaces secrets.") | |
| client = genai.Client(api_key=api_key) | |
| # Initialize cache (7 days timeout) | |
| cache = SimpleCache(threshold=50, default_timeout=60*60*24*7) | |
| app = Flask(__name__) | |
| # Use secret key from environment or a default for development | |
| app.secret_key = os.getenv("SECRET_KEY", "dev_secret_key") | |
| # Set session cookie settings to be more persistent | |
| app.config['SESSION_COOKIE_SECURE'] = False # Set to True in HTTPS environments | |
| app.config['SESSION_COOKIE_HTTPONLY'] = True | |
| app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' | |
| app.config['PERMANENT_SESSION_LIFETIME'] = 60*60*24*7 # 7 days | |
| # Supported languages | |
| LANGUAGES = { | |
| "en": "English", | |
| "hi": "हिंदी (Hindi)", | |
| "bn": "বাংলা (Bengali)", | |
| "te": "తెలుగు (Telugu)", | |
| "mr": "मराठी (Marathi)", | |
| "ta": "தமிழ் (Tamil)", | |
| "gu": "ગુજરાતી (Gujarati)", | |
| "ur": "اردو (Urdu)", | |
| "kn": "ಕನ್ನಡ (Kannada)", | |
| "or": "ଓଡ଼ିଆ (Odia)", | |
| "ml": "മലയാളം (Malayalam)" | |
| } | |
| # List of pests and diseases | |
| PESTS_DISEASES = [ | |
| { | |
| "id": 1, | |
| "name": "Fall Armyworm", | |
| "type": "pest", | |
| "crop": "Maize, Sorghum", | |
| "image_url": "static/images/fall_armyworm.jpg" | |
| }, | |
| { | |
| "id": 2, | |
| "name": "Rice Blast", | |
| "type": "disease", | |
| "crop": "Rice", | |
| "image_url": "static/images/rice_blast.jpg" | |
| }, | |
| { | |
| "id": 3, | |
| "name": "Aphids", | |
| "type": "pest", | |
| "crop": "Various crops", | |
| "image_url": "static/images/aphids.jpg" | |
| }, | |
| { | |
| "id": 4, | |
| "name": "Powdery Mildew", | |
| "type": "disease", | |
| "crop": "Wheat, Vegetables", | |
| "image_url": "static/images/powdery_mildew.jpg" | |
| }, | |
| { | |
| "id": 5, | |
| "name": "Stem Borer", | |
| "type": "pest", | |
| "crop": "Rice, Sugarcane", | |
| "image_url": "static/images/stem_borer.jpg" | |
| }, | |
| { | |
| "id": 6, | |
| "name": "Yellow Mosaic Virus", | |
| "type": "disease", | |
| "crop": "Pulses", | |
| "image_url": "static/images/yellow_mosaic.jpg" | |
| }, | |
| { | |
| "id": 7, | |
| "name": "Brown Planthopper", | |
| "type": "pest", | |
| "crop": "Rice", | |
| "image_url": "static/images/brown_planthopper.jpg" | |
| }, | |
| { | |
| "id": 8, | |
| "name": "Bacterial Leaf Blight", | |
| "type": "disease", | |
| "crop": "Rice", | |
| "image_url": "static/images/bacterial_leaf_blight.jpg" | |
| }, | |
| { | |
| "id": 9, | |
| "name": "Jassids", | |
| "type": "pest", | |
| "crop": "Cotton, Maize", | |
| "image_url": "static/images/jassids.jpg" | |
| }, | |
| { | |
| "id": 10, | |
| "name": "Downy Mildew", | |
| "type": "disease", | |
| "crop": "Grapes, Onion", | |
| "image_url": "static/images/downy_mildew.jpg" | |
| }, | |
| { | |
| "id": 11, | |
| "name": "Whitefly", | |
| "type": "pest", | |
| "crop": "Tomato, Cotton", | |
| "image_url": "static/images/whitefly.jpg" | |
| }, | |
| { | |
| "id": 12, | |
| "name": "Fusarium Wilt", | |
| "type": "disease", | |
| "crop": "Banana, Tomato", | |
| "image_url": "static/images/fusarium_wilt.jpg" | |
| } | |
| ] | |
| def index(): | |
| # Set default language if not set | |
| if 'language' not in session: | |
| session['language'] = 'en' | |
| # Make session persistent | |
| session.permanent = True | |
| return render_template('index.html', | |
| pests_diseases=PESTS_DISEASES, | |
| languages=LANGUAGES, | |
| current_language=session['language']) | |
| def set_language(): | |
| language = request.form.get('language') | |
| if language in LANGUAGES: | |
| session.permanent = True | |
| session['language'] = language | |
| # Log the language change | |
| logger.info(f"Language changed to {language}") | |
| # Create a response with success message | |
| response = make_response(jsonify({"success": True})) | |
| # Set a cookie to ensure the language persists even if session fails | |
| response.set_cookie('user_language', language, max_age=60*60*24*30) # 30 days | |
| return response | |
| return jsonify({"success": False}), 400 | |
| def extract_json_from_response(content): | |
| """Extract and parse JSON from API response.""" | |
| try: | |
| # Try direct JSON parsing first | |
| return json.loads(content) | |
| except json.JSONDecodeError: | |
| # If direct parsing fails, try to extract JSON from markdown code blocks | |
| json_match = re.search(r'```json(.*?)```', content, re.DOTALL) | |
| if json_match: | |
| json_str = json_match.group(1).strip() | |
| else: | |
| json_str = content | |
| # Clean up any potential markdown or text | |
| json_str = json_str.replace('```json', '').replace('```', '') | |
| try: | |
| return json.loads(json_str) | |
| except json.JSONDecodeError as e: | |
| logger.error(f"JSON parsing error: {str(e)}") | |
| raise ValueError(f"Failed to parse response") | |
| def get_details(pest_id): | |
| # Get current language from session or cookie fallback | |
| language = session.get('language', request.cookies.get('user_language', 'en')) | |
| # Find the pest/disease by ID | |
| pest_disease = next((item for item in PESTS_DISEASES if item["id"] == pest_id), None) | |
| if not pest_disease: | |
| return jsonify({"error": "Not found"}), 404 | |
| # Check cache first - cache key includes language | |
| cache_key = f"pest_disease_{pest_id}_{language}" | |
| cached_result = cache.get(cache_key) | |
| if cached_result: | |
| logger.info(f"Cache hit for pest_id {pest_id} in {language}") | |
| return jsonify(cached_result) | |
| logger.info(f"Cache miss for pest_id {pest_id} in {language}, fetching from API") | |
| # If no API key is set, return a fake response for testing | |
| if not api_key: | |
| logger.warning("No API key set, returning placeholder content") | |
| return jsonify({ | |
| **pest_disease, | |
| "details": { | |
| "description": {"title": "Description", "text": "API key not configured. Please set GEMINI_API_KEY in Hugging Face Spaces secrets."}, | |
| "lifecycle": {"title": "Lifecycle", "text": "Information not available without API key."}, | |
| "symptoms": {"title": "Symptoms", "text": "Information not available without API key."}, | |
| "impact": {"title": "Impact", "text": "Information not available without API key."}, | |
| "management": {"title": "Management", "text": "Information not available without API key."}, | |
| "prevention": {"title": "Prevention", "text": "Information not available without API key."} | |
| }, | |
| "language": language, | |
| "cached_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| }) | |
| # Generate prompt with language instruction | |
| lang_instructions = { | |
| "en": "Respond in English", | |
| "hi": "हिंदी में जवाब दें (Respond in Hindi)", | |
| "bn": "বাংলায় উত্তর দিন (Respond in Bengali)", | |
| "te": "తెలుగులో సమాధానం ఇవ్వండి (Respond in Telugu)", | |
| "mr": "मराठीत उत्तर द्या (Respond in Marathi)", | |
| "ta": "தமிழில் பதிலளிக்கவும் (Respond in Tamil)", | |
| "gu": "ગુજરાતીમાં જવાબ આપો (Respond in Gujarati)", | |
| "ur": "اردو میں جواب دیں (Respond in Urdu)", | |
| "kn": "ಕನ್ನಡದಲ್ಲಿ ಉತ್ತರಿಸಿ (Respond in Kannada)", | |
| "or": "ଓଡ଼ିଆରେ ଉତ୍ତର ଦିଅନ୍ତୁ (Respond in Odia)", | |
| "ml": "മലയാളത്തിൽ മറുപടി നൽകുക (Respond in Malayalam)" | |
| } | |
| prompt = f""" | |
| {lang_instructions.get(language, "Respond in English")} | |
| Provide detailed information about the agricultural {pest_disease['type']} known as {pest_disease['name']} | |
| in the context of Indian agriculture, especially affecting {pest_disease['crop']}. | |
| Include the following sections: | |
| 1. Description and identification | |
| 2. Lifecycle and spread | |
| 3. Damage symptoms | |
| 4. Economic impact | |
| 5. Management and control measures (both organic and chemical) | |
| 6. Preventive measures | |
| Format the response as JSON with these keys: "description", "lifecycle", "symptoms", | |
| "impact", "management", "prevention". | |
| Each key should contain an object with "title" and "text" fields. | |
| For example: | |
| {{ | |
| "description": {{"title": "Description", "text": "Detailed description..."}}, | |
| "lifecycle": {{"title": "Lifecycle", "text": "Information about lifecycle..."}}, | |
| ... | |
| }} | |
| Ensure the response is strictly valid JSON. | |
| """ | |
| try: | |
| # Call AI API with retry logic | |
| model = 'gemini-1.5-flash' | |
| max_retries = 3 | |
| retry_delay = 2 | |
| for attempt in range(max_retries): | |
| try: | |
| response = client.models.generate_content(model=model, contents=prompt) | |
| break | |
| except Exception as e: | |
| if attempt < max_retries - 1: | |
| logger.warning(f"API call attempt {attempt+1} failed. Retrying...") | |
| time.sleep(retry_delay) | |
| retry_delay *= 2 | |
| else: | |
| logger.error(f"All API call attempts failed for pest_id {pest_id}") | |
| raise e | |
| # Parse the response | |
| detailed_info = extract_json_from_response(response.text) | |
| # Validate the response structure | |
| required_keys = ["description", "lifecycle", "symptoms", "impact", "management", "prevention"] | |
| for key in required_keys: | |
| if key not in detailed_info: | |
| detailed_info[key] = {"title": f"{key.capitalize()}", "text": "Information not available."} | |
| elif not isinstance(detailed_info[key], dict): | |
| detailed_info[key] = {"title": f"{key.capitalize()}", "text": str(detailed_info[key])} | |
| elif "text" not in detailed_info[key]: | |
| detailed_info[key]["text"] = "Information not available." | |
| # Add timestamp and language info | |
| result = { | |
| **pest_disease, | |
| "details": detailed_info, | |
| "language": language, | |
| "cached_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| } | |
| # Cache the result | |
| cache.set(cache_key, result) | |
| logger.info(f"Successfully cached data for pest_id {pest_id} in {language}") | |
| return jsonify(result) | |
| except Exception as e: | |
| logger.error(f"Error: {str(e)}") | |
| return jsonify({ | |
| "error": "Failed to fetch information", | |
| "message": "Please try again later." | |
| }), 500 | |
| def get_pests_list(): | |
| return jsonify(PESTS_DISEASES) | |
| def clear_specific_cache(pest_id): | |
| for lang in LANGUAGES.keys(): | |
| cache_key = f"pest_disease_{pest_id}_{lang}" | |
| cache.delete(cache_key) | |
| return jsonify({"success": True, "message": f"Cache cleared for pest ID {pest_id}"}) | |
| # Add a diagnostics endpoint to help debug session issues | |
| def debug_session(): | |
| # Only enable in development | |
| if os.getenv("FLASK_ENV") == "production": | |
| return jsonify({"error": "Not available in production"}), 403 | |
| return jsonify({ | |
| "session_data": dict(session), | |
| "cookies": dict(request.cookies), | |
| "session_cookie_name": app.session_cookie_name, | |
| "session_cookie_secure": app.config.get('SESSION_COOKIE_SECURE'), | |
| "session_cookie_httponly": app.config.get('SESSION_COOKIE_HTTPONLY'), | |
| "session_cookie_samesite": app.config.get('SESSION_COOKIE_SAMESITE'), | |
| "permanent_session_lifetime": str(app.config.get('PERMANENT_SESSION_LIFETIME')) | |
| }) | |
| if __name__ == '__main__': | |
| # Use PORT environment variable if available (for Hugging Face Spaces) | |
| port = int(os.environ.get("PORT", 7860)) | |
| app.run(host="0.0.0.0", port=port) |