Spaces:
Running
Running
Instant arrival: hero image + options + download
Browse files- app/app.py +55 -8
app/app.py
CHANGED
|
@@ -29,9 +29,16 @@ _IMG_POOL = concurrent.futures.ThreadPoolExecutor(max_workers=2)
|
|
| 29 |
# zero latency, zero cold-start. Activates only if a (CC0/royalty-free) file is
|
| 30 |
# present, so the app never breaks without it. Drop one at assets/dream-ambient.mp3.
|
| 31 |
import os as _os # noqa: E402
|
| 32 |
-
|
|
|
|
| 33 |
HAS_MUSIC = _os.path.exists(MUSIC_PATH)
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
SPEAKER = {
|
| 36 |
"Dreamweaver": "π *Dreamweaver*",
|
| 37 |
"Nightmare": "π *Nightmare*",
|
|
@@ -106,6 +113,32 @@ def card_md() -> str:
|
|
| 106 |
)
|
| 107 |
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
def _btn_updates():
|
| 110 |
ups = []
|
| 111 |
for i in range(NBTN):
|
|
@@ -143,7 +176,9 @@ def _stream_turn(intent, tier, history):
|
|
| 143 |
"""Shared streaming loop -> [chatbot, state, btn1..N, intent, card, printbtn, dream]."""
|
| 144 |
history = history or []
|
| 145 |
history.append({"role": "user", "content": f"π§βπ {intent}"})
|
| 146 |
-
|
|
|
|
|
|
|
| 147 |
|
| 148 |
current = None
|
| 149 |
die_shown = False
|
|
@@ -156,12 +191,12 @@ def _stream_turn(intent, tier, history):
|
|
| 156 |
if not die_shown and engine.last_outcome is not None:
|
| 157 |
die_shown = True # reveal the seeded roll the instant it's decided
|
| 158 |
history.append(_die_bubble())
|
| 159 |
-
yield history, gr.update(), *
|
| 160 |
if speaker != current:
|
| 161 |
current = speaker
|
| 162 |
history.append({"role": "assistant", "content": SPEAKER.get(speaker, speaker) + ": "})
|
| 163 |
history[-1]["content"] += delta
|
| 164 |
-
yield history, gr.update(), *
|
| 165 |
|
| 166 |
# Show the gambit buttons the INSTANT narration + Hobbes are done β don't make
|
| 167 |
# the player wait on the slower image. (Buttons in ~8s, not ~17s.)
|
|
@@ -181,9 +216,18 @@ def _stream_turn(intent, tier, history):
|
|
| 181 |
def begin(env_id, seed, history):
|
| 182 |
engine.start(env_id, seed=(seed or "dream").strip())
|
| 183 |
env = ENVIRONMENTS[env_id]
|
| 184 |
-
history = [{"role": "assistant", "content": f"{env.emoji} *{env.opening}*"}
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
yield from _stream_turn("(You arrive and take in the scene.)", None, history)
|
| 188 |
|
| 189 |
|
|
@@ -285,7 +329,9 @@ with gr.Blocks(title="DAYDREAM") as demo:
|
|
| 285 |
scale=8, show_label=False, autofocus=True)
|
| 286 |
go = gr.Button("Do it", variant="primary", scale=1)
|
| 287 |
card = gr.Markdown(visible=False, elem_id="card")
|
| 288 |
-
|
|
|
|
|
|
|
| 289 |
with gr.Column(scale=2):
|
| 290 |
dream_img = gr.Image(label="π The dream", height=380, visible=False,
|
| 291 |
interactive=False, elem_id="dream", show_label=False)
|
|
@@ -304,6 +350,7 @@ with gr.Blocks(title="DAYDREAM") as demo:
|
|
| 304 |
for i, b in enumerate(btns):
|
| 305 |
b.click(make_choose(i), [chat], outs)
|
| 306 |
printbtn.click(lambda: gr.update(visible=True), None, card)
|
|
|
|
| 307 |
|
| 308 |
|
| 309 |
if __name__ == "__main__":
|
|
|
|
| 29 |
# zero latency, zero cold-start. Activates only if a (CC0/royalty-free) file is
|
| 30 |
# present, so the app never breaks without it. Drop one at assets/dream-ambient.mp3.
|
| 31 |
import os as _os # noqa: E402
|
| 32 |
+
_ASSETS = pathlib.Path(__file__).resolve().parent.parent / "assets"
|
| 33 |
+
MUSIC_PATH = str(_ASSETS / "dream-ambient.mp3")
|
| 34 |
HAS_MUSIC = _os.path.exists(MUSIC_PATH)
|
| 35 |
|
| 36 |
+
# Pre-generated world hero images shown the INSTANT you Begin β so the world is
|
| 37 |
+
# never empty while the live per-beat image generates. Maps env_id -> path | None.
|
| 38 |
+
def _world_hero(env_id: str):
|
| 39 |
+
p = _ASSETS / "worlds" / f"{env_id}.png"
|
| 40 |
+
return str(p) if p.exists() else None
|
| 41 |
+
|
| 42 |
SPEAKER = {
|
| 43 |
"Dreamweaver": "π *Dreamweaver*",
|
| 44 |
"Nightmare": "π *Nightmare*",
|
|
|
|
| 113 |
)
|
| 114 |
|
| 115 |
|
| 116 |
+
def build_story_md(history) -> str:
|
| 117 |
+
"""The player's full dream as a keepsake markdown β every beat + the journal."""
|
| 118 |
+
s = engine.state
|
| 119 |
+
title = f"# π DAYDREAM β {s.env_name if s else 'a dream'}\n"
|
| 120 |
+
meta = (f"*Seed `{s.seed}` Β· {s.turn} turns Β· "
|
| 121 |
+
f"dreamed by a fleet of small models*\n\n" if s else "")
|
| 122 |
+
out = [title, meta, "---\n"]
|
| 123 |
+
for m in (history or []):
|
| 124 |
+
c = (m.get("content") or "").strip()
|
| 125 |
+
if c:
|
| 126 |
+
out.append(c + "\n")
|
| 127 |
+
if s and s.over:
|
| 128 |
+
out.append("\n---\n\n" + card_md())
|
| 129 |
+
out.append("\n\n*Made with DAYDREAM Β· small models, big dreams π*")
|
| 130 |
+
return "\n".join(out)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def download_story(history):
|
| 134 |
+
"""Write the story to a temp .md and hand the path to the DownloadButton."""
|
| 135 |
+
import tempfile
|
| 136 |
+
seed = engine.state.seed if engine.state else "dream"
|
| 137 |
+
path = pathlib.Path(tempfile.gettempdir()) / f"daydream-{seed}.md"
|
| 138 |
+
path.write_text(build_story_md(history), encoding="utf-8")
|
| 139 |
+
return str(path)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
def _btn_updates():
|
| 143 |
ups = []
|
| 144 |
for i in range(NBTN):
|
|
|
|
| 176 |
"""Shared streaming loop -> [chatbot, state, btn1..N, intent, card, printbtn, dream]."""
|
| 177 |
history = history or []
|
| 178 |
history.append({"role": "user", "content": f"π§βπ {intent}"})
|
| 179 |
+
# Keep the gambit buttons VISIBLE and stable through the whole turn (no flicker).
|
| 180 |
+
# Gradio queues clicks, so a tap mid-stream just runs as the next turn.
|
| 181 |
+
yield history, state_md(), *_btn_updates(), "", *_card_updates(), gr.update()
|
| 182 |
|
| 183 |
current = None
|
| 184 |
die_shown = False
|
|
|
|
| 191 |
if not die_shown and engine.last_outcome is not None:
|
| 192 |
die_shown = True # reveal the seeded roll the instant it's decided
|
| 193 |
history.append(_die_bubble())
|
| 194 |
+
yield history, gr.update(), *_btn_updates(), "", *_card_updates(), gr.update()
|
| 195 |
if speaker != current:
|
| 196 |
current = speaker
|
| 197 |
history.append({"role": "assistant", "content": SPEAKER.get(speaker, speaker) + ": "})
|
| 198 |
history[-1]["content"] += delta
|
| 199 |
+
yield history, gr.update(), *_btn_updates(), "", *_card_updates(), gr.update()
|
| 200 |
|
| 201 |
# Show the gambit buttons the INSTANT narration + Hobbes are done β don't make
|
| 202 |
# the player wait on the slower image. (Buttons in ~8s, not ~17s.)
|
|
|
|
| 216 |
def begin(env_id, seed, history):
|
| 217 |
engine.start(env_id, seed=(seed or "dream").strip())
|
| 218 |
env = ENVIRONMENTS[env_id]
|
| 219 |
+
history = [{"role": "assistant", "content": f"{env.emoji} *{env.opening}*"},
|
| 220 |
+
{"role": "assistant",
|
| 221 |
+
"content": "π² **Your move** β pick a gambit below (riskier = bigger "
|
| 222 |
+
"reward, bigger chance to fail), or type your own action."}]
|
| 223 |
+
# Show the world's hero image + three gambit options INSTANTLY, so the world is
|
| 224 |
+
# never empty and you always know what to do. The live narration, Hobbes' own
|
| 225 |
+
# creative labels, and the per-beat image all layer in over the next few seconds.
|
| 226 |
+
engine.gambits = engine._make_gambits([]) # default labels, upgraded by Hobbes
|
| 227 |
+
hero = _world_hero(env_id)
|
| 228 |
+
hero_up = (gr.update(value=hero, visible=True) if hero
|
| 229 |
+
else gr.update(value=None, visible=False))
|
| 230 |
+
yield history, state_md(), *_btn_updates(), "", *_card_updates(), hero_up
|
| 231 |
yield from _stream_turn("(You arrive and take in the scene.)", None, history)
|
| 232 |
|
| 233 |
|
|
|
|
| 329 |
scale=8, show_label=False, autofocus=True)
|
| 330 |
go = gr.Button("Do it", variant="primary", scale=1)
|
| 331 |
card = gr.Markdown(visible=False, elem_id="card")
|
| 332 |
+
with gr.Row():
|
| 333 |
+
printbtn = gr.Button("πΈ Freeze the dream card", visible=False)
|
| 334 |
+
dlbtn = gr.DownloadButton("π₯ Download my story", size="sm")
|
| 335 |
with gr.Column(scale=2):
|
| 336 |
dream_img = gr.Image(label="π The dream", height=380, visible=False,
|
| 337 |
interactive=False, elem_id="dream", show_label=False)
|
|
|
|
| 350 |
for i, b in enumerate(btns):
|
| 351 |
b.click(make_choose(i), [chat], outs)
|
| 352 |
printbtn.click(lambda: gr.update(visible=True), None, card)
|
| 353 |
+
dlbtn.click(download_story, [chat], dlbtn) # export the full dream as markdown
|
| 354 |
|
| 355 |
|
| 356 |
if __name__ == "__main__":
|