Spaces:
Sleeping
Sleeping
Upload app (16).py
Browse files- app (16).py +203 -0
app (16).py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from fastapi import FastAPI, Request, HTTPException, Form
|
| 3 |
+
import uvicorn
|
| 4 |
+
from gradio_client import Client
|
| 5 |
+
from fastapi.responses import Response
|
| 6 |
+
import json
|
| 7 |
+
import re # Import re for potential future use (e.g., parsing messages)
|
| 8 |
+
import asyncio # Import asyncio for async operations
|
| 9 |
+
|
| 10 |
+
# Connect to your hosted Gradio Space (Futuresony/Mr.Events)
|
| 11 |
+
# This client is used by BOTH the /chat and /webhook endpoints to interact with the core chatbot
|
| 12 |
+
try:
|
| 13 |
+
client = Client("Futuresony/Mr.Events")
|
| 14 |
+
print("Gradio Client for 'Futuresony/Mr.Events' initialized.")
|
| 15 |
+
except Exception as e:
|
| 16 |
+
print(f"Error initializing Gradio Client for 'Futuresony/Mr.Events': {e}")
|
| 17 |
+
print("Ensure the Space name is correct and it is accessible.")
|
| 18 |
+
client = None # Set client to None if initialization fails
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# Get your secure API key for THIS FastAPI application and the hosted Space from environment
|
| 22 |
+
# Assuming the same API key (APP_API_KEY) is used for both.
|
| 23 |
+
VALID_API_KEY = os.getenv("APP_API_KEY")
|
| 24 |
+
# Add a print statement to confirm if the API key is loaded
|
| 25 |
+
print(f"APP_API_KEY loaded: {'Yes' if VALID_API_KEY else 'No'}")
|
| 26 |
+
if not VALID_API_KEY:
|
| 27 |
+
print("Warning: APP_API_KEY secret not set. API key validation and calls to hosted space may fail.")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
app = FastAPI()
|
| 31 |
+
|
| 32 |
+
# --- Chat Endpoint (Existing Functionality) ---
|
| 33 |
+
@app.post("/chat")
|
| 34 |
+
async def chat(request: Request):
|
| 35 |
+
"""
|
| 36 |
+
Handles chat requests via a JSON payload, validates API key,
|
| 37 |
+
and calls the hosted Gradio chatbot with history.
|
| 38 |
+
"""
|
| 39 |
+
print("\n--- Received POST request at /chat ---")
|
| 40 |
+
data = await request.json()
|
| 41 |
+
|
| 42 |
+
# API Key Check for THIS FastAPI application
|
| 43 |
+
api_key = request.headers.get("X-API-Key") # Get API key from headers
|
| 44 |
+
print(f"API Key from header: {api_key[:4]}...") if api_key else "No API Key in header"
|
| 45 |
+
if not VALID_API_KEY or api_key != VALID_API_KEY:
|
| 46 |
+
print("API Key validation failed.")
|
| 47 |
+
raise HTTPException(status_code=403, detail="Invalid API Key")
|
| 48 |
+
print("API Key validation successful.")
|
| 49 |
+
|
| 50 |
+
# Get user message
|
| 51 |
+
user_message = data.get("message")
|
| 52 |
+
if not user_message:
|
| 53 |
+
print("Error: 'message' is required in the request body.")
|
| 54 |
+
raise HTTPException(status_code=400, detail="Message is required")
|
| 55 |
+
print(f"User message: {user_message}")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# Get chat history (assuming it's sent in the request body for stateless API)
|
| 59 |
+
# The chat_history is expected to be a list of lists: [[user_msg, bot_msg], ...]
|
| 60 |
+
# If not provided, initialize as empty list.
|
| 61 |
+
chat_history = data.get("chat_history", [])
|
| 62 |
+
# print(f"Received chat history: {chat_history}") # Be cautious logging history
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
# --- Call the hosted Gradio chatbot ---
|
| 66 |
+
if client is None:
|
| 67 |
+
print("Error: Gradio Client not initialized. Cannot call chatbot.")
|
| 68 |
+
raise HTTPException(status_code=500, detail="Chatbot service not available.")
|
| 69 |
+
|
| 70 |
+
try:
|
| 71 |
+
print(f"Calling hosted Gradio Space 'Futuresony/Mr.Events' /chat endpoint from /chat...")
|
| 72 |
+
# Note: The Gradio ChatInterface API typically expects query (current message)
|
| 73 |
+
# and chat_history (history *before* the current turn).
|
| 74 |
+
# Use the same VALID_API_KEY for the hosted space call
|
| 75 |
+
result = await client.predict( # Use await because client.predict can be async
|
| 76 |
+
query=user_message,
|
| 77 |
+
chat_history=chat_history, # Pass the history directly
|
| 78 |
+
api_key=VALID_API_KEY, # Pass the APP_API_KEY to the hosted space
|
| 79 |
+
api_name="/chat" # Ensure this matches the API endpoint exposed by the hosted Gradio app
|
| 80 |
+
)
|
| 81 |
+
print(f"Received raw result from hosted Space: {result}")
|
| 82 |
+
|
| 83 |
+
# The result from client.predict on a ChatInterface is typically the assistant's response string
|
| 84 |
+
assistant_response = result
|
| 85 |
+
if not isinstance(assistant_response, str):
|
| 86 |
+
print(f"Warning: Hosted Space returned unexpected result type: {type(assistant_response)}. Raw result: {result}")
|
| 87 |
+
# Attempt to convert to string or handle appropriately
|
| 88 |
+
assistant_response = str(assistant_response)
|
| 89 |
+
print(f"Formatted assistant response: {assistant_response}")
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
except Exception as e:
|
| 93 |
+
print(f"Error calling hosted Gradio Space from /chat: {e}")
|
| 94 |
+
raise HTTPException(status_code=500, detail=f"Error communicating with chatbot model: {e}")
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
return {"response": assistant_response}
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
# --- Twilio Webhook Endpoint ---
|
| 101 |
+
# In-memory dictionary to store history per sender (NOT for production!)
|
| 102 |
+
# Replace this with a persistent storage solution (database, file storage) for production.
|
| 103 |
+
conversation_histories = {}
|
| 104 |
+
# For production-level history management, you would initialize and interact with
|
| 105 |
+
# a database or other persistent storage here.
|
| 106 |
+
|
| 107 |
+
@app.post("/webhook")
|
| 108 |
+
async def webhook(
|
| 109 |
+
# Explicitly receive form data parameters expected from Twilio
|
| 110 |
+
From: str = Form(...), # Sender's phone number
|
| 111 |
+
Body: str = Form(...), # Message content
|
| 112 |
+
# Twilio sends other parameters like MessageSid, To, AccountSid, etc.
|
| 113 |
+
# You can receive them here if needed:
|
| 114 |
+
# MessageSid: str = Form(None),
|
| 115 |
+
# To: str = Form(None),
|
| 116 |
+
request: Request = None # Keep request for raw access if needed
|
| 117 |
+
):
|
| 118 |
+
"""
|
| 119 |
+
Handles incoming Twilio webhook requests for new messages,
|
| 120 |
+
processes them with the chatbot, and returns TwiML.
|
| 121 |
+
Note: This implementation uses in-memory history (NOT for production).
|
| 122 |
+
"""
|
| 123 |
+
print("\n--- Received POST request at /webhook from Twilio ---")
|
| 124 |
+
|
| 125 |
+
# Access the incoming message and sender number directly from Form parameters
|
| 126 |
+
incoming_message = Body
|
| 127 |
+
sender_number = From
|
| 128 |
+
|
| 129 |
+
print(f"Parsed Incoming Message: '{incoming_message}' from {sender_number}")
|
| 130 |
+
|
| 131 |
+
# --- Conversation History Management (In-Memory - NOT Persistent!) ---
|
| 132 |
+
# In a real application, you would load/save history from a database/file.
|
| 133 |
+
chat_history = conversation_histories.get(sender_number, [])
|
| 134 |
+
print(f"Retrieved in-memory history for {sender_number}: {chat_history}")
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
# --- Call Chatbot Logic ---
|
| 138 |
+
if client is None:
|
| 139 |
+
print("Error: Gradio Client not initialized. Cannot call chatbot from webhook.")
|
| 140 |
+
bot_response = "Error: Chatbot service is not available."
|
| 141 |
+
else:
|
| 142 |
+
try:
|
| 143 |
+
# Use the same VALID_API_KEY for the hosted space call from webhook
|
| 144 |
+
print(f"Calling hosted Gradio Space 'Futuresony/Mr.Events' /chat endpoint from /webhook...")
|
| 145 |
+
print(f" Query: {incoming_message}")
|
| 146 |
+
# print(f" History: {chat_history}") # Be cautious logging history
|
| 147 |
+
|
| 148 |
+
# Call the hosted chatbot with the retrieved history
|
| 149 |
+
# Gradio client expects query (current message) and chat_history (history *before* current turn)
|
| 150 |
+
result = await client.predict( # Use await
|
| 151 |
+
query=incoming_message,
|
| 152 |
+
chat_history=chat_history, # Pass the retrieved history
|
| 153 |
+
api_key=VALID_API_KEY, # Pass the APP_API_KEY to the hosted space
|
| 154 |
+
api_name="/chat" # Ensure this matches the API endpoint exposed by the hosted Gradio app
|
| 155 |
+
)
|
| 156 |
+
print(f"Received raw result from hosted Space for webhook: {result}")
|
| 157 |
+
|
| 158 |
+
bot_response = result
|
| 159 |
+
if not isinstance(bot_response, str):
|
| 160 |
+
print(f"Warning: Hosted Space returned unexpected result type for webhook: {type(bot_response)}. Raw result: {result}")
|
| 161 |
+
bot_response = str(bot_response)
|
| 162 |
+
print(f"Formatted chatbot response for webhook: {bot_response}")
|
| 163 |
+
|
| 164 |
+
except Exception as e:
|
| 165 |
+
print(f"Error calling hosted Gradio Space from /webhook: {e}")
|
| 166 |
+
bot_response = f"An error occurred while processing your request: {e}" # Provide a user-friendly error message
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
# --- Update and Store History (In-Memory - NOT Persistent!) ---
|
| 170 |
+
# Append the current turn (user message + bot response)
|
| 171 |
+
chat_history.append([incoming_message, bot_response])
|
| 172 |
+
conversation_histories[sender_number] = chat_history
|
| 173 |
+
print(f"Updated in-memory history for {sender_number}: {conversation_histories[sender_number]}")
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
# --- Generate TwiML Response ---
|
| 177 |
+
# Twilio expects TwiML XML to know what to do with the message
|
| 178 |
+
# Use f-string with triple single quotes for multi-line string to avoid conflicts with HTML-like tags
|
| 179 |
+
twiml_response = f'''<Response><Message>{bot_response}</Message></Response>'''
|
| 180 |
+
print(f"Generated TwiML response: {twiml_response}")
|
| 181 |
+
|
| 182 |
+
# Return TwiML with the correct media type
|
| 183 |
+
return Response(content=twiml_response, media_type="application/xml")
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
if __name__ == "__main__":
|
| 187 |
+
# When running this app.py directly (e.g., with `uvicorn app:app --reload`),
|
| 188 |
+
# this block is executed. On Hugging Face Spaces, the environment typically
|
| 189 |
+
# runs the FastAPI application directly without executing this block.
|
| 190 |
+
# If you need specific initializations (like loading RAG data, initializing cache)
|
| 191 |
+
# when running on Spaces via FastAPI directly, you might need to move them
|
| 192 |
+
# outside this __main__ block or ensure they are called on app startup.
|
| 193 |
+
|
| 194 |
+
# Example (commented out, adjust based on your needs):
|
| 195 |
+
# from app_components import authenticate_google_sheets, load_business_info, initialize_cache
|
| 196 |
+
# authenticate_google_sheets()
|
| 197 |
+
# load_business_info()
|
| 198 |
+
# initialize_cache()
|
| 199 |
+
# cleanup_expired_cache_entries() # Optional
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
print("Starting FastAPI application with Uvicorn...")
|
| 203 |
+
uvicorn.run(app, host="0.0.0.0", port=7860) # HF default port
|