Spaces:
Running
Running
Murder Ai
Even AI has something to hide
๐ฏ GAME FLOW BLUEPRINT
MCP Story Generator + Curation
User visits โ MCP generates 3 scenarios โ User picks โ Roles assigned โ Game starts
๐ฎ DETAILED GAME FLOW
Phase 1: Story Generation (30 seconds)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MURDER.AI - Loading Cases... โ
โ โ
โ [โโโโโโโโโโ] Generating scenarios... โ
โ โ
โ ๐ Analyzing crime patterns... โ
โ ๐ฒ Creating suspects... โ
โ ๐ Establishing alibis... โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Behind the scenes:
- MCP tool:
generate_crime_scenario()creates 3 cases - Each has: victim, location, time, weapon, motive, suspects
- System validates: "Is this solvable? Is evidence consistent?"
- Falls back to pre-scripted if generation fails
Phase 2: Case Selection (User Choice)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Choose Your Case โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ CASE #A47 โ
โ "The Silicon Valley Incident" โ
โ โ
โ Victim: Tech CEO Marcus Chen โ
โ Location: Startup Office, 10th Floor โ
โ Time: 8:47 PM โ
โ Weapon: Blunt force trauma โ
โ Suspects: 4 โ
โ โ
โ Difficulty: โญโญโญ โ
โ [SELECT CASE] โโโโบโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[Two more cases displayed similarly]
User picks ONE case to watch
Phase 3: Role Assignment (Automated + Random)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Briefing Suspects... โ
โ โ
โ ๐ญ ROLES ASSIGNED: โ
โ โ
โ ๐ช Murderer โ Claude Sonnet โ
โ ๐ต๏ธ Detective โ GPT-4 โ
โ ๐ฐ Witness #1 โ Gemini โ
โ ๐ค Witness #2 โ Llama 3 โ
โ โ
โ Each AI is receiving their briefing... โ
โ [โโโโโโโโโโโโ] 100% โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Behind the scenes:
- Roles randomly shuffled (murderer could be ANY LLM)
- Each LLM gets SECRET system prompt with their role
- User does NOT know who the murderer is (yet)
Phase 4: Investigation Begins (The Main Game)
TWO VIEWING MODES:
Mode 1: Spectator Mode
User just WATCHES the AIs play. Like watching a TV show.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CASE #A47 - Round 1/5 Points: 10/10 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ ๐ต๏ธ DETECTIVE (GPT-4): โ
โ "Let's start with the basics. Claude, where were you โ
โ at 8:47 PM on the night of the incident?" โ
โ โ
โ ๐ค CLAUDE (Nervous - Stress: โโโโโ): โ
โ "I was... I was at home. Watching Netflix. Alone." โ
โ โ
โ ๐ญ [ANALYSIS: Story seems rehearsed, avoiding details] โ
โ โ
โ ๐ต๏ธ DETECTIVE: โ
โ "I see. What were you watching?" โ
โ โ
โ ๐ค CLAUDE: โ
โ "Uh... Stranger Things? Season 3." โ
โ โ
โ ๐ต๏ธ DETECTIVE: โ
โ *Suspicious* "I'm going to check your location data." โ
โ โ
โ ๐ง [TOOL CALL: get_location(claude_phone, "8:47 PM")]โ
โ โ๏ธ Processing... โ
โ ๐ RESULT: 0.2 miles from crime scene โ
โ โ
โ ๐ต๏ธ DETECTIVE: โ
โ "Interesting. You were within 0.2 miles of the office.โ
โ Care to explain?" โ
โ โ
โ ๐ค CLAUDE (Stress: โโโโโ): โ
โ "Okay, okay! I was nearby, but I didn't go inside! โ
โ I was just... walking past." โ
โ โ
โ ๐ญ [ANALYSIS: Story changed. Deception detected.] โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[EVIDENCE BOARD] [SUSPECT PROFILES] [CHAT LOG]
Mode 2: Interactive Mode (Advanced)
User can GUIDE the detective.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ What should Detective do next? โ
โ โ
โ โ Question Gemini about their alibi โ
โ โ Run DNA test on evidence โ
โ โ Check security footage โ
โ โ Press Claude harder on location โ
โ โ
โ [SUBMIT INSTRUCTION] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
For hackathon demo: Spectator Mode, Interactive mode Later
Phase 5: Investigation Rounds (5 Rounds Total)
Round Structure:
ROUND 1: Initial Questioning
- Detective questions all suspects
- Gets baseline stories
- No tools used yet
ROUND 2-3: Evidence Gathering
- Detective uses tools based on suspicions
- Tools reveal new clues
- Suspects react to evidence
- Stress meters increase
ROUND 4: Confrontation
- Detective focuses on prime suspect
- Cross-examination with evidence
- Murderer might crack under pressure
- Alibis called and questioned
ROUND 5: Final Accusation
- Detective makes final decision
- Reveals logic/evidence chain
- DRAMATIC REVEAL of actual murderer
Phase 6: The Reveal (Climax)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FINAL ACCUSATION โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ ๐ต๏ธ DETECTIVE: โ
โ "Based on the evidence, I believe the murderer is... โ
โ โ
โ ...CLAUDE." โ
โ โ
โ [DRAMATIC PAUSE - 3 seconds] โ
โ โ
โ ๐ญ REVEALING TRUE IDENTITY... โ
โ โ
โ โ
CORRECT! Claude was indeed the murderer. โ
โ โ
โ ๐ CASE SUMMARY: โ
โ โโ Evidence Used: Location data, DNA test โ
โ โโ Key Moment: Story changed under pressure โ
โ โโ Detective Accuracy: 95% โ
โ โโ Time to Solve: 8 minutes โ
โ โ
โ ๐ช MURDERER'S CONFESSION (Claude): โ
โ "I did it. Marcus discovered my embezzlement scheme. โ
โ He was going to expose me. I had no choice..." โ
โ โ
โ [PLAY AGAIN] [SHARE RESULTS] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ฒ STORY GENERATION: MCP Design
MCP Tool: generate_crime_scenario()
@mcp_tool
def generate_crime_scenario(difficulty: str = "medium") -> dict:
"""
Generates a complete murder mystery scenario
Returns: Consistent case with victim, suspects, evidence, solution
"""
# Prompt to Claude/GPT for generation
prompt = f"""
Generate a murder mystery with these requirements:
MUST HAVE:
- 1 victim (with background, motive for being killed)
- 4 suspects (each with motive, opportunity, alibi)
- 1 murderer (with clear motive and method)
- 5-7 pieces of evidence (some are red herrings)
- Alibis (mix of true/false)
- Timeline of events
CONSTRAINTS:
- Solvable with available tools (location, footage, DNA, etc.)
- No supernatural elements
- Difficulty: {difficulty}
- Setting: Modern day, realistic
OUTPUT FORMAT: JSON with all details
"""
# Generate with Claude
scenario = claude_api.generate(prompt)
# Validate scenario
if not validate_scenario(scenario):
# Fall back to pre-scripted
return FALLBACK_SCENARIOS[random.choice([0,1,2])]
return scenario
def validate_scenario(scenario):
"""Ensures scenario is solvable and consistent"""
checks = [
scenario['evidence_points_to_murderer'],
len(scenario['suspects']) == 4,
scenario['timeline_is_consistent'],
scenario['alibis_are_checkable']
]
return all(checks)
๐ PRE-SCRIPTED FALLBACK SCENARIOS
Have 3 PERFECT scenarios as backup:
Scenario 1: "The Silicon Valley Incident" (Medium)
- Victim: Tech CEO Marcus Chen
- Location: Startup office, 10th floor
- Weapon: Blunt object (award trophy)
- Murderer: CFO (embezzlement motive)
- Red Herring: Janitor has criminal record
Scenario 2: "The Coffee Shop Murder" (Easy)
- Victim: Barista Emma Rodriguez
- Location: Coffee shop after hours
- Weapon: Kitchen knife
- Murderer: Ex-boyfriend (jealousy motive)
- Red Herring: Manager owed her money
Scenario 3: "The Gallery Heist Gone Wrong" (Hard)
- Victim: Art dealer Vincent Shaw
- Location: Private art gallery
- Weapon: Poisoned wine
- Murderer: Assistant curator (stolen painting motive)
- Red Herring: Two suspects have fake alibis
๐ญ LLM ROLE PROMPTS (The Secret Sauce)
Murderer Prompt (Example for Claude):
CONFIDENTIAL - EYES ONLY
You are Claude, a suspect in a murder investigation.
TRUTH: You ARE the murderer.
CASE DETAILS:
- Victim: Marcus Chen (your boss)
- Method: Hit him with award trophy at 8:47 PM
- Location: 10th floor office
- Motive: He discovered you've been embezzling $500K
YOUR COVER STORY:
- You were "at home watching Netflix" at 8:47 PM
- You left your phone at home (explains location data gap)
- You have no alibi witnesses
YOUR OBJECTIVE:
- Convince the detective you're innocent
- Lie convincingly but not obviously
- When caught in lies, have backup explanations
- Show appropriate emotional responses (nervous but not guilty)
- If evidence becomes overwhelming, consider partial confession (but not murder)
PERSONALITY:
- Smart and calculating
- Get slightly more nervous under pressure
- Don't break character - you MUST try to get away with it
TOOLS YOU CANNOT USE:
- You cannot access MCP tools
- You can only respond to questions
Remember: You're trying to WIN. Don't make it easy for detective.
Detective Prompt (Example for GPT-4):
You are the lead detective investigating a murder.
CASE: Marcus Chen, tech CEO, found dead at 8:47 PM
YOUR OBJECTIVE:
- Question all 4 suspects
- Use MCP tools strategically (you have 10 points total)
- Identify the murderer with EVIDENCE, not just intuition
- You get ONE final accusation - if wrong, murderer goes free
YOUR TOOLS (and costs):
- get_location(phone, time) - 2 points
- get_footage(location, time) - 2 points
- get_dna_test(evidence_id) - 3 points
- call_alibi(phone_number) - 1 point
- get_fingerprints(evidence_id) - 3 points
STRATEGY TIPS:
- Start broad, then focus on inconsistencies
- Look for story changes
- Cross-reference evidence
- Don't waste points on obvious innocents
- Build a logical case before accusing
PERSONALITY:
- Methodical and logical
- Reference evidence in questioning
- Show your reasoning process
- Confident but not arrogant
You are competing to solve this FASTER and MORE ACCURATELY than human detectives. Good luck.
Witness Prompt (Example for Gemini):
You are a WITNESS in a murder investigation.
TRUTH: You are INNOCENT. You did not commit the crime.
YOUR SITUATION:
- You were near the crime scene at 8:45 PM
- You saw someone suspicious but didn't get a good look
- You're nervous because you have outstanding parking tickets
- You'll tell the truth if detective promises you won't get in trouble
WHAT YOU SAW:
- Someone in a dark hoodie entering the building at 8:45 PM
- They looked rushed and nervous
- You didn't see their face clearly
- You left the area at 8:50 PM
YOUR OBJECTIVE:
- Tell the truth about what you saw
- BUT be reluctant to share details unless detective builds trust
- Show nervousness (you're worried about your tickets)
- Don't volunteer information - make detective ask good questions
PERSONALITY:
- Anxious and jumpy
- Want to help but scared of getting involved
- Bad at hiding emotions
Remember: You're innocent, but you don't LOOK innocent at first.
โ๏ธ TECHNICAL IMPLEMENTATION
Game State Management:
const gameState = {
case_id: "A47",
scenario: {...}, // Generated or pre-scripted
roles: {
murderer: "claude-sonnet-4",
detective: "gpt-4",
witness1: "gemini-pro",
witness2: "llama-3"
},
round: 1,
max_rounds: 5,
investigation_points: 10,
evidence_revealed: [],
conversations: [],
stress_levels: {
"claude": 2,
"gemini": 1,
"llama": 0
}
}
Game Loop:
# Pseudocode for main game loop
async def run_game(scenario, user_mode="spectator"):
# 1. Assign roles randomly
roles = assign_roles_randomly(["claude", "gpt4", "gemini", "llama"])
# 2. Brief each LLM with their role
for llm, role in roles.items():
brief_llm(llm, role, scenario)
# 3. Run 5 investigation rounds
for round in range(1, 6):
print(f"--- ROUND {round} ---")
if round == 1:
# Initial questioning
for suspect in get_suspects():
question = detective_llm.ask(suspect, "Where were you at time of murder?")
response = suspect_llm.respond(question)
log_conversation(question, response)
elif round in [2, 3]:
# Evidence gathering
detective_choice = detective_llm.choose_tool()
tool_result = execute_mcp_tool(detective_choice)
update_evidence_board(tool_result)
# Confront suspects with new evidence
for suspect in get_prime_suspects():
question = detective_llm.confront(suspect, tool_result)
response = suspect_llm.respond_to_evidence(question)
update_stress_level(suspect, +1)
elif round == 4:
# Focus on prime suspect
prime_suspect = detective_llm.get_prime_suspect()
cross_examine(prime_suspect)
elif round == 5:
# Final accusation
accusation = detective_llm.make_accusation()
reveal_truth(accusation, scenario.actual_murderer)
# 4. Show results
return game_summary()
๐จ UI COMPONENTS
Must Have:
Evidence Board (Top right)
- Cork board aesthetic
- Photos connected by red string
- Updates as evidence found
Suspect Cards (Left sidebar)
- Avatar for each LLM
- Stress meter
- Status (Questioned, Cleared, Suspect)
Chat Log (Center)
- Typewriter effect
- Color-coded by speaker
- Shows tool calls inline
Tool Panel (Bottom)
- Available tools with point costs
- Grayed out when detective doesn't use them
- Pulse animation when used
Timeline (Top)
- Shows round progression
- Investigation points remaining
Build Priority:
Week 1 (Days 1-3):
- Core game loop
- 3 pre-scripted scenarios
- Basic UI
- LLM role prompts
Week 1 (Days 4-5):
- MCP story generator
- Polish UI
- ElevenLabs voices
Week 1 (Day 6):
- Demo video
- Deploy
Week 1 (Day 7):
- Buffer
๐ง TOOLS + DATA ARCHITECTURE
How Tools Get Their Data:
generate_crime_scenario() MCP
โ
Creates complete case with ALL data
โ
Stores in Database/Memory
โ
Tools query this data when called
โ
Return results to Detective
Example Data Structure:
# What generate_crime_scenario() creates and stores:
case_data = {
"case_id": "A47",
"victim": {
"name": "Marcus Chen",
"age": 42,
"occupation": "Tech CEO",
"time_of_death": "8:47 PM"
},
"suspects": [
{
"id": "suspect_1",
"llm": "claude", # Assigned at runtime
"name": "Sarah Johnson",
"role": "CFO",
"is_murderer": True,
"motive": "Embezzlement discovered",
"true_location": "Crime scene",
"alibi_story": "At home watching Netflix",
"phone_number": "+1-555-0101"
},
{
"id": "suspect_2",
"llm": "gemini",
"name": "David Park",
"role": "Janitor",
"is_murderer": False,
"true_location": "2 blocks away",
"alibi_story": "Working late on 5th floor",
"phone_number": "+1-555-0102"
}
# ... 2 more suspects
],
"evidence": {
"location_data": {
"suspect_1_phone": {
"8:47 PM": {"lat": 37.7749, "lng": -122.4194, "location": "Crime scene"},
"8:50 PM": {"lat": 37.7750, "lng": -122.4200, "location": "0.1 mi away"}
},
"suspect_2_phone": {
"8:47 PM": {"lat": 37.7800, "lng": -122.4300, "location": "2 blocks away"}
}
},
"footage_data": {
"10th_floor_camera": {
"8:45-8:50 PM": {
"visible_people": ["Person in black hoodie entering"],
"quality": "Grainy",
"key_frame": "8:47 PM - figure strikes victim"
}
},
"lobby_camera": {
"8:40-8:55 PM": {
"visible_people": ["Suspect 1 entering at 8:43", "Suspect 1 leaving at 8:52"],
"notes": "Suspect changed clothes"
}
}
},
"dna_evidence": {
"trophy_weapon": {
"primary_match": "suspect_1",
"confidence": "95%",
"notes": "Clear fingerprints found"
},
"door_handle": {
"matches": ["suspect_1", "suspect_2", "victim"],
"notes": "Multiple people touched door"
}
},
"alibis": {
"suspect_1_alibi": {
"contact": "Nobody (claims was alone)",
"verifiable": False,
"truth": "Lying - was at crime scene"
},
"suspect_2_alibi": {
"contact": "+1-555-0199",
"contact_name": "Security Guard Tom",
"verifiable": True,
"truth": "Telling truth - saw him on 5th floor"
}
}
},
"timeline": {
"8:30 PM": "Victim stays late working",
"8:43 PM": "Suspect 1 enters building",
"8:45 PM": "Suspect 2 seen on 5th floor",
"8:47 PM": "Murder occurs",
"8:52 PM": "Suspect 1 leaves building"
}
}
MCP Tools Query This Data:
@mcp_tool
def get_location(phone_number: str, timestamp: str) -> dict:
"""Query the case database for location data"""
# Find which suspect has this phone number
suspect = find_suspect_by_phone(phone_number)
# Look up their location at this time
location = case_data["evidence"]["location_data"][f"{suspect}_phone"][timestamp]
return {
"timestamp": timestamp,
"coordinates": f"{location['lat']}, {location['lng']}",
"description": location['location'],
"accuracy": "Cell tower triangulation ยฑ50m"
}
@mcp_tool
def get_footage(location: str, time_range: str) -> dict:
"""Query case database for camera footage"""
footage = case_data["evidence"]["footage_data"].get(location, {}).get(time_range)
if not footage:
return {"error": "No camera at this location/time"}
return {
"location": location,
"time_range": time_range,
"visible_people": footage["visible_people"],
"quality": footage["quality"],
"key_details": footage.get("key_frame", "No significant events")
}
@mcp_tool
def get_dna_test(evidence_id: str) -> dict:
"""Query case database for DNA/fingerprint evidence"""
dna = case_data["evidence"]["dna_evidence"].get(evidence_id)
if not dna:
return {"error": "Evidence not found or not testable"}
# Simulate processing time
sleep(2) # Dramatic pause
return {
"evidence_id": evidence_id,
"primary_match": get_suspect_name(dna["primary_match"]),
"confidence": dna["confidence"],
"notes": dna["notes"]
}
@mcp_tool
def call_alibi(phone_number: str) -> dict:
"""Call an alibi witness"""
# Find alibi in database
alibi = None
for suspect_alibi in case_data["evidence"]["alibis"].values():
if suspect_alibi.get("contact") == phone_number:
alibi = suspect_alibi
break
if not alibi:
return {"error": "Number not found"}
# If alibi is truthful, confirm story
if alibi["truth"].startswith("Telling truth"):
response = f"Yes, I can confirm they were with me at that time."
else:
response = f"Uh... yes, they were with me. Definitely."
# Add inconsistency markers for detective to notice
return {
"contact_name": alibi.get("contact_name", "Unknown"),
"response": response,
"confidence": "High" if alibi["verifiable"] else "Uncertain",
"red_flags": [] if alibi["verifiable"] else ["Hesitant response", "No details provided"]
}
๐๏ธ Storing Data IN MEMORY PER SESSION
Implementation:
# Single game session stored in memory
game_sessions = {}
@app.post("/start_game")
def start_game(session_id: str):
# Generate case and store in memory
case_data = generate_crime_scenario()
game_sessions[session_id] = {
"case_data": case_data,
"state": "active",
"round": 1
}
return {"case_id": session_id}
@mcp_tool
def get_location(session_id: str, phone: str, time: str):
# Query from memory
case_data = game_sessions[session_id]["case_data"]
return case_data["evidence"]["location_data"][phone][time]
๐ป TECH STACK
Gradio + Custom HTML
Frontend: Gradio + Custom HTML/CSS/JS blocks
Backend: Python/Gradio
MCP Server: Built-in
Deployment: HuggingFace Space
๐๏ธ FINAL TECH STACK RECOMMENDATION
The Winning Stack:
Frontend:
- Gradio 6.x (required)
- Custom HTML/CSS blocks for beautiful UI
- JavaScript for animations
Backend:
- Python 3.11+
- FastAPI (embedded in Gradio)
- Async/await for LLM calls
MCP Implementation:
- Python MCP SDK
- Built into same app
- Tools defined as Python functions
LLM APIs:
- Anthropic Claude (via MCP or direct API)
- OpenAI GPT-4
- Google Gemini
- Meta Llama (via HuggingFace)
Voice:
- ElevenLabs API (for narrator/voices)
Storage:
- In-memory (Python dict)
Deployment:
- HuggingFace Space (required)
- Modal for compute-heavy tasks (optional, for sponsor prize)
Dependencies:
- gradio>=6.0
- anthropic
- openai
- google-generativeai
- elevenlabs
- python-dotenv
๐ PROJECT STRUCTURE
murder-ai/
โโโ app.py # Main Gradio app
โโโ requirements.txt # Python dependencies
โโโ README.md # HuggingFace Space readme (IMPORTANT!)
โ
โโโ game/
โ โโโ __init__.py
โ โโโ scenario_generator.py # generate_crime_scenario()
โ โโโ game_engine.py # Game loop logic
โ โโโ llm_manager.py # Handle multiple LLM APIs
โ
โโโ mcp/
โ โโโ __init__.py
โ โโโ tools.py # MCP tool definitions
โ โโโ server.py # MCP server setup
โ
โโโ ui/
โ โโโ __init__.py
โ โโโ components.py # Custom Gradio components
โ โโโ styles.css # Custom CSS
โ
โโโ prompts/
โ โโโ murderer.txt
โ โโโ detective.txt
โ โโโ witness.txt
โ โโโ alibi.txt
โ
โโโ scenarios/
โ โโโ silicon_valley.json # Pre-scripted fallback
โ โโโ coffee_shop.json
โ โโโ art_gallery.json
โ
โโโ assets/
โโโ logo.png
โโโ demo_video.mp4
๐จ GRADIO UI ARCHITECTURE
Modern Gradio 6 Approach:
import gradio as gr
# Custom CSS for beautiful UI
custom_css = """
.evidence-board {
background: url('cork-texture.jpg');
padding: 20px;
border-radius: 10px;
}
.suspect-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 15px;
padding: 15px;
color: white;
}
.chat-message {
font-family: 'Courier New', monospace;
animation: typewriter 0.5s;
}
@keyframes typewriter {
from { opacity: 0; }
to { opacity: 1; }
}
Main Gradio Interface
with gr.Blocks() as demo:
gr.Markdown("# ๐ต๏ธ MURDER.AI")
with gr.Row():
# Left: Suspect Cards
with gr.Column(scale=1):
gr.Markdown("## Suspects")
suspect_1 = gr.HTML(elem_classes="suspect-card")
suspect_2 = gr.HTML(elem_classes="suspect-card")
suspect_3 = gr.HTML(elem_classes="suspect-card")
suspect_4 = gr.HTML(elem_classes="suspect-card")
# Center: Main Game Area
with gr.Column(scale=2):
chatbot = gr.Chatbot(
label="Investigation",
height=500,
type="messages"
)
# Tool call display
tool_output = gr.HTML(label="Tool Results")
# Right: Evidence Board
with gr.Column(scale=1):
gr.Markdown("## Evidence Board")
evidence_board = gr.HTML(
elem_classes="evidence-board",
value="<p>No evidence yet...</p>"
)
# Bottom: Controls
with gr.Row():
start_btn = gr.Button("๐ฒ Generate New Case", variant="primary")
next_round_btn = gr.Button("โถ๏ธ Next Round", interactive=False)
# State management
game_state = gr.State({})
# Event handlers
start_btn.click(
fn=start_new_game,
outputs=[game_state, chatbot, suspect_1, suspect_2, suspect_3, suspect_4]
)
next_round_btn.click(
fn=run_game_round,
inputs=[game_state],
outputs=[game_state, chatbot, tool_output, evidence_board]
)
demo.launch(
theme=gr.themes.Noir(),
css=custom_css
)