A newer version of the Gradio SDK is available: 6.25.0
NumZoo β Agent Guidelines
What this project is
Kids mental math app. Correct answers earn AI-generated cute animal images (FLUX.2-klein-4B). Stack: Python Β· Gradio Β· diffusers Β· HuggingFace Spaces (ZeroGPU).
Mandatory dev loop
Run these scripts in order before every push. Fix failures before proceeding.
| Step | Command | Must pass |
|---|---|---|
| Syntax | bash scripts/syntax.sh |
No syntax errors |
| Imports | bash scripts/check_imports.sh |
No import errors |
| SDK version | bash scripts/check_sdk.sh |
README sdk_version == local gradio |
| Run | bash scripts/run.sh |
App starts, no errors in logs |
| Test | bash scripts/test.sh |
All Puppeteer checks pass, screenshots look correct |
Full loop shortcut: bash scripts/dev.sh (runs steps 1β4, then run test.sh manually).
Never push if any step fails.
Key files
| File | Role |
|---|---|
app.py |
Gradio UI, game logic, panel visibility, event wiring |
image_generator.py |
FLUX.2-klein-4B pipeline, persistent cache setup, emojiβtext prompt builder |
math_engine.py |
Question generation, level names & thresholds |
requirements.txt |
Python dependencies β never include gradio here |
README.md |
HF Spaces config (frontmatter sdk_version) + user docs |
scripts/ |
Dev loop automation scripts |
Architecture
3-panel flow: Welcome β Emoji Picker β Quiz
Game state dict keys:
name, level, score, streak, correct_since_reward, level_correct,
question, answer, selected_animals, selected_places,
reward_count, pregenerate_next, generate_now, pending_reward_id
Reward trigger: every REWARD_EVERY = 3 correct answers.
Pre-generation flow (async image UX):
- "Let's go!" β
start_game()β adds locked placeholder for reward #1 in collection β.then(pregenerate_image)starts background generation immediately pregenerate_imagestores PIL image inhidden_image(gr.Image) and base64 data URL inhidden_data_url(gr.Textbox), setsstate["pending_reward_id"]- When user earns a reward:
check_answerreceiveshidden_image+hidden_data_urlas inputs.- If pre-image matches reward_id β show immediately + unlock in collection + kick off next pregenerate
- If not ready β show LOADER_HTML + set
generate_now=True
.then(generate_on_demand)β generates on-demand ifgenerate_now, unlocks collection.then(pregenerate_image)β generates next reward in background
Collection system:
collection_trigger = gr.HTML("")receives JSON-encoded action payloads- JS polls
#numzoo-coll-dataelement every 200ms for{ts, actions:[{action,id,src}]} - Actions:
add-locked(locked placeholder),unlock(reveal image),failed(remove) - Images stored in localStorage as base64 JPEGs (key:
numzoo_collection_v2)
Loader fix: Use value-swapping ("" = hidden, LOADER_HTML = shown) instead of visible flag.
This is more reliable in Gradio 6 than toggling visibility on gr.HTML components.
Gradio rules
- Use
gr.update(visible=True/False)for visibility β never return bare booleans - Theme and CSS go in
demo.launch(css=..., theme=...), NOT ingr.Blocks() gr.Grouphas no.change()event- Never put
gradioinrequirements.txtβ HF Spaces pins it viasdk_versionin README - Never put
audioop-ltsinrequirements.txtβ HF Spaces runs Python 3.10 whereaudioopis stdlib;audioop-ltsrequires >=3.13 and will break the build - Keep
sdk_versionin README in sync with local gradio (runscripts/check_sdk.sh)
Python runtime
- Local:
~/miniforge3/bin/python3(arm64, Python 3.13, torch 2.12, MPS) - HF Spaces: Python 3.13, ZeroGPU (A100 via
@spaces.GPU) - Do not use system Python (
/usr/local/bin/python3) β it's x64 Rosetta, incompatible
Image generation
- Model:
black-forest-labs/FLUX.2-klein-4B(Apache 2.0 β accept license on HF before first run) - Same model local + HF Spaces. 4 steps,
guidance_scale=1.0, 512Γ512 - Local: float16 on MPS (needs ~13GB, fits in 32GB unified mem) Β· HF ZeroGPU: ~fast on A100
- Pipeline class:
Flux2KleinPipeline(diffusers β₯ 0.38) - Always wrap in try/except β return
(None, "")on failure IS_HF_SPACE = os.environ.get("SPACE_ID") is not Nonecontrols ZeroGPU decorator- Logs generation time (
β Generated in Xs)
Persistent model cache (HF Spaces)
/data/hf_cache_v4mount caches the model across restarts (set in Space Settings β Storage)- Setup is bulletproof: poisoned/partial cache is auto-wiped & re-downloaded;
any
/datafailure falls back to ephemeral container cache; import never crashes - If the cache tree gets poisoned again (
[Errno 20] Not a directory), bump the path suffix (_v4β_v5) to escape it β established repo pattern NUMZOO_STYLEconstant inimage_generator.pyis the single source of truth for the art style;scripts/generate_dataset.pyimports it so training captions match
HuggingFace deployment
- Space:
https://huggingface.co/spaces/Goumsss/numzoo - Push = redeploy. Rebuilds automatically.
- ZeroGPU: set in Space Settings β Hardware (requires HF Pro or hackathon grant)
UI/UX principles
- English only, minimal text β words not sentences (kids with limited English)
- Emoji-heavy feedback (
β Great! π₯π₯not full sentences) - Never crash visibly β all handlers wrapped in try/except with safe fallbacks