IFX-sandbox / gradio_app.py
aliss77777's picture
Upload folder using huggingface_hub
c9a37bd verified
import os
import uuid
import asyncio
import json
import gradio as gr
from zep_cloud.client import AsyncZep
from zep_cloud.types import Message
# Import the Gradio-specific implementations directly, not patching
from gradio_graph import graph
from gradio_llm import llm
import gradio_utils
from components.game_recap_component import create_game_recap_component
from components.player_card_component import create_player_card_component
from components.team_story_component import create_team_story_component
# Import the Gradio-compatible agent instead of the original agent
import gradio_agent
from gradio_agent import generate_response, set_memory_session_id
# Import cache getter functions
from tools.game_recap import get_last_game_data
from tools.player_search import get_last_player_data
from tools.team_story import get_last_team_story_data
# --- IMPORTANT: Need access to the lists themselves to clear them --- #
from tools import game_recap, player_search, team_story
# Load persona session IDs
def load_persona_session_ids():
"""Load persona session IDs from JSON file"""
try:
with open("z_utils/persona_session_ids.json", "r") as f:
return json.load(f)
except Exception as e:
print(f"[ERROR] Failed to load persona_session_ids.json: {e}")
# Fallback to hardcoded values if file can't be loaded
return {
"Casual Fan": "241b3478c7634492abee9f178b5341cb",
"Super Fan": "dedcf5cb0d71475f976f4f66d98d6400"
}
# Define CSS directly
css = """
/* Base styles */
body {
font-family: 'Arial', sans-serif;
background-color: #111111;
color: #E6E6E6;
}
/* Headings */
h1, h2, h3 {
color: #AA0000;
}
/* Buttons */
button {
background-color: #AA0000;
color: #FFFFFF;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #B3995D;
}
/* Game Recap Component */
.game-recap-container {
background-color: #111111;
padding: 20px;
margin: 20px 0;
border-radius: 10px;
}
.game-recap-row {
display: flex;
justify-content: space-between;
align-items: center;
margin: 20px 0;
}
.team-info {
text-align: center;
}
.team-logo {
width: 100px;
height: 100px;
margin-bottom: 10px;
}
.team-name {
font-size: 1.2em;
color: #E6E6E6;
}
.team-score {
font-size: 2em;
color: #FFFFFF;
font-weight: bold;
}
.winner {
color: #B3995D;
}
.video-preview {
background-color: #222222;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
}
/* Chat Interface */
.chatbot {
background-color: #111111;
border: 1px solid #333333; /* Reverted for Bug 2 */
border-radius: 10px; /* Reverted for Bug 2 */
padding: 20px;
margin: 20px 0;
}
.message-input {
background-color: #222222;
color: #E6E6E6;
border: 1px solid #333333;
border-radius: 5px;
padding: 10px;
}
.clear-button {
background-color: #AA0000;
color: #FFFFFF;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
}
.clear-button:hover {
background-color: #B3995D;
}
"""
# Initialize Zep client
zep_api_key = os.environ.get("ZEP_API_KEY")
if not zep_api_key:
print("ZEP_API_KEY environment variable is not set. Memory features will be disabled.")
zep = None
else:
zep = AsyncZep(api_key=zep_api_key)
class AppState:
def __init__(self):
self.chat_history = []
self.initialized = False
self.user_id = None
self.session_id = None
self.zep_client = None
def add_message(self, role, content):
self.chat_history.append({"role": role, "content": content})
def get_chat_history(self):
return self.chat_history
# Initialize global state
state = AppState()
# Add welcome message to state
welcome_message = """
# 🏈 Welcome to the 49ers FanAI Hub!
I can help you with:
- Information about the 49ers, players, and fans
- Finding 49ers games based on plot descriptions or themes
- Discovering connections between people in the 49ers industry
What would you like to know about today?
"""
# Initialize the chat session
async def initialize_chat():
"""Initialize the chat session with Zep and return a welcome message."""
try:
# Generate unique identifiers for the user and session
state.user_id = gradio_utils.get_user_id()
state.session_id = gradio_utils.get_session_id()
print(f"Starting new chat session. User ID: {state.user_id}, Session ID: {state.session_id}")
# Register user in Zep if available
if zep:
await zep.user.add(
user_id=state.user_id,
email="[email protected]",
first_name="User",
last_name="MovieFan",
)
# Start a new session in Zep
await zep.memory.add_session(
session_id=state.session_id,
user_id=state.user_id,
)
# Add welcome message to state
state.add_message("assistant", welcome_message)
state.initialized = True
# Return the welcome message in the format expected by Chatbot
return [[None, welcome_message]]
except Exception as e:
import traceback
print(f"Error in initialize_chat: {str(e)}")
print(f"Traceback: {traceback.format_exc()}")
error_message = "There was an error starting the chat. Please refresh the page and try again."
state.add_message("system", error_message)
return error_message
# Process a message and return a response
async def process_message(message):
"""Process a message and return a response (text only)."""
# NOTE: This function now primarily focuses on getting the agent's text response.
# UI component updates are handled in process_and_respond based on cached data.
try:
# Store user message in Zep memory if available
if zep:
print("Storing user message in Zep...")
await zep.memory.add(
session_id=state.session_id,
messages=[Message(role_type="user", content=message, role="user")]
)
# Add user message to state (for context, though Gradio manages history display)
# state.add_message("user", message)
# Process with the agent
print('Calling generate_response function...')
agent_response = generate_response(message, state.session_id)
print(f"Agent response received: {agent_response}")
# Always extract the text output
output = agent_response.get("output", "I apologize, I encountered an issue.")
# metadata = agent_response.get("metadata", {})
print(f"Extracted output: {output}")
# Add assistant response to state (for context)
# state.add_message("assistant", output)
# Store assistant's response in Zep memory if available
if zep:
print("Storing assistant response in Zep...")
await zep.memory.add(
session_id=state.session_id,
messages=[Message(role_type="assistant", content=output, role="assistant")]
)
print("Assistant response stored in Zep")
return output # Return only the text output
except Exception as e:
import traceback
print(f"Error in process_message: {str(e)}")
print(f"Traceback: {traceback.format_exc()}")
error_message = f"I'm sorry, there was an error processing your request: {str(e)}"
# state.add_message("assistant", error_message)
return error_message
# Function to handle user input in Gradio
def user_input(message, history):
"""Handle user input and update the chat history."""
# Check if this is the first message (initialization)
if not state.initialized:
# Initialize the chat session
asyncio.run(initialize_chat())
state.initialized = True
# Add the user message to the history
history.append({"role": "user", "content": message})
# Clear the input field
return "", history
# Function to generate bot response in Gradio
def bot_response(history):
"""Generate a response from the bot and update the chat history."""
# Get the last user message
user_message = history[-1]["content"]
# Process the message and get a response
response = asyncio.run(process_message(user_message))
# Add the bot response to the history
history.append({"role": "assistant", "content": response})
return history
# Create the Gradio interface
with gr.Blocks(title="49ers FanAI Hub", css=css) as demo:
gr.Markdown("# 🏈 49ers FanAI Hub")
# --- Component Display Area --- #
# REMOVED Unused/Redundant Component Placeholders:
# debug_textbox = gr.Textbox(label="Debug Player Data", visible=True, interactive=False)
# player_card_display = gr.HTML(visible=False)
# game_recap_display = gr.HTML(visible=False)
# Chat interface - Components will be added directly here
chatbot = gr.Chatbot(
# value=state.get_chat_history(), # Let Gradio manage history display directly
height=500,
show_label=False,
elem_id="chatbot",
type="tuples", # this triggers a deprecation warning but OK for now
render_markdown=True
)
# Input components
with gr.Row():
# Add persona selection radio button (Step 4) - initially doesn't do anything
persona_radio = gr.Radio(
choices=["Casual Fan", "Super Fan"],
value="Casual Fan", # Default to Casual Fan
label="Select Persona",
scale=3
)
msg = gr.Textbox(
placeholder="Ask me about the 49ers...",
show_label=False,
scale=6
)
submit_btn = gr.Button("Send", scale=1) # Renamed for clarity
# Feedback area for persona changes
persona_feedback = gr.Textbox(
label="Persona Status",
value="Current Persona: Casual Fan",
interactive=False
)
# Handle persona selection changes - Step 4 (skeleton only)
def on_persona_change(persona_choice):
"""Handle changes to the persona selection radio button"""
print(f"[UI EVENT] Persona selection changed to: {persona_choice}")
# Load session IDs from file
persona_ids = load_persona_session_ids()
# Verify the persona exists in our mapping
if persona_choice not in persona_ids:
print(f"[ERROR] Unknown persona selected: {persona_choice}")
return f"Error: Unknown persona '{persona_choice}'"
# Get the session ID for this persona
session_id = persona_ids[persona_choice]
print(f"[UI EVENT] Mapping {persona_choice} to session ID: {session_id}")
# Update the agent's session ID
feedback = set_memory_session_id(session_id, persona_choice)
# Return feedback to display in the UI
return feedback
# Set up persona change event listener
persona_radio.change(on_persona_change, inputs=[persona_radio], outputs=[persona_feedback])
# Define a combined function for user input and bot response
async def process_and_respond(message, history):
"""Process user input, get agent response, check for components, and update history."""
# --- Clear caches before processing --- #
print("Clearing tool data caches...")
player_search.LAST_PLAYER_DATA = []
game_recap.LAST_GAME_DATA = []
team_story.LAST_TEAM_STORY_DATA = []
# --- End cache clearing --- #
print(f"process_and_respond: Received message: {message}")
# history.append((message, None)) # Add user message placeholder
# yield "", history # Show user message immediately
# Call the agent to get the response (text output + potentially populates cached data)
agent_response = generate_response(message, state.session_id)
text_output = agent_response.get("output", "Sorry, something went wrong.")
metadata = agent_response.get("metadata", {})
tools_used = metadata.get("tools_used", ["None"])
print(f"process_and_respond: Agent text output: {text_output}")
print(f"process_and_respond: Tools used: {tools_used}")
# Initialize response list with the text output
response_list = [(message, text_output)]
# Check for specific component data based on tools used or cached data
# Important: Call the getter functions *after* generate_response has run
# Check for Player Card
player_data = get_last_player_data()
if player_data:
print(f"process_and_respond: Found player data: {player_data}")
player_card_component = create_player_card_component(player_data)
if player_card_component:
response_list.append((None, player_card_component))
print("process_and_respond: Added player card component.")
else:
print("process_and_respond: Player data found but component creation failed.")
# Check for Game Recap
game_data = get_last_game_data()
if game_data:
print(f"process_and_respond: Found game data: {game_data}")
game_recap_comp = create_game_recap_component(game_data)
if game_recap_comp:
response_list.append((None, game_recap_comp))
print("process_and_respond: Added game recap component.")
else:
print("process_and_respond: Game data found but component creation failed.")
# Check for Team Story --- NEW ---
team_story_data = get_last_team_story_data()
if team_story_data:
print(f"process_and_respond: Found team story data: {team_story_data}")
team_story_comp = create_team_story_component(team_story_data)
if team_story_comp:
response_list.append((None, team_story_comp))
print("process_and_respond: Added team story component.")
else:
print("process_and_respond: Team story data found but component creation failed.")
# Update history with all parts of the response (text + components)
# Gradio's Chatbot handles lists of (user, assistant) tuples,
# where assistant can be text or a Gradio component.
# We replace the last entry (user, None) with the actual response items.
# Gradio manages history display; we just return the latest exchange.
# The actual history state is managed elsewhere (e.g., Zep, Neo4j history)
# Return the combined response list to update the chatbot UI
# The first element is user message + assistant text response
# Subsequent elements are None + UI component
print(f"process_and_respond: Final response list for UI: {response_list}")
# Return values suitable for outputs: [msg, chatbot]
return "", response_list # Return empty string for msg, list for chatbot
# Set up event handlers with the combined function
# Ensure outputs list matches the return values of process_and_respond
# REMOVED redundant components from outputs_list
outputs_list = [msg, chatbot]
msg.submit(process_and_respond, [msg, chatbot], outputs_list)
submit_btn.click(process_and_respond, [msg, chatbot], outputs_list)
# Add a clear button
clear_btn = gr.Button("Clear Conversation")
# Clear function - now only needs to clear msg and chatbot
def clear_chat():
# Return empty values for msg and chatbot
return "", []
# Update clear outputs - only need msg and chatbot
clear_btn.click(clear_chat, None, [msg, chatbot])
# Trigger initialization function on app load
demo.load(initialize_chat, inputs=None, outputs=chatbot)
# Launch the app
if __name__ == "__main__":
demo.launch()