Spaces:
Runtime error
Runtime error
| from fastapi import APIRouter, HTTPException | |
| from fastapi.concurrency import run_in_threadpool | |
| from pydantic import BaseModel | |
| from typing import Optional | |
| import json, os, openai | |
| import logging | |
| router = APIRouter() | |
| logger = logging.getLogger(__name__) | |
| class ChatRequest(BaseModel): | |
| prompt: str | |
| language: Optional[str] = "en" # Added: language support (en/it) | |
| class ChatResponse(BaseModel): | |
| status: str | |
| message: Optional[str] = None | |
| parsed_data: Optional[dict] = None | |
| OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") | |
| client = openai.OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None | |
| # Multi-language system prompts | |
| SYSTEM_PROMPTS = { | |
| "en": """You are ChefCode's AI Inventory Parser. Parse commands silently and return only JSON. | |
| Extract: item_name, unit, quantity, unit_price, type, lot_number (optional), expiry_date (optional) | |
| Type detection: | |
| beef/chicken/pork/meat β meat | |
| lettuce/tomato/onion/vegetable β vegetable | |
| apple/banana/orange/fruit β fruit | |
| milk/cheese/yogurt/dairy β dairy | |
| water/juice/wine/soda/beverage β beverage | |
| sugar/flour/pasta/rice/bread β grocery | |
| soap/detergent/cleaner β cleaning | |
| Lot number keywords: "lot", "batch", "lot number", "batch number", "LOT" | |
| Expiry date keywords: "expires", "expiry", "best before", "use by", "exp date", "expiration" | |
| Date formats: Parse dates flexibly (e.g., "Dec 25", "12/25/2024", "2024-12-25", "December 25 2024") | |
| If price missing: {"status": "ask_price", "message": "Price?"} | |
| If complete: {"status": "complete", "parsed_data": {"item_name": "...", "unit": "...", "quantity": ..., "unit_price": ..., "type": "...", "lot_number": "..." or null, "expiry_date": "YYYY-MM-DD" or null}} | |
| Output ONLY valid JSON. No explanations.""", | |
| "it": """Sei l'AI parser di ChefCode. Analizza comandi in silenzio e restituisci solo JSON. | |
| Estrai: item_name, unit, quantity, unit_price, type, lot_number (opzionale), expiry_date (opzionale) | |
| Rilevamento tipo: | |
| manzo/pollo/maiale/carne β meat | |
| lattuga/pomodoro/cipolla/verdura β vegetable | |
| mela/banana/arancia/frutta β fruit | |
| latte/formaggio/yogurt/latticini β dairy | |
| acqua/succo/vino/bevanda β beverage | |
| zucchero/farina/pasta/riso/pane β grocery | |
| sapone/detergente β cleaning | |
| Parole chiave lotto: "lotto", "batch", "numero lotto", "lotto numero" | |
| Parole chiave scadenza: "scadenza", "scade", "da consumarsi entro", "exp", "data scadenza" | |
| Formati data: Analizza date flessibilmente (es. "25 dic", "25/12/2024", "2024-12-25", "25 dicembre 2024") | |
| Se manca prezzo: {"status": "ask_price", "message": "Prezzo?"} | |
| Se completo: {"status": "complete", "parsed_data": {"item_name": "...", "unit": "...", "quantity": ..., "unit_price": ..., "type": "...", "lot_number": "..." o null, "expiry_date": "YYYY-MM-DD" o null}} | |
| Output SOLO JSON valido. Niente spiegazioni.""" | |
| } | |
| # Added: Health check endpoint | |
| async def chat_health(): | |
| """Health check for ChatGPT integration""" | |
| return { | |
| "status": "available" if client else "unavailable", | |
| "message": "ChatGPT integration ready" if client else "OPENAI_API_KEY not set", | |
| "supported_languages": ["en", "it"], | |
| "default_language": "en" | |
| } | |
| async def parse_inventory_command(request: ChatRequest): | |
| if not client: | |
| # Mock response when API key is not set | |
| lang = request.language or "en" | |
| mock_message = { | |
| "en": "ChatGPT integration ready. Please set OPENAI_API_KEY environment variable to enable AI functionality.", | |
| "it": "Integrazione ChatGPT pronta. Imposta la variabile d'ambiente OPENAI_API_KEY per abilitare la funzionalitΓ AI." | |
| } | |
| return ChatResponse( | |
| status="mock", | |
| message=mock_message.get(lang, mock_message["en"]) | |
| ) | |
| lang = request.language or "en" | |
| try: | |
| # Get language-specific system prompt | |
| system_prompt = SYSTEM_PROMPTS.get(lang, SYSTEM_PROMPTS["en"]) | |
| # Run blocking OpenAI call in thread pool to avoid blocking event loop | |
| response = await run_in_threadpool( | |
| lambda: client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": request.prompt} | |
| ], | |
| temperature=0 | |
| ) | |
| ) | |
| ai_response = response.choices[0].message.content.strip() | |
| try: | |
| parsed = json.loads(ai_response) | |
| except json.JSONDecodeError: | |
| logger.error(f"Invalid JSON response from OpenAI: {ai_response}") | |
| error_msg = { | |
| "en": "Unable to parse AI response. Please try again.", | |
| "it": "Impossibile analizzare la risposta AI. Riprova." | |
| } | |
| raise HTTPException(status_code=500, detail=error_msg.get(lang, error_msg["en"])) | |
| # If missing price β ask user | |
| if parsed.get("status") == "ask_price": | |
| return ChatResponse( | |
| status="ask_price", | |
| message=parsed.get("message") | |
| ) | |
| # If complete β save to inventory | |
| elif parsed.get("status") == "complete": | |
| data = parsed.get("parsed_data") | |
| success_msg = { | |
| "en": f"Item '{data.get('item_name')}' added to inventory.", | |
| "it": f"Articolo '{data.get('item_name')}' aggiunto all'inventario." | |
| } | |
| return ChatResponse( | |
| status="success", | |
| message=success_msg.get(lang, success_msg["en"]), | |
| parsed_data=data | |
| ) | |
| else: | |
| error_msg = { | |
| "en": "Unexpected AI response format", | |
| "it": "Formato risposta AI inaspettato" | |
| } | |
| raise HTTPException(status_code=400, detail=error_msg.get(lang, error_msg["en"])) | |
| except HTTPException: | |
| # Re-raise HTTP exceptions as-is | |
| raise | |
| except Exception as e: | |
| # Log the detailed error internally but return generic message to user | |
| logger.error(f"ChatGPT error: {type(e).__name__} - {str(e)}") | |
| error_msg = { | |
| "en": "An error occurred while processing your request. Please try again.", | |
| "it": "Si Γ¨ verificato un errore durante l'elaborazione della richiesta. Riprova." | |
| } | |
| raise HTTPException(status_code=500, detail=error_msg.get(lang, error_msg["en"])) | |