Pestipedia / app.py
RushiMane2003's picture
Update app.py
2d19799 verified
import os
import json
import re
import logging
from datetime import datetime, timedelta
from flask import (
Flask, render_template, request, jsonify, session,
make_response, redirect, url_for
)
from werkzeug.middleware.proxy_fix import ProxyFix
from flask_session import Session
import google.generativeai as genai
from dotenv import load_dotenv
from cachelib import SimpleCache
# Load environment
load_dotenv()
# Logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("app")
# Flask app
app = Flask(__name__, static_folder="static", template_folder="templates")
# Secret key
app.secret_key = os.getenv("FLASK_SECRET_KEY", os.urandom(24))
# ProxyFix for deployments behind proxies (Hugging Face)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)
# Session configuration (filesystem-based) -- persists server-side during container life
# Flask-Session will write files under /tmp by default
app.config["SESSION_TYPE"] = "filesystem"
app.config["SESSION_FILE_DIR"] = os.getenv("SESSION_FILE_DIR", "/tmp/flask_session")
app.config["SESSION_PERMANENT"] = True
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=7)
# Cookie settings - browsers require SameSite=None + Secure for cross-site cookies
disable_secure = os.getenv("DISABLE_SECURE_COOKIE", "0") in ("1", "true", "True")
app.config["SESSION_COOKIE_SAMESITE"] = "None"
app.config["SESSION_COOKIE_SECURE"] = False if disable_secure else True
app.config["SESSION_COOKIE_NAME"] = os.getenv("SESSION_COOKIE_NAME", "hf_app_session")
# Initialize server-side sessions
Session(app)
# Simple in-memory cache (for API results)
cache = SimpleCache(threshold=200, default_timeout=60 * 60 * 24 * 7) # 7 days
# Gemini API
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
if GEMINI_API_KEY:
try:
genai.configure(api_key=GEMINI_API_KEY)
logger.info("Gemini client configured.")
except Exception as e:
logger.exception("Failed to configure Gemini client: %s", e)
else:
logger.warning("GEMINI_API_KEY not set. API calls will return placeholders.")
# Supported languages and pest list (original)
LANGUAGES = {
"en": "English", "hi": "हिंदी (Hindi)", "bn": "বাংলা (Bengali)", "te": "తెలుగు (Telugu)",
"mr": "मराठी (Marathi)", "ta": "தமிழ் (Tamil)", "gu": "ગુજરાતી (Gujarati)", "ur": "اردو (Urdu)",
"kn": "ಕನ್ನಡ (Kannada)", "or": "ଓଡ଼ିଆ (Odia)", "ml": "മലയാളം (Malayalam)"
}
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"}
]
# JSON extraction helper (resilient to noisy model output)
def extract_json_from_response(content: str):
try:
return json.loads(content)
except json.JSONDecodeError:
json_match = re.search(r'```json(.*?)```', content, re.DOTALL)
if json_match:
json_str = json_match.group(1).strip()
else:
brace_match = re.search(r'(\{(?:.|\n)*\})', content)
json_str = brace_match.group(1) if brace_match else content
json_str = json_str.replace('```json', '').replace('```', '').strip()
json_str = re.sub(r',(\s*[\]\}])', r'\1', json_str)
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
logger.error("JSON parsing error after cleanup: %s\nSnippet: %s", e, content[:1000])
raise ValueError("Failed to parse JSON response from API")
@app.route("/")
def index():
# Use session first, cookie fallback. This sets HTML lang attribute properly.
language = session.get("language") or request.cookies.get("language") or "en"
session["language"] = language
session.permanent = True
return render_template("index.html", pests_diseases=PESTS_DISEASES, languages=LANGUAGES, current_language=language)
# Keep this for compatibility; not required if client only sets cookie & passes lang in fetch
@app.route("/set_language", methods=["POST"])
def set_language():
language = request.form.get("language", "en")
if language not in LANGUAGES:
language = "en"
session["language"] = language
session.permanent = True
logger.info("Language set to %s in session", language)
resp = make_response(redirect(url_for("index")))
max_age = 60 * 60 * 24 * 7
secure_flag = False if disable_secure else True
resp.set_cookie("language", language, max_age=max_age, secure=secure_flag, samesite="None", httponly=False, path="/")
return resp
@app.route("/get_details/<int:pest_id>")
def get_details(pest_id):
# Priority: lang query param > session > cookie > default
lang_param = request.args.get("lang")
language = lang_param or session.get("language") or request.cookies.get("language") or "en"
session["language"] = language
session.permanent = True
pest = next((p for p in PESTS_DISEASES if p["id"] == pest_id), None)
if not pest:
return jsonify({"error": "Not found"}), 404
cache_key = f"pest_{pest_id}_{language}"
cached = cache.get(cache_key)
if cached:
logger.info("Cache hit for pest %s (%s)", pest_id, language)
return jsonify(cached)
if not GEMINI_API_KEY:
logger.warning("GEMINI_API_KEY missing; returning placeholder details")
result = {
**pest,
"details": {
"description": {"title": "Description", "text": f"Placeholder description for {pest['name']} ({language})."},
"lifecycle": {"title": "Lifecycle", "text": "N/A"},
"symptoms": {"title": "Symptoms", "text": "N/A"},
"impact": {"title": "Economic Impact", "text": "N/A"},
"management": {"title": "Management", "text": "N/A"},
"prevention": {"title": "Prevention", "text": "N/A"},
},
"language": language,
"cached_at": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
}
cache.set(cache_key, result)
return jsonify(result)
# Build prompt for Gemini
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['type']} '{pest['name']}' affecting '{pest['crop']}' in India.
Include these sections: Description and identification, Lifecycle and spread, Damage symptoms, Economic impact, Management and control (organic and chemical), Preventive measures.
Format the response as a single, strictly valid JSON object with keys: "description", "lifecycle", "symptoms", "impact", "management", "prevention".
Each key must contain an object with "title" and "text" fields.
"""
try:
model = genai.GenerativeModel("gemini-1.5-flash")
response = model.generate_content(prompt)
text = getattr(response, "text", "") or str(response)
detailed_info = extract_json_from_response(text)
required_keys = ["description", "lifecycle", "symptoms", "impact", "management", "prevention"]
for key in required_keys:
if key not in detailed_info or "text" not in detailed_info.get(key, {}):
detailed_info[key] = {"title": key.capitalize(), "text": "Information could not be generated for this section."}
logger.warning("Missing section in API response: %s", key)
result = {
**pest,
"details": detailed_info,
"language": language,
"cached_at": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
}
cache.set(cache_key, result)
logger.info("Cached details for pest %s (%s)", pest_id, language)
return jsonify(result)
except Exception as e:
logger.exception("Error fetching from Gemini: %s", e)
return jsonify({"error": "Failed to fetch information", "message": str(e)}), 500
if __name__ == "__main__":
port = int(os.environ.get("PORT", 7860))
host = os.environ.get("HOST", "0.0.0.0")
app.run(host=host, port=port)