RushiMane2003 commited on
Commit
2d19799
·
verified ·
1 Parent(s): 8095b84

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +39 -47
app.py CHANGED
@@ -9,11 +9,12 @@ from flask import (
9
  make_response, redirect, url_for
10
  )
11
  from werkzeug.middleware.proxy_fix import ProxyFix
 
12
  import google.generativeai as genai
13
  from dotenv import load_dotenv
14
  from cachelib import SimpleCache
15
 
16
- # Load .env
17
  load_dotenv()
18
 
19
  # Logging
@@ -26,33 +27,40 @@ app = Flask(__name__, static_folder="static", template_folder="templates")
26
  # Secret key
27
  app.secret_key = os.getenv("FLASK_SECRET_KEY", os.urandom(24))
28
 
29
- # ProxyFix - trust one proxy layer (adjust numbers if you sit behind more proxies)
30
  app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)
31
 
32
- # Cookie / session policy (sane defaults for Hugging Face Spaces)
33
- # By default we set SECURE cookies and SameSite=None which is required for cross-site in modern browsers.
34
- # If you are running locally over HTTP, set DISABLE_SECURE_COOKIE=1 in your environment (for development only).
 
 
 
 
 
35
  disable_secure = os.getenv("DISABLE_SECURE_COOKIE", "0") in ("1", "true", "True")
36
  app.config["SESSION_COOKIE_SAMESITE"] = "None"
37
  app.config["SESSION_COOKIE_SECURE"] = False if disable_secure else True
38
- app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=7)
39
- app.config["SESSION_PERMANENT"] = True
40
  app.config["SESSION_COOKIE_NAME"] = os.getenv("SESSION_COOKIE_NAME", "hf_app_session")
41
 
42
- # Simple in-memory cache (for details from the Gemini API)
43
- cache = SimpleCache(threshold=200, default_timeout=60 * 60 * 24 * 7) # 7 days default timeout
 
 
 
44
 
45
- # Configure Gemini (Gemini API key must be set in environment as GEMINI_API_KEY)
46
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
47
  if GEMINI_API_KEY:
48
  try:
49
  genai.configure(api_key=GEMINI_API_KEY)
 
50
  except Exception as e:
51
  logger.exception("Failed to configure Gemini client: %s", e)
52
  else:
53
- logger.warning("GEMINI_API_KEY not provided. API calls will not work until you set this env var.")
54
 
55
- # Supported languages and list of pests/diseases (kept from your original list)
56
  LANGUAGES = {
57
  "en": "English", "hi": "हिंदी (Hindi)", "bn": "বাংলা (Bengali)", "te": "తెలుగు (Telugu)",
58
  "mr": "मराठी (Marathi)", "ta": "தமிழ் (Tamil)", "gu": "ગુજરાતી (Gujarati)", "ur": "اردو (Urdu)",
@@ -74,82 +82,69 @@ PESTS_DISEASES = [
74
  {"id": 12, "name": "Fusarium Wilt", "type": "disease", "crop": "Banana, Tomato", "image_url": "/static/images/fusarium_wilt.jpg"}
75
  ]
76
 
77
- # Utility: robust JSON extraction from potentially noisy model output
78
  def extract_json_from_response(content: str):
79
  try:
80
  return json.loads(content)
81
  except json.JSONDecodeError:
82
- # Attempt to extract codeblock containing JSON
83
  json_match = re.search(r'```json(.*?)```', content, re.DOTALL)
84
  if json_match:
85
  json_str = json_match.group(1).strip()
86
  else:
87
- # Fallback: try to extract first {...} block
88
  brace_match = re.search(r'(\{(?:.|\n)*\})', content)
89
  json_str = brace_match.group(1) if brace_match else content
90
-
91
- # Remove stray markdown markers and weird trailing commas
92
  json_str = json_str.replace('```json', '').replace('```', '').strip()
93
  json_str = re.sub(r',(\s*[\]\}])', r'\1', json_str)
94
-
95
  try:
96
  return json.loads(json_str)
97
  except json.JSONDecodeError as e:
98
- logger.error("JSON parsing error after cleanup: %s\nOriginal content: %s", e, content[:1000])
99
  raise ValueError("Failed to parse JSON response from API")
100
 
101
- # Root page
102
  @app.route("/")
103
  def index():
104
- # Determine language: prefer session, fallback to cookie, then default 'en'.
105
  language = session.get("language") or request.cookies.get("language") or "en"
106
- # Keep session in sync with cookie if needed
107
  session["language"] = language
108
  session.permanent = True
109
- return render_template("pests.html",
110
- pests_diseases=PESTS_DISEASES,
111
- languages=LANGUAGES,
112
- current_language=language)
113
 
114
- # Language setter (Post-Redirect-Get)
115
  @app.route("/set_language", methods=["POST"])
116
  def set_language():
117
  language = request.form.get("language", "en")
118
  if language not in LANGUAGES:
119
- logger.warning("Attempt to set unsupported language: %s", language)
120
  language = "en"
121
-
122
- # Save in server-side session
123
  session["language"] = language
124
  session.permanent = True
125
- logger.info("Language successfully changed to %s in session.", language)
126
 
127
- # Also set a client cookie as reliable fallback for stateless platforms
128
  resp = make_response(redirect(url_for("index")))
129
- # cookie lifetime 7 days
130
  max_age = 60 * 60 * 24 * 7
131
  secure_flag = False if disable_secure else True
132
- resp.set_cookie("language", language, max_age=max_age, secure=secure_flag, samesite="None", httponly=False)
133
  return resp
134
 
135
- # Endpoint to fetch details for a pest/disease (uses cache + Gemini)
136
  @app.route("/get_details/<int:pest_id>")
137
- def get_details(pest_id: int):
138
- # Resolve language (session first, cookie fallback)
139
- language = session.get("language") or request.cookies.get("language") or "en"
 
 
 
140
 
141
  pest = next((p for p in PESTS_DISEASES if p["id"] == pest_id), None)
142
- if pest is None:
143
  return jsonify({"error": "Not found"}), 404
144
 
145
  cache_key = f"pest_{pest_id}_{language}"
146
  cached = cache.get(cache_key)
147
  if cached:
148
- logger.info("Cache hit for %s (%s)", pest_id, language)
149
  return jsonify(cached)
150
 
151
  if not GEMINI_API_KEY:
152
- logger.warning("Gemini API key missing; returning placeholder result.")
153
  result = {
154
  **pest,
155
  "details": {
@@ -166,7 +161,7 @@ def get_details(pest_id: int):
166
  cache.set(cache_key, result)
167
  return jsonify(result)
168
 
169
- # Build prompt with language-specific instruction
170
  lang_instructions = {
171
  "en": "Respond in English",
172
  "hi": "हिंदी में जवाब दें (Respond in Hindi)",
@@ -194,12 +189,11 @@ Each key must contain an object with "title" and "text" fields.
194
  text = getattr(response, "text", "") or str(response)
195
  detailed_info = extract_json_from_response(text)
196
 
197
- # Ensure keys exist
198
  required_keys = ["description", "lifecycle", "symptoms", "impact", "management", "prevention"]
199
  for key in required_keys:
200
  if key not in detailed_info or "text" not in detailed_info.get(key, {}):
201
  detailed_info[key] = {"title": key.capitalize(), "text": "Information could not be generated for this section."}
202
- logger.warning("API response missing or malformed for key: %s", key)
203
 
204
  result = {
205
  **pest,
@@ -210,12 +204,10 @@ Each key must contain an object with "title" and "text" fields.
210
  cache.set(cache_key, result)
211
  logger.info("Cached details for pest %s (%s)", pest_id, language)
212
  return jsonify(result)
213
-
214
  except Exception as e:
215
- logger.exception("Error fetching details from Gemini API: %s", e)
216
  return jsonify({"error": "Failed to fetch information", "message": str(e)}), 500
217
 
218
-
219
  if __name__ == "__main__":
220
  port = int(os.environ.get("PORT", 7860))
221
  host = os.environ.get("HOST", "0.0.0.0")
 
9
  make_response, redirect, url_for
10
  )
11
  from werkzeug.middleware.proxy_fix import ProxyFix
12
+ from flask_session import Session
13
  import google.generativeai as genai
14
  from dotenv import load_dotenv
15
  from cachelib import SimpleCache
16
 
17
+ # Load environment
18
  load_dotenv()
19
 
20
  # Logging
 
27
  # Secret key
28
  app.secret_key = os.getenv("FLASK_SECRET_KEY", os.urandom(24))
29
 
30
+ # ProxyFix for deployments behind proxies (Hugging Face)
31
  app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1)
32
 
33
+ # Session configuration (filesystem-based) -- persists server-side during container life
34
+ # Flask-Session will write files under /tmp by default
35
+ app.config["SESSION_TYPE"] = "filesystem"
36
+ app.config["SESSION_FILE_DIR"] = os.getenv("SESSION_FILE_DIR", "/tmp/flask_session")
37
+ app.config["SESSION_PERMANENT"] = True
38
+ app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=7)
39
+
40
+ # Cookie settings - browsers require SameSite=None + Secure for cross-site cookies
41
  disable_secure = os.getenv("DISABLE_SECURE_COOKIE", "0") in ("1", "true", "True")
42
  app.config["SESSION_COOKIE_SAMESITE"] = "None"
43
  app.config["SESSION_COOKIE_SECURE"] = False if disable_secure else True
 
 
44
  app.config["SESSION_COOKIE_NAME"] = os.getenv("SESSION_COOKIE_NAME", "hf_app_session")
45
 
46
+ # Initialize server-side sessions
47
+ Session(app)
48
+
49
+ # Simple in-memory cache (for API results)
50
+ cache = SimpleCache(threshold=200, default_timeout=60 * 60 * 24 * 7) # 7 days
51
 
52
+ # Gemini API
53
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
54
  if GEMINI_API_KEY:
55
  try:
56
  genai.configure(api_key=GEMINI_API_KEY)
57
+ logger.info("Gemini client configured.")
58
  except Exception as e:
59
  logger.exception("Failed to configure Gemini client: %s", e)
60
  else:
61
+ logger.warning("GEMINI_API_KEY not set. API calls will return placeholders.")
62
 
63
+ # Supported languages and pest list (original)
64
  LANGUAGES = {
65
  "en": "English", "hi": "हिंदी (Hindi)", "bn": "বাংলা (Bengali)", "te": "తెలుగు (Telugu)",
66
  "mr": "मराठी (Marathi)", "ta": "தமிழ் (Tamil)", "gu": "ગુજરાતી (Gujarati)", "ur": "اردو (Urdu)",
 
82
  {"id": 12, "name": "Fusarium Wilt", "type": "disease", "crop": "Banana, Tomato", "image_url": "/static/images/fusarium_wilt.jpg"}
83
  ]
84
 
85
+ # JSON extraction helper (resilient to noisy model output)
86
  def extract_json_from_response(content: str):
87
  try:
88
  return json.loads(content)
89
  except json.JSONDecodeError:
 
90
  json_match = re.search(r'```json(.*?)```', content, re.DOTALL)
91
  if json_match:
92
  json_str = json_match.group(1).strip()
93
  else:
 
94
  brace_match = re.search(r'(\{(?:.|\n)*\})', content)
95
  json_str = brace_match.group(1) if brace_match else content
 
 
96
  json_str = json_str.replace('```json', '').replace('```', '').strip()
97
  json_str = re.sub(r',(\s*[\]\}])', r'\1', json_str)
 
98
  try:
99
  return json.loads(json_str)
100
  except json.JSONDecodeError as e:
101
+ logger.error("JSON parsing error after cleanup: %s\nSnippet: %s", e, content[:1000])
102
  raise ValueError("Failed to parse JSON response from API")
103
 
 
104
  @app.route("/")
105
  def index():
106
+ # Use session first, cookie fallback. This sets HTML lang attribute properly.
107
  language = session.get("language") or request.cookies.get("language") or "en"
 
108
  session["language"] = language
109
  session.permanent = True
110
+ return render_template("index.html", pests_diseases=PESTS_DISEASES, languages=LANGUAGES, current_language=language)
 
 
 
111
 
112
+ # Keep this for compatibility; not required if client only sets cookie & passes lang in fetch
113
  @app.route("/set_language", methods=["POST"])
114
  def set_language():
115
  language = request.form.get("language", "en")
116
  if language not in LANGUAGES:
 
117
  language = "en"
 
 
118
  session["language"] = language
119
  session.permanent = True
120
+ logger.info("Language set to %s in session", language)
121
 
 
122
  resp = make_response(redirect(url_for("index")))
 
123
  max_age = 60 * 60 * 24 * 7
124
  secure_flag = False if disable_secure else True
125
+ resp.set_cookie("language", language, max_age=max_age, secure=secure_flag, samesite="None", httponly=False, path="/")
126
  return resp
127
 
 
128
  @app.route("/get_details/<int:pest_id>")
129
+ def get_details(pest_id):
130
+ # Priority: lang query param > session > cookie > default
131
+ lang_param = request.args.get("lang")
132
+ language = lang_param or session.get("language") or request.cookies.get("language") or "en"
133
+ session["language"] = language
134
+ session.permanent = True
135
 
136
  pest = next((p for p in PESTS_DISEASES if p["id"] == pest_id), None)
137
+ if not pest:
138
  return jsonify({"error": "Not found"}), 404
139
 
140
  cache_key = f"pest_{pest_id}_{language}"
141
  cached = cache.get(cache_key)
142
  if cached:
143
+ logger.info("Cache hit for pest %s (%s)", pest_id, language)
144
  return jsonify(cached)
145
 
146
  if not GEMINI_API_KEY:
147
+ logger.warning("GEMINI_API_KEY missing; returning placeholder details")
148
  result = {
149
  **pest,
150
  "details": {
 
161
  cache.set(cache_key, result)
162
  return jsonify(result)
163
 
164
+ # Build prompt for Gemini
165
  lang_instructions = {
166
  "en": "Respond in English",
167
  "hi": "हिंदी में जवाब दें (Respond in Hindi)",
 
189
  text = getattr(response, "text", "") or str(response)
190
  detailed_info = extract_json_from_response(text)
191
 
 
192
  required_keys = ["description", "lifecycle", "symptoms", "impact", "management", "prevention"]
193
  for key in required_keys:
194
  if key not in detailed_info or "text" not in detailed_info.get(key, {}):
195
  detailed_info[key] = {"title": key.capitalize(), "text": "Information could not be generated for this section."}
196
+ logger.warning("Missing section in API response: %s", key)
197
 
198
  result = {
199
  **pest,
 
204
  cache.set(cache_key, result)
205
  logger.info("Cached details for pest %s (%s)", pest_id, language)
206
  return jsonify(result)
 
207
  except Exception as e:
208
+ logger.exception("Error fetching from Gemini: %s", e)
209
  return jsonify({"error": "Failed to fetch information", "message": str(e)}), 500
210
 
 
211
  if __name__ == "__main__":
212
  port = int(os.environ.get("PORT", 7860))
213
  host = os.environ.get("HOST", "0.0.0.0")