File size: 10,987 Bytes
793fced
 
 
 
8ba060e
9c00b1c
8ba060e
 
 
 
9c00b1c
eae4b47
793fced
 
 
8ba060e
793fced
 
8ba060e
 
 
793fced
8ba060e
 
793fced
8ba060e
 
9c00b1c
8ba060e
 
9c00b1c
8ba060e
 
 
 
 
 
 
 
 
9c00b1c
8ba060e
 
 
 
 
 
 
 
 
 
 
 
793fced
8ba060e
793fced
9c00b1c
 
 
793fced
 
 
8ba060e
 
 
 
 
 
 
 
 
 
 
 
793fced
 
8ba060e
 
793fced
 
 
8ba060e
793fced
 
 
 
8ba060e
 
 
 
 
 
 
 
793fced
 
 
8ba060e
9c00b1c
793fced
8ba060e
 
 
 
 
 
 
 
c8b7f88
8ba060e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
793fced
9c00b1c
8ba060e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
793fced
8ba060e
 
 
 
 
 
 
 
 
 
 
793fced
 
8ba060e
 
 
 
 
 
 
793fced
8ba060e
9c00b1c
8ba060e
 
 
 
793fced
 
9c00b1c
8ba060e
 
9c00b1c
793fced
8ba060e
793fced
 
8ba060e
793fced
 
8ba060e
793fced
8ba060e
793fced
8ba060e
9c00b1c
793fced
8ba060e
 
793fced
8ba060e
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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
import google.generativeai as genai
from dotenv import load_dotenv
from cachelib import SimpleCache

# Load .env
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 - trust one proxy layer (adjust numbers if you sit behind more proxies)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)

# Cookie / session policy (sane defaults for Hugging Face Spaces)
# By default we set SECURE cookies and SameSite=None which is required for cross-site in modern browsers.
# If you are running locally over HTTP, set DISABLE_SECURE_COOKIE=1 in your environment (for development only).
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["PERMANENT_SESSION_LIFETIME"] = timedelta(days=7)
app.config["SESSION_PERMANENT"] = True
app.config["SESSION_COOKIE_NAME"] = os.getenv("SESSION_COOKIE_NAME", "hf_app_session")

# Simple in-memory cache (for details from the Gemini API)
cache = SimpleCache(threshold=200, default_timeout=60 * 60 * 24 * 7)  # 7 days default timeout

# Configure Gemini (Gemini API key must be set in environment as GEMINI_API_KEY)
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
if GEMINI_API_KEY:
    try:
        genai.configure(api_key=GEMINI_API_KEY)
    except Exception as e:
        logger.exception("Failed to configure Gemini client: %s", e)
else:
    logger.warning("GEMINI_API_KEY not provided. API calls will not work until you set this env var.")

# Supported languages and list of pests/diseases (kept from your original list)
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"}
]

# Utility: robust JSON extraction from potentially noisy model output
def extract_json_from_response(content: str):
    try:
        return json.loads(content)
    except json.JSONDecodeError:
        # Attempt to extract codeblock containing JSON
        json_match = re.search(r'```json(.*?)```', content, re.DOTALL)
        if json_match:
            json_str = json_match.group(1).strip()
        else:
            # Fallback: try to extract first {...} block
            brace_match = re.search(r'(\{(?:.|\n)*\})', content)
            json_str = brace_match.group(1) if brace_match else content

        # Remove stray markdown markers and weird trailing commas
        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\nOriginal content: %s", e, content[:1000])
            raise ValueError("Failed to parse JSON response from API")

# Root page
@app.route("/")
def index():
    # Determine language: prefer session, fallback to cookie, then default 'en'.
    language = session.get("language") or request.cookies.get("language") or "en"
    # Keep session in sync with cookie if needed
    session["language"] = language
    session.permanent = True
    return render_template("index.html",
                           pests_diseases=PESTS_DISEASES,
                           languages=LANGUAGES,
                           current_language=language)

# Language setter (Post-Redirect-Get)
@app.route("/set_language", methods=["POST"])
def set_language():
    language = request.form.get("language", "en")
    if language not in LANGUAGES:
        logger.warning("Attempt to set unsupported language: %s", language)
        language = "en"

    # Save in server-side session
    session["language"] = language
    session.permanent = True
    logger.info("Language successfully changed to %s in session.", language)

    # Also set a client cookie as reliable fallback for stateless platforms
    resp = make_response(redirect(url_for("index")))
    # cookie lifetime 7 days
    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)
    return resp

# Endpoint to fetch details for a pest/disease (uses cache + Gemini)
@app.route("/get_details/<int:pest_id>")
def get_details(pest_id: int):
    # Resolve language (session first, cookie fallback)
    language = session.get("language") or request.cookies.get("language") or "en"

    pest = next((p for p in PESTS_DISEASES if p["id"] == pest_id), None)
    if pest is None:
        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 %s (%s)", pest_id, language)
        return jsonify(cached)

    if not GEMINI_API_KEY:
        logger.warning("Gemini API key missing; returning placeholder result.")
        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 with language-specific 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['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)

        # Ensure keys exist
        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("API response missing or malformed for key: %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 details from Gemini API: %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)