File size: 9,519 Bytes
141953b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import os
from fastapi import FastAPI, Request, HTTPException, Form
import uvicorn
from gradio_client import Client
from fastapi.responses import Response
import json
import re # Import re for potential future use (e.g., parsing messages)
import asyncio # Import asyncio for async operations

# Connect to your hosted Gradio Space (Futuresony/Mr.Events)
# This client is used by BOTH the /chat and /webhook endpoints to interact with the core chatbot
try:
    client = Client("Futuresony/Mr.Events")
    print("Gradio Client for 'Futuresony/Mr.Events' initialized.")
except Exception as e:
    print(f"Error initializing Gradio Client for 'Futuresony/Mr.Events': {e}")
    print("Ensure the Space name is correct and it is accessible.")
    client = None # Set client to None if initialization fails


# Get your secure API key for THIS FastAPI application and the hosted Space from environment
# Assuming the same API key (APP_API_KEY) is used for both.
VALID_API_KEY = os.getenv("APP_API_KEY")
# Add a print statement to confirm if the API key is loaded
print(f"APP_API_KEY loaded: {'Yes' if VALID_API_KEY else 'No'}")
if not VALID_API_KEY:
    print("Warning: APP_API_KEY secret not set. API key validation and calls to hosted space may fail.")


app = FastAPI()

# --- Chat Endpoint (Existing Functionality) ---
@app.post("/chat")
async def chat(request: Request):
    """
    Handles chat requests via a JSON payload, validates API key,
    and calls the hosted Gradio chatbot with history.
    """
    print("\n--- Received POST request at /chat ---")
    data = await request.json()

    # API Key Check for THIS FastAPI application
    api_key = request.headers.get("X-API-Key") # Get API key from headers
    print(f"API Key from header: {api_key[:4]}...") if api_key else "No API Key in header"
    if not VALID_API_KEY or api_key != VALID_API_KEY:
        print("API Key validation failed.")
        raise HTTPException(status_code=403, detail="Invalid API Key")
    print("API Key validation successful.")

    # Get user message
    user_message = data.get("message")
    if not user_message:
        print("Error: 'message' is required in the request body.")
        raise HTTPException(status_code=400, detail="Message is required")
    print(f"User message: {user_message}")


    # Get chat history (assuming it's sent in the request body for stateless API)
    # The chat_history is expected to be a list of lists: [[user_msg, bot_msg], ...]
    # If not provided, initialize as empty list.
    chat_history = data.get("chat_history", [])
    # print(f"Received chat history: {chat_history}") # Be cautious logging history


    # --- Call the hosted Gradio chatbot ---
    if client is None:
        print("Error: Gradio Client not initialized. Cannot call chatbot.")
        raise HTTPException(status_code=500, detail="Chatbot service not available.")

    try:
        print(f"Calling hosted Gradio Space 'Futuresony/Mr.Events' /chat endpoint from /chat...")
        # Note: The Gradio ChatInterface API typically expects query (current message)
        # and chat_history (history *before* the current turn).
        # Use the same VALID_API_KEY for the hosted space call
        result = await client.predict( # Use await because client.predict can be async
            query=user_message,
            chat_history=chat_history, # Pass the history directly
            api_key=VALID_API_KEY, # Pass the APP_API_KEY to the hosted space
            api_name="/chat" # Ensure this matches the API endpoint exposed by the hosted Gradio app
        )
        print(f"Received raw result from hosted Space: {result}")

        # The result from client.predict on a ChatInterface is typically the assistant's response string
        assistant_response = result
        if not isinstance(assistant_response, str):
            print(f"Warning: Hosted Space returned unexpected result type: {type(assistant_response)}. Raw result: {result}")
            # Attempt to convert to string or handle appropriately
            assistant_response = str(assistant_response)
        print(f"Formatted assistant response: {assistant_response}")


    except Exception as e:
        print(f"Error calling hosted Gradio Space from /chat: {e}")
        raise HTTPException(status_code=500, detail=f"Error communicating with chatbot model: {e}")


    return {"response": assistant_response}


# --- Twilio Webhook Endpoint ---
# In-memory dictionary to store history per sender (NOT for production!)
# Replace this with a persistent storage solution (database, file storage) for production.
conversation_histories = {}
# For production-level history management, you would initialize and interact with
# a database or other persistent storage here.

@app.post("/webhook")
async def webhook(
    # Explicitly receive form data parameters expected from Twilio
    From: str = Form(...), # Sender's phone number
    Body: str = Form(...), # Message content
    # Twilio sends other parameters like MessageSid, To, AccountSid, etc.
    # You can receive them here if needed:
    # MessageSid: str = Form(None),
    # To: str = Form(None),
    request: Request = None # Keep request for raw access if needed
):
    """
    Handles incoming Twilio webhook requests for new messages,
    processes them with the chatbot, and returns TwiML.
    Note: This implementation uses in-memory history (NOT for production).
    """
    print("\n--- Received POST request at /webhook from Twilio ---")

    # Access the incoming message and sender number directly from Form parameters
    incoming_message = Body
    sender_number = From

    print(f"Parsed Incoming Message: '{incoming_message}' from {sender_number}")

    # --- Conversation History Management (In-Memory - NOT Persistent!) ---
    # In a real application, you would load/save history from a database/file.
    chat_history = conversation_histories.get(sender_number, [])
    print(f"Retrieved in-memory history for {sender_number}: {chat_history}")


    # --- Call Chatbot Logic ---
    if client is None:
        print("Error: Gradio Client not initialized. Cannot call chatbot from webhook.")
        bot_response = "Error: Chatbot service is not available."
    else:
        try:
             # Use the same VALID_API_KEY for the hosted space call from webhook
             print(f"Calling hosted Gradio Space 'Futuresony/Mr.Events' /chat endpoint from /webhook...")
             print(f"  Query: {incoming_message}")
             # print(f"  History: {chat_history}") # Be cautious logging history

             # Call the hosted chatbot with the retrieved history
             # Gradio client expects query (current message) and chat_history (history *before* current turn)
             result = await client.predict( # Use await
                query=incoming_message,
                chat_history=chat_history, # Pass the retrieved history
                api_key=VALID_API_KEY, # Pass the APP_API_KEY to the hosted space
                api_name="/chat" # Ensure this matches the API endpoint exposed by the hosted Gradio app
             )
             print(f"Received raw result from hosted Space for webhook: {result}")

             bot_response = result
             if not isinstance(bot_response, str):
                 print(f"Warning: Hosted Space returned unexpected result type for webhook: {type(bot_response)}. Raw result: {result}")
                 bot_response = str(bot_response)
             print(f"Formatted chatbot response for webhook: {bot_response}")

        except Exception as e:
             print(f"Error calling hosted Gradio Space from /webhook: {e}")
             bot_response = f"An error occurred while processing your request: {e}" # Provide a user-friendly error message


    # --- Update and Store History (In-Memory - NOT Persistent!) ---
    # Append the current turn (user message + bot response)
    chat_history.append([incoming_message, bot_response])
    conversation_histories[sender_number] = chat_history
    print(f"Updated in-memory history for {sender_number}: {conversation_histories[sender_number]}")


    # --- Generate TwiML Response ---
    # Twilio expects TwiML XML to know what to do with the message
    # Use f-string with triple single quotes for multi-line string to avoid conflicts with HTML-like tags
    twiml_response = f'''<Response><Message>{bot_response}</Message></Response>'''
    print(f"Generated TwiML response: {twiml_response}")

    # Return TwiML with the correct media type
    return Response(content=twiml_response, media_type="application/xml")


if __name__ == "__main__":
    # When running this app.py directly (e.g., with `uvicorn app:app --reload`),
    # this block is executed. On Hugging Face Spaces, the environment typically
    # runs the FastAPI application directly without executing this block.
    # If you need specific initializations (like loading RAG data, initializing cache)
    # when running on Spaces via FastAPI directly, you might need to move them
    # outside this __main__ block or ensure they are called on app startup.

    # Example (commented out, adjust based on your needs):
    # from app_components import authenticate_google_sheets, load_business_info, initialize_cache
    # authenticate_google_sheets()
    # load_business_info()
    # initialize_cache()
    # cleanup_expired_cache_entries() # Optional


    print("Starting FastAPI application with Uvicorn...")
    uvicorn.run(app, host="0.0.0.0", port=7860) # HF default port