polats Claude Opus 4.8 (1M context) commited on
Commit
dfdc145
·
1 Parent(s): 753598a

Gradio app at root with Pixi battlefield embedded via iframe (/battle)

Browse files

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +27 -13
  2. web/index.html +0 -10
app.py CHANGED
@@ -1,9 +1,10 @@
1
- """Tiny Army — HF Space.
2
 
3
- FastAPI serves the Pixi battle frontend (web/) at "/", where the deterministic
4
- combat engine runs in-browser. The Gradio "barracks" is mounted at "/app"; its
5
- war-diary generator is a stub for now, wired to a local llama.cpp small model
6
- during the hack.
 
7
  """
8
  import gradio as gr
9
  import uvicorn
@@ -18,6 +19,11 @@ def healthz():
18
  return {"ok": True}
19
 
20
 
 
 
 
 
 
21
  def write_diary(unit: str, traits: str) -> str:
22
  """Stub. Replaced during the hack by a local llama.cpp small model that writes
23
  a first-person war diary from the unit's memory + traits."""
@@ -27,20 +33,28 @@ def write_diary(unit: str, traits: str) -> str:
27
  f"real voice soon, shaped by what I lived through on the field.")
28
 
29
 
30
- with gr.Blocks(title="Tiny Army — Barracks", theme=gr.themes.Soft()) as barracks:
31
- gr.Markdown("# 🪖 Tiny Army — Barracks\n"
32
- "*Every fighter writes its own legend.* (skeleton — diary is a stub) · "
33
- "[← battlefield](/)")
 
 
 
 
 
 
 
 
 
 
34
  with gr.Row():
35
  unit = gr.Textbox(label="Unit", value="Bram the Warrior")
36
  traits = gr.Textbox(label="Traits", value="Cautious, Veteran, Vengeful")
37
  out = gr.Textbox(label="War diary", lines=6)
38
  gr.Button("Write diary", variant="primary").click(write_diary, [unit, traits], out)
39
 
40
- # Gradio barracks at /app; the Pixi frontend (static) at / mounted last so the
41
- # more specific /app and /healthz routes win.
42
- app = gr.mount_gradio_app(app, barracks, path="/app")
43
- app.mount("/", StaticFiles(directory="web", html=True), name="web")
44
 
45
 
46
  if __name__ == "__main__":
 
1
+ """Tiny Army — HF Space (Gradio app at the root, Pixi battlefield embedded).
2
 
3
+ The deterministic combat engine + Pixi renderer are served as a static page under
4
+ /battle and embedded in the Gradio app via an iframe so the Space root *is* a
5
+ Gradio app (hackathon requirement) with the live battlefield shown inside it. The
6
+ barracks war-diary generator is a stub for now, wired to a local llama.cpp small
7
+ model during the hack.
8
  """
9
  import gradio as gr
10
  import uvicorn
 
19
  return {"ok": True}
20
 
21
 
22
+ # The Pixi battlefield (static: index.html + main.js + bundled engine.js) lives
23
+ # under /battle so the Gradio app can iframe it.
24
+ app.mount("/battle", StaticFiles(directory="web", html=True), name="battle")
25
+
26
+
27
  def write_diary(unit: str, traits: str) -> str:
28
  """Stub. Replaced during the hack by a local llama.cpp small model that writes
29
  a first-person war diary from the unit's memory + traits."""
 
33
  f"real voice soon, shaped by what I lived through on the field.")
34
 
35
 
36
+ BATTLE_IFRAME = (
37
+ '<iframe src="/battle/" title="Tiny Army battlefield" '
38
+ 'style="width:100%;height:60vh;border:1px solid #20262e;border-radius:12px;'
39
+ 'background:#0b0e12"></iframe>'
40
+ )
41
+
42
+ with gr.Blocks(title="Tiny Army", theme=gr.themes.Soft()) as demo:
43
+ gr.Markdown("# 🪖 Tiny Army\n"
44
+ "*Every fighter writes its own legend — and the legend is true.* "
45
+ "The deterministic auto-battler engine runs in your browser, below.")
46
+ gr.HTML(BATTLE_IFRAME)
47
+ gr.Markdown("### Barracks — war diaries\n"
48
+ "_(skeleton — the diary is a stub; the hack wires it to a local "
49
+ "llama.cpp small model that writes from each unit's lived experience.)_")
50
  with gr.Row():
51
  unit = gr.Textbox(label="Unit", value="Bram the Warrior")
52
  traits = gr.Textbox(label="Traits", value="Cautious, Veteran, Vengeful")
53
  out = gr.Textbox(label="War diary", lines=6)
54
  gr.Button("Write diary", variant="primary").click(write_diary, [unit, traits], out)
55
 
56
+ # Gradio app at the root; /battle and /healthz were registered first so they win.
57
+ app = gr.mount_gradio_app(app, demo, path="/")
 
 
58
 
59
 
60
  if __name__ == "__main__":
web/index.html CHANGED
@@ -8,22 +8,12 @@
8
  html, body { margin: 0; height: 100%; background: #0b0e12; color: #f4ecd8;
9
  font-family: ui-monospace, Menlo, monospace; }
10
  #wrap { display: flex; flex-direction: column; height: 100vh; }
11
- header { padding: 10px 14px; display: flex; justify-content: space-between;
12
- align-items: center; border-bottom: 1px solid #20262e; }
13
- header strong { letter-spacing: .03em; }
14
- header span { color: #9aa4b2; font-size: 13px; }
15
- header a { color: #ffd54a; text-decoration: none; }
16
  #stage { flex: 1; min-height: 0; }
17
  canvas { display: block; width: 100%; height: 100%; }
18
  </style>
19
  </head>
20
  <body>
21
  <div id="wrap">
22
- <header>
23
- <strong>🪖 Tiny Army</strong>
24
- <span>deterministic auto-battler engine running in-browser ·
25
- <a href="/app">barracks →</a></span>
26
- </header>
27
  <div id="stage"></div>
28
  </div>
29
  <script type="module" src="./main.js"></script>
 
8
  html, body { margin: 0; height: 100%; background: #0b0e12; color: #f4ecd8;
9
  font-family: ui-monospace, Menlo, monospace; }
10
  #wrap { display: flex; flex-direction: column; height: 100vh; }
 
 
 
 
 
11
  #stage { flex: 1; min-height: 0; }
12
  canvas { display: block; width: 100%; height: 100%; }
13
  </style>
14
  </head>
15
  <body>
16
  <div id="wrap">
 
 
 
 
 
17
  <div id="stage"></div>
18
  </div>
19
  <script type="module" src="./main.js"></script>