Spaces:
Running
Running
Gradio app at root with Pixi battlefield embedded via iframe (/battle)
Browse filesCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- app.py +27 -13
- web/index.html +0 -10
app.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
-
"""Tiny Army — HF Space.
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
|
|
|
| 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 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 41 |
-
|
| 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>
|