rairo commited on
Commit
e871aa6
·
verified ·
1 Parent(s): 2868e10

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +176 -62
main.py CHANGED
@@ -1179,43 +1179,84 @@ def get_price_trends():
1179
 
1180
  # --- AI Chat Endpoint & History ---
1181
  def _get_price_trend_analysis_for_chat(crop_type=None, location=None):
1182
- if not gemini_client: return "AI service for trend analysis is currently unavailable.", []
1183
- if not FIREBASE_INITIALIZED: return "Firebase not ready for trend analysis.", []
 
 
 
1184
  try:
1185
  all_deals = db.reference('deals', app=db_app).order_by_child('status').equal_to('completed').get() or {}
1186
  price_data_points = []
 
1187
  if all_deals:
1188
  for deal_id, deal in all_deals.items():
1189
- if not deal: continue # Skip if deal data is malformed or None
 
 
1190
  listing_id = deal.get('listing_id')
1191
- listing_details = db.reference(f'listings/{listing_id}', app=db_app).get() if listing_id else None
1192
- if listing_details:
1193
- deal_crop_type = listing_details.get('crop_type')
1194
- deal_location = listing_details.get('location')
1195
 
1196
- match_crop = not crop_type or (deal_crop_type and crop_type.lower() in deal_crop_type.lower())
1197
- match_location = not location or (deal_location and location.lower() in deal_location.lower())
1198
-
1199
- if match_crop and match_location:
 
 
 
 
 
 
 
 
 
 
 
 
 
1200
  price_data_points.append({
1201
- 'price': deal.get('agreed_price') or deal.get('proposed_price'),
1202
- 'date': (deal.get('admin_approved_at') or deal.get('created_at', ''))[:10], # Date only, ensure created_at exists
1203
- 'crop': deal_crop_type
1204
  })
1205
- if not price_data_points: return f"Not enough historical data for trends for {crop_type or 'crops'} in {location or 'all locations'}.", []
 
 
 
 
 
 
1206
 
1207
  data_summary_for_gemini = f"Historical transaction data for {crop_type or 'various crops'} in {location or 'various locations'}:\n"
1208
- for point in price_data_points[:15]: # Limit data points for chat context
1209
  data_summary_for_gemini += f"- Crop: {point.get('crop')}, Price: {point.get('price')}, Date: {point.get('date')}\n"
1210
 
1211
  prompt = f"""
1212
  Analyze transaction data from Tunasonga Agri. Provide a brief price trend analysis for {crop_type if crop_type else 'the general market'}{f' in {location}' if location else ''}.
1213
- Is price increasing, decreasing, or stable? Note patterns. Concise. State if data is sparse.
 
1214
  Data: {data_summary_for_gemini}
 
1215
  Analysis:
1216
  """
1217
- response = gemini_client.generate_content(model='gemini-2.0-flash', contents=[{'parts': [{'text': prompt}]}])
1218
- return response.text.strip(), price_data_points
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1219
  except firebase_exceptions.FirebaseError as fe:
1220
  logger.error(f"Chat (Trend Helper): Firebase Error: {fe}")
1221
  return "Database error occurred while fetching trend data.", []
@@ -1223,59 +1264,102 @@ def _get_price_trend_analysis_for_chat(crop_type=None, location=None):
1223
  logger.error(f"Chat (Trend Helper): Gemini Trend Error or other: {e}")
1224
  return "Could not generate price trend analysis at this time.", []
1225
 
 
1226
  def _fetch_platform_data_for_chat(user_message):
1227
- if not FIREBASE_INITIALIZED: return "Firebase not ready for platform data.", []
 
 
1228
  try:
1229
  keywords = user_message.lower().split()
1230
  extracted_entities = {'crop_type': None, 'location': None, 'listing_type': None}
 
1231
  # Consider making these lists configurable or fetched from DB if they grow large
1232
- common_crops = ["maize", "beans", "tomatoes", "potatoes", "cabbage", "onions", "sorghum", "millet", "groundnuts", "wheat", "soybeans", "sunflower"]
1233
- common_locations = ["harare", "bulawayo", "mutare", "gweru", "masvingo", "chinhoyi", "bindura", "marondera", # Zimbabwe
1234
- "lusaka", "ndola", "kitwe", "livingstone", "chipata", # Zambia
1235
- "maputo", "beira", "nampula", "tete"] # Mozambique
1236
-
 
 
 
1237
  for word in keywords:
1238
- if word in common_crops and not extracted_entities['crop_type']: extracted_entities['crop_type'] = word
1239
- if word in common_locations and not extracted_entities['location']: extracted_entities['location'] = word
 
 
 
 
 
 
1240
 
1241
- if any(k in keywords for k in ["listings", "produce", "selling", "for sale", "offerings"]): extracted_entities['listing_type'] = 'produce'
1242
- elif any(k in keywords for k in ["demands", "requests", "buying", "looking for", "needs"]): extracted_entities['listing_type'] = 'demand'
 
 
1243
 
1244
  if not extracted_entities['listing_type'] and not extracted_entities['crop_type']:
1245
  return "To search platform data, please specify if you're looking for produce for sale or buyer demands, and optionally a crop type or location.", []
1246
 
 
1247
  listings_ref = db.reference('listings', app=db_app).order_by_child('status').equal_to('active')
1248
  all_active_listings = listings_ref.get() or {}
1249
  found_items = []
1250
 
1251
  for lid, ldata in all_active_listings.items():
1252
- if not isinstance(ldata, dict): continue # Skip malformed entries
 
1253
 
1254
  match = True
 
 
1255
  if extracted_entities['listing_type'] and ldata.get('listing_type') != extracted_entities['listing_type']:
1256
  match = False
1257
- # Use 'in' for partial matches on crop type and location if desired, or exact match
1258
- if extracted_entities['crop_type'] and not (extracted_entities['crop_type'] in ldata.get('crop_type', '').lower()):
1259
- match = False
1260
- if extracted_entities['location'] and not (extracted_entities['location'] in ldata.get('location', '').lower()):
1261
- match = False
 
 
 
 
 
 
 
1262
 
1263
  if match:
1264
  item_summary = f"- {ldata.get('listing_type', 'Item').capitalize()}: {ldata.get('crop_type', 'N/A')} in {ldata.get('location', 'N/A')}"
 
1265
  if ldata.get('listing_type') == 'produce':
1266
- item_summary += f", Price: {ldata.get('asking_price', 'N/A')}"
 
1267
  elif ldata.get('listing_type') == 'demand':
1268
- item_summary += f", Price Range: {ldata.get('price_range', 'N/A')}"
1269
- item_summary += f" (Qty: {ldata.get('quantity', 'N/A')})"
 
 
 
1270
  found_items.append(item_summary)
1271
 
1272
  if not found_items:
1273
- return f"No active {extracted_entities['listing_type'] or 'items'} currently found on the platform matching your criteria ({extracted_entities['crop_type'] or 'any crop'}, {extracted_entities['location'] or 'any location'}). You can browse all items in the marketplace section.", []
 
 
 
 
 
 
 
 
 
1274
 
1275
- summary = f"Here's what I found on the Tunasonga Agri platform based on your query:\n" + "\n".join(found_items[:5]) # Show top 5
 
1276
  if len(found_items) > 5:
1277
  summary += f"\n...and {len(found_items) - 5} more. For a full list, please visit the marketplace section or refine your search terms."
 
1278
  return summary, found_items
 
1279
  except firebase_exceptions.FirebaseError as fe:
1280
  logger.error(f"Chat (Platform Data Helper): Firebase Error: {fe}")
1281
  return "Database error occurred while fetching platform data.", []
@@ -1283,21 +1367,27 @@ def _fetch_platform_data_for_chat(user_message):
1283
  logger.error(f"Chat (Platform Data Helper): Error: {e}")
1284
  return "Could not fetch platform data at this time.", []
1285
 
 
1286
  # --- AI Chat Endpoint ---
1287
  @app.route("/api/ai/chat", methods=["POST"])
1288
  def ai_chat():
1289
- auth_header = request.headers.get("Authorization", "");
1290
  uid = None
1291
- response_obj_gemini = None # For logging Gemini response object in case of AttributeError
 
 
1292
  try:
1293
  if not FIREBASE_INITIALIZED or not gemini_client:
1294
  return jsonify({'error': 'Server or AI service not ready.'}), 503
1295
 
1296
  uid = verify_token(auth_header)
1297
- if not uid: # verify_token now raises, so this is a fallback if it somehow returns None without raising
1298
  return jsonify({"error": "Authentication required.", "login_required": True}), 401
1299
 
1300
  data = request.get_json()
 
 
 
1301
  user_message = data.get("message", "").strip()
1302
  if not user_message:
1303
  return jsonify({"error": "Message cannot be empty."}), 400
@@ -1314,9 +1404,20 @@ def ai_chat():
1314
 
1315
  Return ONLY the category string (e.g., "platform_data_query").
1316
  """
1317
- response_obj_gemini = gemini_client.generate_content(model='gemini-2.0-flash', contents=[{'parts': [{'text': classify_prompt}]}])
1318
- intent = response_obj_gemini.text.strip().replace('"', '')
1319
- if intent not in ["platform_data_query", "price_trend_query", "general_agri_info", "other"]:
 
 
 
 
 
 
 
 
 
 
 
1320
  logger.warning(f"AI Chat: Unexpected intent: '{intent}' for query '{user_message}'. Defaulting to general_agri_info.")
1321
  intent = "general_agri_info"
1322
 
@@ -1332,7 +1433,7 @@ def ai_chat():
1332
 
1333
  elif intent == "price_trend_query":
1334
  # Basic entity extraction for trend query (crop, location)
1335
- crop_match = re.search(r"(?:trend for|prices of|price of|trend of)\s+([\w\s]+?)(?:\s+in\s+([\w\s]+))?$", user_message.lower())
1336
  trend_crop, trend_location = (None, None)
1337
  if crop_match:
1338
  trend_crop = crop_match.group(1).strip()
@@ -1362,27 +1463,40 @@ Specific Instructions based on intent:
1362
  Keep your answers clear and easy to understand. Avoid overly technical jargon unless necessary and explain it.
1363
  Answer:
1364
  """
1365
- response_obj_gemini = gemini_client.generate_content(model='gemini-2.0-flash', contents=[{'parts': [{'text': main_prompt}]}])
1366
- ai_response_text = response_obj_gemini.text.strip()
 
 
 
 
 
 
 
 
 
 
 
 
1367
 
1368
  # 4. Store Chat History
1369
- db.reference(f'ai_chat_history/{uid}/{str(uuid.uuid4())}', app=db_app).set({
1370
- 'user_message': user_message,
1371
- 'ai_response': ai_response_text,
1372
- 'intent_classified': intent,
1373
- 'timestamp': datetime.now(timezone.utc).isoformat()
1374
- })
 
 
 
 
 
1375
  return jsonify({"response": ai_response_text, "intent": intent})
1376
 
1377
- except AttributeError as ae: # Gemini response structure issue
1378
  logger.error(f"Gemini Response Attribute Error in ai_chat (UID: {uid}): {ae}. Response object: {response_obj_gemini}")
1379
  ai_response_text = "I'm having a little trouble understanding that. Could you try rephrasing?"
1380
- try: # Attempt to get text if it's a common alternative structure
1381
- if response_obj_gemini and response_obj_gemini.candidates:
1382
- ai_response_text = response_obj_gemini.candidates[0].content.parts[0].text
1383
- except Exception: pass # Stick to default error message
1384
- return jsonify({"response": ai_response_text, "intent": intent or "unknown", "error_detail": "AI_RESPONSE_FORMAT_ISSUE"}), 200 # Return 200 with a user-friendly error
1385
- except Exception as e: # Catches errors from verify_token, Firebase, or other unexpected issues
1386
  return handle_route_errors(e, uid_context=uid)
1387
 
1388
  @app.route('/api/user/ai-chat-history', methods=['GET'])
 
1179
 
1180
  # --- AI Chat Endpoint & History ---
1181
  def _get_price_trend_analysis_for_chat(crop_type=None, location=None):
1182
+ if not gemini_client:
1183
+ return "AI service for trend analysis is currently unavailable.", []
1184
+ if not FIREBASE_INITIALIZED:
1185
+ return "Firebase not ready for trend analysis.", []
1186
+
1187
  try:
1188
  all_deals = db.reference('deals', app=db_app).order_by_child('status').equal_to('completed').get() or {}
1189
  price_data_points = []
1190
+
1191
  if all_deals:
1192
  for deal_id, deal in all_deals.items():
1193
+ if not deal or not isinstance(deal, dict):
1194
+ continue # Skip if deal data is malformed or None
1195
+
1196
  listing_id = deal.get('listing_id')
1197
+ if not listing_id:
1198
+ continue
 
 
1199
 
1200
+ listing_details = db.reference(f'listings/{listing_id}', app=db_app).get()
1201
+ if not listing_details or not isinstance(listing_details, dict):
1202
+ continue
1203
+
1204
+ deal_crop_type = listing_details.get('crop_type')
1205
+ deal_location = listing_details.get('location')
1206
+
1207
+ match_crop = not crop_type or (deal_crop_type and crop_type.lower() in deal_crop_type.lower())
1208
+ match_location = not location or (deal_location and location.lower() in deal_location.lower())
1209
+
1210
+ if match_crop and match_location:
1211
+ price = deal.get('agreed_price') or deal.get('proposed_price')
1212
+ if price: # Only include if we have a valid price
1213
+ date_str = deal.get('admin_approved_at') or deal.get('created_at', '')
1214
+ # Extract date portion safely
1215
+ date_only = date_str[:10] if isinstance(date_str, str) and len(date_str) >= 10 else ''
1216
+
1217
  price_data_points.append({
1218
+ 'price': price,
1219
+ 'date': date_only,
1220
+ 'crop': deal_crop_type or 'Unknown'
1221
  })
1222
+
1223
+ if not price_data_points:
1224
+ return f"Not enough historical data for trends for {crop_type or 'crops'} in {location or 'all locations'}.", []
1225
+
1226
+ # Sort by date (newest first) and limit data points
1227
+ price_data_points.sort(key=lambda x: x.get('date', ''), reverse=True)
1228
+ limited_data = price_data_points[:15]
1229
 
1230
  data_summary_for_gemini = f"Historical transaction data for {crop_type or 'various crops'} in {location or 'various locations'}:\n"
1231
+ for point in limited_data:
1232
  data_summary_for_gemini += f"- Crop: {point.get('crop')}, Price: {point.get('price')}, Date: {point.get('date')}\n"
1233
 
1234
  prompt = f"""
1235
  Analyze transaction data from Tunasonga Agri. Provide a brief price trend analysis for {crop_type if crop_type else 'the general market'}{f' in {location}' if location else ''}.
1236
+ Is price increasing, decreasing, or stable? Note patterns. Keep response concise (2-3 sentences). State if data is sparse.
1237
+
1238
  Data: {data_summary_for_gemini}
1239
+
1240
  Analysis:
1241
  """
1242
+
1243
+ # Use proper Gemini API call structure
1244
+ response = gemini_client.generate_content(prompt)
1245
+ response_text = ""
1246
+
1247
+ # Handle different response structures safely
1248
+ if hasattr(response, 'text') and response.text:
1249
+ response_text = response.text.strip()
1250
+ elif hasattr(response, 'candidates') and response.candidates:
1251
+ if hasattr(response.candidates[0], 'content'):
1252
+ if hasattr(response.candidates[0].content, 'parts') and response.candidates[0].content.parts:
1253
+ response_text = response.candidates[0].content.parts[0].text.strip()
1254
+
1255
+ if not response_text:
1256
+ response_text = "Unable to generate trend analysis at this time."
1257
+
1258
+ return response_text, price_data_points
1259
+
1260
  except firebase_exceptions.FirebaseError as fe:
1261
  logger.error(f"Chat (Trend Helper): Firebase Error: {fe}")
1262
  return "Database error occurred while fetching trend data.", []
 
1264
  logger.error(f"Chat (Trend Helper): Gemini Trend Error or other: {e}")
1265
  return "Could not generate price trend analysis at this time.", []
1266
 
1267
+
1268
  def _fetch_platform_data_for_chat(user_message):
1269
+ if not FIREBASE_INITIALIZED:
1270
+ return "Firebase not ready for platform data.", []
1271
+
1272
  try:
1273
  keywords = user_message.lower().split()
1274
  extracted_entities = {'crop_type': None, 'location': None, 'listing_type': None}
1275
+
1276
  # Consider making these lists configurable or fetched from DB if they grow large
1277
+ common_crops = ["maize", "beans", "tomatoes", "potatoes", "cabbage", "onions", "sorghum",
1278
+ "millet", "groundnuts", "wheat", "soybeans", "sunflower"]
1279
+ common_locations = ["harare", "bulawayo", "mutare", "gweru", "masvingo", "chinhoyi",
1280
+ "bindura", "marondera", # Zimbabwe
1281
+ "lusaka", "ndola", "kitwe", "livingstone", "chipata", # Zambia
1282
+ "maputo", "beira", "nampula", "tete"] # Mozambique
1283
+
1284
+ # Extract entities from keywords
1285
  for word in keywords:
1286
+ if word in common_crops and not extracted_entities['crop_type']:
1287
+ extracted_entities['crop_type'] = word
1288
+ if word in common_locations and not extracted_entities['location']:
1289
+ extracted_entities['location'] = word
1290
+
1291
+ # Determine listing type
1292
+ produce_keywords = ["listings", "produce", "selling", "for sale", "offerings"]
1293
+ demand_keywords = ["demands", "requests", "buying", "looking for", "needs"]
1294
 
1295
+ if any(k in user_message.lower() for k in produce_keywords):
1296
+ extracted_entities['listing_type'] = 'produce'
1297
+ elif any(k in user_message.lower() for k in demand_keywords):
1298
+ extracted_entities['listing_type'] = 'demand'
1299
 
1300
  if not extracted_entities['listing_type'] and not extracted_entities['crop_type']:
1301
  return "To search platform data, please specify if you're looking for produce for sale or buyer demands, and optionally a crop type or location.", []
1302
 
1303
+ # Fetch active listings
1304
  listings_ref = db.reference('listings', app=db_app).order_by_child('status').equal_to('active')
1305
  all_active_listings = listings_ref.get() or {}
1306
  found_items = []
1307
 
1308
  for lid, ldata in all_active_listings.items():
1309
+ if not isinstance(ldata, dict):
1310
+ continue # Skip malformed entries
1311
 
1312
  match = True
1313
+
1314
+ # Check listing type match
1315
  if extracted_entities['listing_type'] and ldata.get('listing_type') != extracted_entities['listing_type']:
1316
  match = False
1317
+
1318
+ # Check crop type match (partial match)
1319
+ if extracted_entities['crop_type']:
1320
+ crop_type_data = ldata.get('crop_type', '').lower()
1321
+ if not crop_type_data or extracted_entities['crop_type'] not in crop_type_data:
1322
+ match = False
1323
+
1324
+ # Check location match (partial match)
1325
+ if extracted_entities['location']:
1326
+ location_data = ldata.get('location', '').lower()
1327
+ if not location_data or extracted_entities['location'] not in location_data:
1328
+ match = False
1329
 
1330
  if match:
1331
  item_summary = f"- {ldata.get('listing_type', 'Item').capitalize()}: {ldata.get('crop_type', 'N/A')} in {ldata.get('location', 'N/A')}"
1332
+
1333
  if ldata.get('listing_type') == 'produce':
1334
+ asking_price = ldata.get('asking_price', 'N/A')
1335
+ item_summary += f", Price: {asking_price}"
1336
  elif ldata.get('listing_type') == 'demand':
1337
+ price_range = ldata.get('price_range', 'N/A')
1338
+ item_summary += f", Price Range: {price_range}"
1339
+
1340
+ quantity = ldata.get('quantity', 'N/A')
1341
+ item_summary += f" (Qty: {quantity})"
1342
  found_items.append(item_summary)
1343
 
1344
  if not found_items:
1345
+ search_criteria = []
1346
+ if extracted_entities['crop_type']:
1347
+ search_criteria.append(extracted_entities['crop_type'])
1348
+ if extracted_entities['location']:
1349
+ search_criteria.append(extracted_entities['location'])
1350
+ if extracted_entities['listing_type']:
1351
+ search_criteria.append(extracted_entities['listing_type'])
1352
+
1353
+ criteria_str = ', '.join(search_criteria) if search_criteria else 'your criteria'
1354
+ return f"No active {extracted_entities['listing_type'] or 'items'} currently found on the platform matching {criteria_str}. You can browse all items in the marketplace section.", []
1355
 
1356
+ # Prepare summary response
1357
+ summary = f"Here's what I found on the Tunasonga Agri platform based on your query:\n" + "\n".join(found_items[:5])
1358
  if len(found_items) > 5:
1359
  summary += f"\n...and {len(found_items) - 5} more. For a full list, please visit the marketplace section or refine your search terms."
1360
+
1361
  return summary, found_items
1362
+
1363
  except firebase_exceptions.FirebaseError as fe:
1364
  logger.error(f"Chat (Platform Data Helper): Firebase Error: {fe}")
1365
  return "Database error occurred while fetching platform data.", []
 
1367
  logger.error(f"Chat (Platform Data Helper): Error: {e}")
1368
  return "Could not fetch platform data at this time.", []
1369
 
1370
+
1371
  # --- AI Chat Endpoint ---
1372
  @app.route("/api/ai/chat", methods=["POST"])
1373
  def ai_chat():
1374
+ auth_header = request.headers.get("Authorization", "")
1375
  uid = None
1376
+ response_obj_gemini = None # For logging Gemini response object in case of AttributeError
1377
+ intent = "unknown" # Initialize intent
1378
+
1379
  try:
1380
  if not FIREBASE_INITIALIZED or not gemini_client:
1381
  return jsonify({'error': 'Server or AI service not ready.'}), 503
1382
 
1383
  uid = verify_token(auth_header)
1384
+ if not uid: # verify_token now raises, so this is a fallback if it somehow returns None without raising
1385
  return jsonify({"error": "Authentication required.", "login_required": True}), 401
1386
 
1387
  data = request.get_json()
1388
+ if not data:
1389
+ return jsonify({"error": "Invalid request data."}), 400
1390
+
1391
  user_message = data.get("message", "").strip()
1392
  if not user_message:
1393
  return jsonify({"error": "Message cannot be empty."}), 400
 
1404
 
1405
  Return ONLY the category string (e.g., "platform_data_query").
1406
  """
1407
+
1408
+ response_obj_gemini = gemini_client.generate_content(classify_prompt)
1409
+
1410
+ # Safely extract intent
1411
+ if hasattr(response_obj_gemini, 'text') and response_obj_gemini.text:
1412
+ intent = response_obj_gemini.text.strip().replace('"', '')
1413
+ elif hasattr(response_obj_gemini, 'candidates') and response_obj_gemini.candidates:
1414
+ if hasattr(response_obj_gemini.candidates[0], 'content'):
1415
+ if hasattr(response_obj_gemini.candidates[0].content, 'parts') and response_obj_gemini.candidates[0].content.parts:
1416
+ intent = response_obj_gemini.candidates[0].content.parts[0].text.strip().replace('"', '')
1417
+
1418
+ # Validate intent
1419
+ valid_intents = ["platform_data_query", "price_trend_query", "general_agri_info", "other"]
1420
+ if intent not in valid_intents:
1421
  logger.warning(f"AI Chat: Unexpected intent: '{intent}' for query '{user_message}'. Defaulting to general_agri_info.")
1422
  intent = "general_agri_info"
1423
 
 
1433
 
1434
  elif intent == "price_trend_query":
1435
  # Basic entity extraction for trend query (crop, location)
1436
+ crop_match = re.search(r"(?:trend for|prices of|price of|trend of)\s+([\w\s]+?)(?:\s+in\s+([\w\s]+?))?(?:\s|$)", user_message.lower())
1437
  trend_crop, trend_location = (None, None)
1438
  if crop_match:
1439
  trend_crop = crop_match.group(1).strip()
 
1463
  Keep your answers clear and easy to understand. Avoid overly technical jargon unless necessary and explain it.
1464
  Answer:
1465
  """
1466
+
1467
+ response_obj_gemini = gemini_client.generate_content(main_prompt)
1468
+ ai_response_text = ""
1469
+
1470
+ # Safely extract response text
1471
+ if hasattr(response_obj_gemini, 'text') and response_obj_gemini.text:
1472
+ ai_response_text = response_obj_gemini.text.strip()
1473
+ elif hasattr(response_obj_gemini, 'candidates') and response_obj_gemini.candidates:
1474
+ if hasattr(response_obj_gemini.candidates[0], 'content'):
1475
+ if hasattr(response_obj_gemini.candidates[0].content, 'parts') and response_obj_gemini.candidates[0].content.parts:
1476
+ ai_response_text = response_obj_gemini.candidates[0].content.parts[0].text.strip()
1477
+
1478
+ if not ai_response_text:
1479
+ ai_response_text = "I'm having trouble generating a response right now. Please try again."
1480
 
1481
  # 4. Store Chat History
1482
+ try:
1483
+ db.reference(f'ai_chat_history/{uid}/{str(uuid.uuid4())}', app=db_app).set({
1484
+ 'user_message': user_message,
1485
+ 'ai_response': ai_response_text,
1486
+ 'intent_classified': intent,
1487
+ 'timestamp': datetime.now(timezone.utc).isoformat()
1488
+ })
1489
+ except Exception as chat_history_error:
1490
+ logger.error(f"Failed to store chat history for UID {uid}: {chat_history_error}")
1491
+ # Don't fail the request if chat history storage fails
1492
+
1493
  return jsonify({"response": ai_response_text, "intent": intent})
1494
 
1495
+ except AttributeError as ae: # Gemini response structure issue
1496
  logger.error(f"Gemini Response Attribute Error in ai_chat (UID: {uid}): {ae}. Response object: {response_obj_gemini}")
1497
  ai_response_text = "I'm having a little trouble understanding that. Could you try rephrasing?"
1498
+ return jsonify({"response": ai_response_text, "intent": intent, "error_detail": "AI_RESPONSE_FORMAT_ISSUE"}), 200
1499
+ except Exception as e: # Catches errors from verify_token, Firebase, or other unexpected issues
 
 
 
 
1500
  return handle_route_errors(e, uid_context=uid)
1501
 
1502
  @app.route('/api/user/ai-chat-history', methods=['GET'])