Spaces:
Running
Running
Update TODO and enhance game UI with tool showcase and video demo
Browse files
TODO.md
CHANGED
|
@@ -11,11 +11,12 @@
|
|
| 11 |
- add ai
|
| 12 |
- mode selection
|
| 13 |
- numbered steps to launch game
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
-
# Doing
|
| 17 |
|
|
|
|
|
|
|
| 18 |
- sound/music
|
| 19 |
-
- video embeded
|
| 20 |
-
- divider
|
| 21 |
-
- mcp showcase with title "try tools without playing"
|
|
|
|
| 11 |
- add ai
|
| 12 |
- mode selection
|
| 13 |
- numbered steps to launch game
|
| 14 |
+
- video embeded
|
| 15 |
+
- divider
|
| 16 |
+
- mcp showcase with title "try tools without playing"
|
| 17 |
|
| 18 |
|
|
|
|
| 19 |
|
| 20 |
+
# Doing
|
| 21 |
+
- README
|
| 22 |
- sound/music
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -434,11 +434,19 @@ def start_game_from_ui(case_name, mode, voice):
|
|
| 434 |
|
| 435 |
init_data = session.start(difficulty, mode_slug, voice)
|
| 436 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
# Return visible updates
|
| 438 |
return (
|
| 439 |
gr.update(visible=False), # Hide selector row
|
| 440 |
gr.update(visible=True), # Show game frame
|
| 441 |
-
json.dumps(init_data)
|
|
|
|
|
|
|
|
|
|
| 442 |
)
|
| 443 |
|
| 444 |
css = """
|
|
@@ -505,11 +513,96 @@ with gr.Blocks(title="Murder.Ai", fill_height=True) as demo:
|
|
| 505 |
|
| 506 |
auto_refresh.change(fn=toggle_timer, inputs=auto_refresh, outputs=log_timer)
|
| 507 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 508 |
# Start Game Event
|
| 509 |
start_btn.click(
|
| 510 |
fn=start_game_from_ui,
|
| 511 |
inputs=[case_dropdown, game_mode, voice_toggle],
|
| 512 |
-
outputs=[setup_col, game_group, bridge_output]
|
| 513 |
)
|
| 514 |
|
| 515 |
# Bridge Logic with Logging (Legacy/Fallback)
|
|
|
|
| 434 |
|
| 435 |
init_data = session.start(difficulty, mode_slug, voice)
|
| 436 |
|
| 437 |
+
# Extract data for tools
|
| 438 |
+
phones = [s["phone_number"] for s in init_data["data"]["scenario"]["suspects"]]
|
| 439 |
+
cameras = init_data["data"]["available_cameras"]
|
| 440 |
+
suspects = [s["name"] for s in init_data["data"]["scenario"]["suspects"]]
|
| 441 |
+
|
| 442 |
# Return visible updates
|
| 443 |
return (
|
| 444 |
gr.update(visible=False), # Hide selector row
|
| 445 |
gr.update(visible=True), # Show game frame
|
| 446 |
+
json.dumps(init_data), # Send init data to bridge
|
| 447 |
+
gr.update(choices=phones, value=phones[0] if phones else None),
|
| 448 |
+
gr.update(choices=cameras, value=cameras[0] if cameras else None),
|
| 449 |
+
gr.update(choices=suspects, value=suspects[0] if suspects else None)
|
| 450 |
)
|
| 451 |
|
| 452 |
css = """
|
|
|
|
| 513 |
|
| 514 |
auto_refresh.change(fn=toggle_timer, inputs=auto_refresh, outputs=log_timer)
|
| 515 |
|
| 516 |
+
# --- Footer / Showcase ---
|
| 517 |
+
gr.Markdown("---")
|
| 518 |
+
gr.Markdown("### π₯ How to Play (Demo)")
|
| 519 |
+
gr.HTML('<iframe width="100%" height="400" src="https://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>')
|
| 520 |
+
|
| 521 |
+
gr.Markdown("# π οΈ Try MCP Tools Directly")
|
| 522 |
+
gr.Markdown("*Note: Start a game first to populate these tools.*")
|
| 523 |
+
|
| 524 |
+
with gr.Tabs():
|
| 525 |
+
with gr.Tab("π Location"):
|
| 526 |
+
loc_phone = gr.Radio(label="Select Phone Number", choices=[], interactive=True)
|
| 527 |
+
loc_btn = gr.Button("Get Location")
|
| 528 |
+
loc_out = gr.JSON(label="Result")
|
| 529 |
+
|
| 530 |
+
with gr.Tab("πΉ Footage"):
|
| 531 |
+
foot_cam = gr.Radio(label="Select Camera", choices=[], interactive=True)
|
| 532 |
+
foot_btn = gr.Button("Get Footage")
|
| 533 |
+
foot_out = gr.JSON(label="Result")
|
| 534 |
+
|
| 535 |
+
with gr.Tab("𧬠DNA"):
|
| 536 |
+
dna_id = gr.Textbox(label="Evidence ID")
|
| 537 |
+
dna_btn = gr.Button("Test DNA")
|
| 538 |
+
dna_out = gr.JSON(label="Result")
|
| 539 |
+
|
| 540 |
+
with gr.Tab("π Alibi"):
|
| 541 |
+
alibi_id = gr.Textbox(label="Alibi ID")
|
| 542 |
+
alibi_q = gr.Textbox(label="Question", value="Where were they?")
|
| 543 |
+
alibi_btn = gr.Button("Call Alibi")
|
| 544 |
+
alibi_out = gr.JSON(label="Result")
|
| 545 |
+
|
| 546 |
+
with gr.Tab("π¬ Interrogate"):
|
| 547 |
+
int_suspect = gr.Radio(label="Select Suspect", choices=[], interactive=True)
|
| 548 |
+
int_q = gr.Textbox(label="Question", value="Where were you?")
|
| 549 |
+
int_btn = gr.Button("Interrogate")
|
| 550 |
+
int_out_text = gr.Markdown(label="Response")
|
| 551 |
+
int_out_audio = gr.Audio(label="Voice Response", autoplay=True)
|
| 552 |
+
|
| 553 |
+
# --- Event Handlers for Tools ---
|
| 554 |
+
|
| 555 |
+
def wrap_tool(tool_name, *args):
|
| 556 |
+
if not session.game: return {"error": "Start game first"}
|
| 557 |
+
|
| 558 |
+
kwargs = {}
|
| 559 |
+
if tool_name == "get_location": kwargs = {"phone_number": args[0]}
|
| 560 |
+
elif tool_name == "get_footage": kwargs = {"location": args[0]}
|
| 561 |
+
elif tool_name == "get_dna_test": kwargs = {"evidence_id": args[0]}
|
| 562 |
+
elif tool_name == "call_alibi": kwargs = {"alibi_id": args[0], "question": args[1]}
|
| 563 |
+
|
| 564 |
+
return session.game.use_tool(tool_name, **kwargs)
|
| 565 |
+
|
| 566 |
+
loc_btn.click(lambda p: wrap_tool("get_location", p), inputs=loc_phone, outputs=loc_out)
|
| 567 |
+
foot_btn.click(lambda c: wrap_tool("get_footage", c), inputs=foot_cam, outputs=foot_out)
|
| 568 |
+
dna_btn.click(lambda e: wrap_tool("get_dna_test", e), inputs=dna_id, outputs=dna_out)
|
| 569 |
+
alibi_btn.click(lambda i, q: wrap_tool("call_alibi", i, q), inputs=[alibi_id, alibi_q], outputs=alibi_out)
|
| 570 |
+
|
| 571 |
+
def wrap_chat(suspect_name, question):
|
| 572 |
+
if not session.game: return "Start game first", None
|
| 573 |
+
# Find ID from name
|
| 574 |
+
s_id = next((s["id"] for s in session.game.scenario["suspects"] if s["name"] == suspect_name), None)
|
| 575 |
+
if not s_id: return "Suspect not found", None
|
| 576 |
+
|
| 577 |
+
resp = session.game.question_suspect(s_id, question)
|
| 578 |
+
|
| 579 |
+
# Audio
|
| 580 |
+
audio_path = None
|
| 581 |
+
suspect = next((s for s in session.game.scenario["suspects"] if s["id"] == s_id), None)
|
| 582 |
+
|
| 583 |
+
# Clean text
|
| 584 |
+
cleaned = resp
|
| 585 |
+
if cleaned.strip().startswith('(') and ')' in cleaned:
|
| 586 |
+
cleaned = cleaned[cleaned.find(')')+1:].strip()
|
| 587 |
+
cleaned = cleaned.replace('*', '').strip()
|
| 588 |
+
|
| 589 |
+
if suspect and "voice_id" in suspect and cleaned:
|
| 590 |
+
audio_bytes = session.game.voice_manager.generate_audio(cleaned, suspect["voice_id"])
|
| 591 |
+
if audio_bytes:
|
| 592 |
+
import tempfile
|
| 593 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as fp:
|
| 594 |
+
fp.write(audio_bytes)
|
| 595 |
+
audio_path = fp.name
|
| 596 |
+
|
| 597 |
+
return resp, audio_path
|
| 598 |
+
|
| 599 |
+
int_btn.click(wrap_chat, inputs=[int_suspect, int_q], outputs=[int_out_text, int_out_audio])
|
| 600 |
+
|
| 601 |
# Start Game Event
|
| 602 |
start_btn.click(
|
| 603 |
fn=start_game_from_ui,
|
| 604 |
inputs=[case_dropdown, game_mode, voice_toggle],
|
| 605 |
+
outputs=[setup_col, game_group, bridge_output, loc_phone, foot_cam, int_suspect]
|
| 606 |
)
|
| 607 |
|
| 608 |
# Bridge Logic with Logging (Legacy/Fallback)
|