File size: 6,540 Bytes
9aaec2c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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
@router.get("/chat/health")
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"
    }

@router.post("/chatgpt-smart", response_model=ChatResponse)
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"]))