Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, HTTPException, Request, File, UploadFile | |
| from fastapi.responses import JSONResponse | |
| from pydantic import BaseModel | |
| from typing import Optional, List | |
| import logging | |
| from config import settings | |
| from agent import agent | |
| from database import db, Platform | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| app = FastAPI(title="Rural E-commerce Suggestion Bot API", version="1.0.0") | |
| class MessageRequest(BaseModel): | |
| platform: str | |
| user_id: str | |
| text: Optional[str] = None | |
| image_url: Optional[str] = None | |
| audio_url: Optional[str] = None | |
| lang: Optional[str] = "en" | |
| class SuggestRequest(BaseModel): | |
| user_id: str | |
| platform: str | |
| category: str | |
| budget_inr: int | |
| text: Optional[str] = None | |
| class MessageResponse(BaseModel): | |
| success: bool | |
| text: str | |
| picks: Optional[List[dict]] = None | |
| error: Optional[str] = None | |
| async def root(): | |
| return { | |
| "service": "Rural E-commerce Suggestion Bot", | |
| "version": "1.0.0", | |
| "status": "running" | |
| } | |
| async def health_check(): | |
| """Health check endpoint for monitoring.""" | |
| try: | |
| conn = db.get_connection() | |
| conn.execute("SELECT 1") | |
| conn.close() | |
| db_status = "healthy" | |
| except Exception as e: | |
| db_status = f"unhealthy: {str(e)}" | |
| return { | |
| "status": "ok" if db_status == "healthy" else "degraded", | |
| "database": db_status, | |
| "version": "1.0.0" | |
| } | |
| async def handle_message(request: MessageRequest): | |
| """Universal message endpoint for all channels.""" | |
| try: | |
| logger.info(f"Received message from {request.platform} user {request.user_id}") | |
| if not request.text: | |
| return MessageResponse( | |
| success=False, | |
| text="Please provide a message.", | |
| error="Empty message" | |
| ) | |
| image_data = None | |
| audio_data = None | |
| if request.image_url: | |
| import requests | |
| try: | |
| resp = requests.get(request.image_url, timeout=10) | |
| if resp.status_code == 200: | |
| image_data = resp.content | |
| except Exception as e: | |
| logger.error(f"Failed to fetch image: {e}") | |
| if request.audio_url: | |
| import requests | |
| try: | |
| resp = requests.get(request.audio_url, timeout=10) | |
| if resp.status_code == 200: | |
| audio_data = resp.content | |
| except Exception as e: | |
| logger.error(f"Failed to fetch audio: {e}") | |
| result = agent.process_request( | |
| platform=request.platform, | |
| platform_user_id=request.user_id, | |
| user_message=request.text, | |
| image_data=image_data, | |
| audio_data=audio_data | |
| ) | |
| if result.get("success"): | |
| db.log_event( | |
| user_id=db.get_user(Platform(request.platform), request.user_id)["id"], | |
| channel=request.platform, | |
| event_type="message_processed", | |
| payload_meta={"text": request.text[:100]} | |
| ) | |
| return MessageResponse( | |
| success=True, | |
| text=result["response_text"], | |
| picks=result.get("picks") | |
| ) | |
| else: | |
| return MessageResponse( | |
| success=False, | |
| text=result.get("response_text", "An error occurred"), | |
| error=result.get("error") | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error processing message: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def suggest_products(request: SuggestRequest): | |
| """Direct product suggestion endpoint.""" | |
| try: | |
| logger.info(f"Suggestion request for {request.category} under ₹{request.budget_inr}") | |
| message_text = request.text or f"Suggest {request.category} under ₹{request.budget_inr}" | |
| result = agent.process_request( | |
| platform=request.platform, | |
| platform_user_id=request.user_id, | |
| user_message=message_text | |
| ) | |
| return { | |
| "success": result.get("success"), | |
| "response": result.get("response_text"), | |
| "picks": result.get("picks"), | |
| "error": result.get("error") | |
| } | |
| except Exception as e: | |
| logger.error(f"Error in suggest endpoint: {e}") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def whatsapp_webhook(request: Request): | |
| """WhatsApp webhook handler.""" | |
| try: | |
| data = await request.json() | |
| logger.info(f"WhatsApp webhook: {data}") | |
| if not data.get("entry"): | |
| return JSONResponse({"status": "no_entry"}) | |
| for entry in data["entry"]: | |
| for change in entry.get("changes", []): | |
| value = change.get("value", {}) | |
| messages = value.get("messages", []) | |
| for message in messages: | |
| from_number = message.get("from") | |
| msg_type = message.get("type") | |
| text = None | |
| image_url = None | |
| audio_url = None | |
| if msg_type == "text": | |
| text = message.get("text", {}).get("body") | |
| elif msg_type == "image": | |
| image_url = message.get("image", {}).get("url") | |
| elif msg_type == "audio": | |
| audio_url = message.get("audio", {}).get("url") | |
| if text or image_url or audio_url: | |
| result = agent.process_request( | |
| platform="whatsapp", | |
| platform_user_id=from_number, | |
| user_message=text or "Processing media...", | |
| image_data=None, | |
| audio_data=None | |
| ) | |
| from channels.whatsapp_handler import send_whatsapp_message | |
| await send_whatsapp_message(from_number, result["response_text"]) | |
| return JSONResponse({"status": "ok"}) | |
| except Exception as e: | |
| logger.error(f"WhatsApp webhook error: {e}") | |
| return JSONResponse({"status": "error", "message": str(e)}) | |
| async def whatsapp_verify(request: Request): | |
| """WhatsApp webhook verification.""" | |
| mode = request.query_params.get("hub.mode") | |
| token = request.query_params.get("hub.verify_token") | |
| challenge = request.query_params.get("hub.challenge") | |
| if mode == "subscribe" and token == settings.whatsapp_verify_token: | |
| logger.info("WhatsApp webhook verified") | |
| return int(challenge) | |
| return JSONResponse({"status": "error"}, status_code=403) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host=settings.host, port=settings.port) | |