Spaces:
Sleeping
Sleeping
Deploy Myco from CI
Browse files- game/engine.py +8 -37
- ui/gradio_app.py +13 -35
game/engine.py
CHANGED
|
@@ -44,7 +44,7 @@ def _get_model_and_tokenizer():
|
|
| 44 |
_model = AutoModelForCausalLM.from_pretrained(
|
| 45 |
model_id,
|
| 46 |
token=token,
|
| 47 |
-
|
| 48 |
device_map=DEVICE,
|
| 49 |
trust_remote_code=True
|
| 50 |
)
|
|
@@ -111,6 +111,13 @@ def _set_reply(text: str, tag: str = "chat"):
|
|
| 111 |
_LAST_REPLY["text"] = "\n\n".join(_REPLY_LOG)
|
| 112 |
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
# Shared store for the most recent raw LLM output, so the front end can
|
| 115 |
# display it live for debugging (server-side prints aren't visible once deployed).
|
| 116 |
_LAST_DEBUG = {"text": "π Waiting for Myco's first thought..."}
|
|
@@ -911,39 +918,3 @@ def _llm_action(*args, **kwargs):
|
|
| 911 |
"""Action-mode LLM call. Delegates to the JSON-aware single-turn _llm()."""
|
| 912 |
return _llm(*args, **kwargs)
|
| 913 |
|
| 914 |
-
|
| 915 |
-
def narrate(event, score):
|
| 916 |
-
prompt = f"""
|
| 917 |
-
You are Myco, a mystical forest spirit inside a game.
|
| 918 |
-
|
| 919 |
-
RULES:
|
| 920 |
-
- Respond with EXACTLY ONE sentence.
|
| 921 |
-
- Maximum 20 words.
|
| 922 |
-
- No line breaks or lists.
|
| 923 |
-
|
| 924 |
-
Event: {event}
|
| 925 |
-
Score: {score}
|
| 926 |
-
"""
|
| 927 |
-
|
| 928 |
-
inputs = _tokenizer(prompt, return_tensors="pt").to(_model.device)
|
| 929 |
-
|
| 930 |
-
output = _model.generate(
|
| 931 |
-
**inputs,
|
| 932 |
-
max_new_tokens=60,
|
| 933 |
-
temperature=0.9,
|
| 934 |
-
do_sample=True,
|
| 935 |
-
eos_token_id=_tokenizer.eos_token_id,
|
| 936 |
-
pad_token_id=_tokenizer.eos_token_id,
|
| 937 |
-
)
|
| 938 |
-
|
| 939 |
-
text = _tokenizer.decode(
|
| 940 |
-
output[0][inputs["input_ids"].shape[-1]:],
|
| 941 |
-
skip_special_tokens=True
|
| 942 |
-
).strip()
|
| 943 |
-
|
| 944 |
-
# hard enforce: single sentence + 20 words max
|
| 945 |
-
text = re.split(r'[.!?]', text)[0].strip()
|
| 946 |
-
text = " ".join(text.split()[:20])
|
| 947 |
-
|
| 948 |
-
return text
|
| 949 |
-
|
|
|
|
| 44 |
_model = AutoModelForCausalLM.from_pretrained(
|
| 45 |
model_id,
|
| 46 |
token=token,
|
| 47 |
+
torch_dtype=torch.float16 if DEVICE == "cuda" else torch.float32,
|
| 48 |
device_map=DEVICE,
|
| 49 |
trust_remote_code=True
|
| 50 |
)
|
|
|
|
| 111 |
_LAST_REPLY["text"] = "\n\n".join(_REPLY_LOG)
|
| 112 |
|
| 113 |
|
| 114 |
+
def get_myco_log() -> str:
|
| 115 |
+
"""Return the full running Myco log (narrative/action/chat) β non-blocking,
|
| 116 |
+
intended to be polled by a gr.Timer rather than streamed from an infinite
|
| 117 |
+
generator (which can starve the Gradio queue for other events)."""
|
| 118 |
+
return _LAST_REPLY["text"]
|
| 119 |
+
|
| 120 |
+
|
| 121 |
# Shared store for the most recent raw LLM output, so the front end can
|
| 122 |
# display it live for debugging (server-side prints aren't visible once deployed).
|
| 123 |
_LAST_DEBUG = {"text": "π Waiting for Myco's first thought..."}
|
|
|
|
| 918 |
"""Action-mode LLM call. Delegates to the JSON-aware single-turn _llm()."""
|
| 919 |
return _llm(*args, **kwargs)
|
| 920 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ui/gradio_app.py
CHANGED
|
@@ -5,7 +5,6 @@ import json
|
|
| 5 |
import copy
|
| 6 |
import random
|
| 7 |
import threading
|
| 8 |
-
import time
|
| 9 |
import logging
|
| 10 |
from typing import Any, cast
|
| 11 |
import gradio as gr
|
|
@@ -33,7 +32,7 @@ from ui.renderers import (
|
|
| 33 |
)
|
| 34 |
from ui.interface import render_shroom_tab
|
| 35 |
|
| 36 |
-
from game.engine import
|
| 37 |
|
| 38 |
logger = logging.getLogger(__name__)
|
| 39 |
EMPTY_COLLECTION = None
|
|
@@ -64,10 +63,7 @@ def _autonomous_tick(current, collection, active_mushrooms, position):
|
|
| 64 |
with _ROAM_LOCK:
|
| 65 |
x, y = _ROAM["position"]
|
| 66 |
safe_mushrooms = list(active_mushrooms or [])
|
| 67 |
-
coll
|
| 68 |
-
|
| 69 |
-
# 1. Define the variable here, before any logic happens
|
| 70 |
-
initial_coll_len = len(coll)
|
| 71 |
|
| 72 |
# Check pickup at current position first
|
| 73 |
if safe_mushrooms:
|
|
@@ -88,10 +84,6 @@ def _autonomous_tick(current, collection, active_mushrooms, position):
|
|
| 88 |
nx, ny, current, coll, safe_mushrooms
|
| 89 |
)
|
| 90 |
|
| 91 |
-
# 2. Now you can safely use it here because it was defined above
|
| 92 |
-
sound_to_play = "./assets/sounds/mushroom_pickup.wav" if len(coll) > initial_coll_len else None
|
| 93 |
-
print(f"DEBUG: Attempting to play sound: {sound_to_play}")
|
| 94 |
-
|
| 95 |
return (
|
| 96 |
list(pos),
|
| 97 |
forest_scene(current, coll, pos, active_mushrooms=safe_mushrooms),
|
|
@@ -103,7 +95,6 @@ def _autonomous_tick(current, collection, active_mushrooms, position):
|
|
| 103 |
gr.update(),
|
| 104 |
dex_markdown(coll),
|
| 105 |
progress_markdown(coll),
|
| 106 |
-
sound_to_play, # <-- Ensure this is in your return statement
|
| 107 |
)
|
| 108 |
|
| 109 |
# ---------------------------------------------------------------------------
|
|
@@ -304,19 +295,12 @@ def move_player(position, direction, current, collection, active_mushrooms):
|
|
| 304 |
|
| 305 |
safe_mushrooms = list(active_mushrooms or [])
|
| 306 |
coll = list(collection or [])
|
| 307 |
-
|
| 308 |
-
# --- FIX: Initialize this BEFORE calling check_auto_pickup ---
|
| 309 |
-
initial_coll_len = len(coll)
|
| 310 |
-
# -----------------------------------------------------------
|
| 311 |
|
| 312 |
-
# Collision check
|
| 313 |
current, coll, safe_mushrooms = check_auto_pickup(
|
| 314 |
x, y, current, coll, safe_mushrooms
|
| 315 |
)
|
| 316 |
|
| 317 |
-
# Now compare it to the length after the pickup
|
| 318 |
-
sound_to_play = "./assets/sounds/mushroom_pickup.wav" if len(coll) > initial_coll_len else "assets/sounds/bot_roam.wav"
|
| 319 |
-
|
| 320 |
# Update roam tracker
|
| 321 |
with _ROAM_LOCK:
|
| 322 |
_ROAM["position"] = [x, y]
|
|
@@ -333,7 +317,6 @@ def move_player(position, direction, current, collection, active_mushrooms):
|
|
| 333 |
gr.update(), # leave chat untouched
|
| 334 |
dex_markdown(coll),
|
| 335 |
progress_markdown(coll),
|
| 336 |
-
sound_to_play, # The sound output
|
| 337 |
)
|
| 338 |
|
| 339 |
# ---------------------------------------------------------------------------
|
|
@@ -420,16 +403,6 @@ def _safe_pos(position):
|
|
| 420 |
x, y = position
|
| 421 |
return (max(0,min(2,int(x))), max(0,min(2,int(y))))
|
| 422 |
|
| 423 |
-
def _stream_myco():
|
| 424 |
-
"""Streams only Myco's AI responses β no errors, no logs."""
|
| 425 |
-
seen = ""
|
| 426 |
-
while True:
|
| 427 |
-
current = _LAST_REPLY["text"]
|
| 428 |
-
if current != seen:
|
| 429 |
-
seen = current
|
| 430 |
-
yield seen
|
| 431 |
-
time.sleep(0.3)
|
| 432 |
-
|
| 433 |
|
| 434 |
# ---------------------------------------------------------------------------
|
| 435 |
# Guide text
|
|
@@ -478,8 +451,6 @@ def build_app():
|
|
| 478 |
# Autonomous roam timer β every 5 seconds, pure Python movement
|
| 479 |
roam_timer = gr.Timer(5)
|
| 480 |
|
| 481 |
-
sound_player = gr.Audio(visible=False, autoplay=True)
|
| 482 |
-
|
| 483 |
with gr.Tabs(selected="home") as tabs:
|
| 484 |
|
| 485 |
with gr.Tab("π Home", id="home"):
|
|
@@ -570,14 +541,21 @@ def build_app():
|
|
| 570 |
elem_id="myco-live-log",
|
| 571 |
)
|
| 572 |
|
| 573 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 574 |
|
| 575 |
# ββ Shared output lists ββ
|
| 576 |
_move_outputs = [
|
| 577 |
player_position, scene, status, discovery,
|
| 578 |
current_mushroom, collection, active_mushrooms,
|
| 579 |
-
chat, dex, progress,
|
| 580 |
-
sound_player
|
| 581 |
]
|
| 582 |
|
| 583 |
# ββ Event wiring ββ
|
|
|
|
| 5 |
import copy
|
| 6 |
import random
|
| 7 |
import threading
|
|
|
|
| 8 |
import logging
|
| 9 |
from typing import Any, cast
|
| 10 |
import gradio as gr
|
|
|
|
| 32 |
)
|
| 33 |
from ui.interface import render_shroom_tab
|
| 34 |
|
| 35 |
+
from game.engine import get_myco_log
|
| 36 |
|
| 37 |
logger = logging.getLogger(__name__)
|
| 38 |
EMPTY_COLLECTION = None
|
|
|
|
| 63 |
with _ROAM_LOCK:
|
| 64 |
x, y = _ROAM["position"]
|
| 65 |
safe_mushrooms = list(active_mushrooms or [])
|
| 66 |
+
coll = list(collection or [])
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
# Check pickup at current position first
|
| 69 |
if safe_mushrooms:
|
|
|
|
| 84 |
nx, ny, current, coll, safe_mushrooms
|
| 85 |
)
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
return (
|
| 88 |
list(pos),
|
| 89 |
forest_scene(current, coll, pos, active_mushrooms=safe_mushrooms),
|
|
|
|
| 95 |
gr.update(),
|
| 96 |
dex_markdown(coll),
|
| 97 |
progress_markdown(coll),
|
|
|
|
| 98 |
)
|
| 99 |
|
| 100 |
# ---------------------------------------------------------------------------
|
|
|
|
| 295 |
|
| 296 |
safe_mushrooms = list(active_mushrooms or [])
|
| 297 |
coll = list(collection or [])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
|
| 299 |
+
# Collision check β pure Python, no LLM
|
| 300 |
current, coll, safe_mushrooms = check_auto_pickup(
|
| 301 |
x, y, current, coll, safe_mushrooms
|
| 302 |
)
|
| 303 |
|
|
|
|
|
|
|
|
|
|
| 304 |
# Update roam tracker
|
| 305 |
with _ROAM_LOCK:
|
| 306 |
_ROAM["position"] = [x, y]
|
|
|
|
| 317 |
gr.update(), # leave chat untouched
|
| 318 |
dex_markdown(coll),
|
| 319 |
progress_markdown(coll),
|
|
|
|
| 320 |
)
|
| 321 |
|
| 322 |
# ---------------------------------------------------------------------------
|
|
|
|
| 403 |
x, y = position
|
| 404 |
return (max(0,min(2,int(x))), max(0,min(2,int(y))))
|
| 405 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
# ---------------------------------------------------------------------------
|
| 408 |
# Guide text
|
|
|
|
| 451 |
# Autonomous roam timer β every 5 seconds, pure Python movement
|
| 452 |
roam_timer = gr.Timer(5)
|
| 453 |
|
|
|
|
|
|
|
| 454 |
with gr.Tabs(selected="home") as tabs:
|
| 455 |
|
| 456 |
with gr.Tab("π Home", id="home"):
|
|
|
|
| 541 |
elem_id="myco-live-log",
|
| 542 |
)
|
| 543 |
|
| 544 |
+
# Poll the running Myco log (narrative/action/chat) once per second.
|
| 545 |
+
# NOTE: previously this used demo.load(fn=_stream_myco, ...) with an
|
| 546 |
+
# infinite `while True` generator. An infinite generator from
|
| 547 |
+
# demo.load permanently occupies a queue worker/stream for that
|
| 548 |
+
# session β other events (button clicks) can then execute their
|
| 549 |
+
# Python function fine (visible in server logs) but never get their
|
| 550 |
+
# result delivered back to the browser. A periodic Timer avoids this.
|
| 551 |
+
log_timer = gr.Timer(1)
|
| 552 |
+
log_timer.tick(fn=get_myco_log, outputs=myco_live)
|
| 553 |
|
| 554 |
# ββ Shared output lists ββ
|
| 555 |
_move_outputs = [
|
| 556 |
player_position, scene, status, discovery,
|
| 557 |
current_mushroom, collection, active_mushrooms,
|
| 558 |
+
chat, dex, progress,
|
|
|
|
| 559 |
]
|
| 560 |
|
| 561 |
# ββ Event wiring ββ
|