| """Read the Room — navigate a complex social situation, directed by AI. |
| |
| Each scenario is a tab. `gameview` builds every game from the Scenario data, so adding a |
| scenario in scenarios/ adds a tab here for free; `communityview` browses what other players |
| shared; `creatorview` is the author-your-own tab. Both hand a freshly parsed Scenario to |
| the dedicated Play tab. |
| """ |
|
|
| import threading |
| import urllib.request |
|
|
| import gradio as gr |
|
|
| import communityview |
| import creator |
| import creatorview |
| import gameview |
| from art import font_css |
| from engine import API_BASE |
| from scenarios import REGISTRY |
|
|
| PLAY_TAB_ID = "play_custom" |
|
|
| |
| |
| |
| |
|
|
|
|
| def white_edge(r): |
| """A text-shadow outline: 8 white shadows ringing the glyph at radius `r` px.""" |
| return ",".join( |
| f"{dx}px {dy}px 0 #fff" for dx in (-r, 0, r) for dy in (-r, 0, r) if dx or dy |
| ) |
|
|
|
|
| NAV_CSS = ( |
| |
| |
| |
| f".rtr-title{{overflow:visible !important}}" |
| f".rtr-title h1{{color:#111;text-shadow:{white_edge(1)},{white_edge(2)}}}" |
| ".tab-container{background:rgba(255,255,255,.72);backdrop-filter:blur(8px);" |
| "border-radius:10px;padding:0 10px}" |
| ".tab-container button:not(.selected){color:#111}" |
| ) |
|
|
| |
| |
| FOOTER_CSS = ( |
| "footer button.settings,footer .divider:not(.show-api-divider)" |
| "{display:none !important}" |
| ) |
|
|
|
|
| def warm_model(): |
| """Ping the model endpoint so the Modal container boots while the user reads.""" |
|
|
| def ping(): |
| try: |
| |
| |
| urllib.request.urlopen( |
| API_BASE.removesuffix("/v1") + "/health", timeout=300 |
| ) |
| except OSError: |
| pass |
|
|
| threading.Thread(target=ping, daemon=True).start() |
|
|
|
|
| def do_load(scen): |
| """Switch to the Play tab and fill it with the freshly parsed scenario.""" |
| return (gr.update(selected=PLAY_TAB_ID), *gameview.load_into_play(scen)) |
|
|
|
|
| def load_scenario(path): |
| """Open a saved scenario straight in the Play tab — no detour through the editor.""" |
| try: |
| scen = creator.spec_to_scenario(creator.load_spec(path)) |
| except Exception as e: |
| raise gr.Error(f"{creator.LOAD_ERROR}: {e}") |
| return gameview.load_into_play(scen) |
|
|
|
|
| with gr.Blocks( |
| title="Read the Room", |
| |
| |
| |
| |
| |
| |
| |
| |
| theme=gr.themes.Soft( |
| primary_hue="indigo", |
| font=( |
| gr.themes.Font("Alkatra"), |
| gr.themes.Font("ui-sans-serif"), |
| gr.themes.Font("system-ui"), |
| gr.themes.Font("sans-serif"), |
| ), |
| ).set( |
| block_background_fill="transparent", |
| block_shadow="none", |
| block_title_background_fill="none", |
| block_title_padding="0", |
| block_title_text_color="*neutral_500", |
| block_title_text_weight="500", |
| ), |
| css=font_css() |
| + gameview.HIDE_CSS |
| + gameview.STORY_CSS |
| + gameview.BG_CSS |
| + creatorview.CREATOR_CSS |
| + NAV_CSS |
| + FOOTER_CSS, |
| ) as demo: |
| gr.Markdown("# Read the Room", elem_classes="rtr-title") |
| with gr.Tabs() as tabs: |
| for scen in REGISTRY.values(): |
| gameview.build_game(scen) |
| comm_play, comm_inputs, comm_share = communityview.build_community() |
| play_btn, form = creatorview.build_creator() |
| with gr.Tab("â–¶ Play your scenario", id=PLAY_TAB_ID): |
| with gr.Row(): |
| load_play = creatorview.load_button() |
| gr.HTML("") |
| _scen_state, play_outs = gameview.build_game_ui() |
|
|
| parse_state = gr.State() |
| play_btn.click(creatorview.parse_or_raise, form, parse_state).success( |
| do_load, parse_state, [tabs, *play_outs] |
| ) |
| comm_play.click(communityview.play_or_raise, comm_inputs, parse_state).success( |
| do_load, parse_state, [tabs, *play_outs] |
| ) |
| comm_share.click(lambda: gr.update(selected=creatorview.CREATOR_TAB_ID), None, tabs) |
| load_play.upload(load_scenario, load_play, play_outs) |
| demo.load(warm_model) |
|
|
| if __name__ == "__main__": |
| demo.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| ) |
|
|