Spaces:
Runtime error
Runtime error
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() | |