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() | |