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) |