Spaces:
Sleeping
Sleeping
Update main.py
Browse files
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:
|
| 1183 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
| 1190 |
listing_id = deal.get('listing_id')
|
| 1191 |
-
|
| 1192 |
-
|
| 1193 |
-
deal_crop_type = listing_details.get('crop_type')
|
| 1194 |
-
deal_location = listing_details.get('location')
|
| 1195 |
|
| 1196 |
-
|
| 1197 |
-
|
| 1198 |
-
|
| 1199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1200 |
price_data_points.append({
|
| 1201 |
-
'price':
|
| 1202 |
-
'date':
|
| 1203 |
-
'crop': deal_crop_type
|
| 1204 |
})
|
| 1205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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.
|
|
|
|
| 1214 |
Data: {data_summary_for_gemini}
|
|
|
|
| 1215 |
Analysis:
|
| 1216 |
"""
|
| 1217 |
-
|
| 1218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
|
|
|
|
|
|
| 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",
|
| 1233 |
-
|
| 1234 |
-
|
| 1235 |
-
|
| 1236 |
-
|
|
|
|
|
|
|
|
|
|
| 1237 |
for word in keywords:
|
| 1238 |
-
if word in common_crops and not extracted_entities['crop_type']:
|
| 1239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1240 |
|
| 1241 |
-
if any(k in
|
| 1242 |
-
|
|
|
|
|
|
|
| 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):
|
|
|
|
| 1253 |
|
| 1254 |
match = True
|
|
|
|
|
|
|
| 1255 |
if extracted_entities['listing_type'] and ldata.get('listing_type') != extracted_entities['listing_type']:
|
| 1256 |
match = False
|
| 1257 |
-
|
| 1258 |
-
|
| 1259 |
-
|
| 1260 |
-
|
| 1261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 1267 |
elif ldata.get('listing_type') == 'demand':
|
| 1268 |
-
|
| 1269 |
-
|
|
|
|
|
|
|
|
|
|
| 1270 |
found_items.append(item_summary)
|
| 1271 |
|
| 1272 |
if not found_items:
|
| 1273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1274 |
|
| 1275 |
-
|
|
|
|
| 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
|
|
|
|
|
|
|
| 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:
|
| 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 |
-
|
| 1318 |
-
|
| 1319 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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]
|
| 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 |
-
|
| 1366 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1367 |
|
| 1368 |
# 4. Store Chat History
|
| 1369 |
-
|
| 1370 |
-
'
|
| 1371 |
-
|
| 1372 |
-
|
| 1373 |
-
|
| 1374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1375 |
return jsonify({"response": ai_response_text, "intent": intent})
|
| 1376 |
|
| 1377 |
-
except AttributeError as ae:
|
| 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 |
-
|
| 1381 |
-
|
| 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'])
|