File size: 5,495 Bytes
eb5e547 2b1f846 eb5e547 61571e8 eb5e547 2b1f846 eb5e547 61571e8 eb5e547 ecbb6ed eb5e547 ecbb6ed eb5e547 61571e8 eb5e547 61571e8 07b0d03 61571e8 eb5e547 61571e8 eb5e547 2b1f846 61571e8 eb5e547 2b1f846 eb5e547 61571e8 eb5e547 61571e8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | # 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):**
1. "Let's go!" β `start_game()` β adds locked placeholder for reward #1 in collection β
`.then(pregenerate_image)` starts background generation immediately
2. `pregenerate_image` stores PIL image in `hidden_image` (gr.Image) and base64 data URL in
`hidden_data_url` (gr.Textbox), sets `state["pending_reward_id"]`
3. When user earns a reward: `check_answer` receives `hidden_image` + `hidden_data_url` as 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`
4. `.then(generate_on_demand)` β generates on-demand if `generate_now`, unlocks collection
5. `.then(pregenerate_image)` β generates next reward in background
**Collection system:**
- `collection_trigger = gr.HTML("")` receives JSON-encoded action payloads
- JS polls `#numzoo-coll-data` element 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 in `gr.Blocks()`
- `gr.Group` has no `.change()` event
- Never put `gradio` in `requirements.txt` β HF Spaces pins it via `sdk_version` in README
- Never put `audioop-lts` in `requirements.txt` β HF Spaces runs Python 3.10 where `audioop` is stdlib; `audioop-lts` requires >=3.13 and will break the build
- Keep `sdk_version` in README in sync with local gradio (run `scripts/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 None` controls ZeroGPU decorator
- Logs generation time (`β
Generated in Xs`)
## Persistent model cache (HF Spaces)
- `/data/hf_cache_v4` mount caches the model across restarts (set in Space Settings β Storage)
- Setup is **bulletproof**: poisoned/partial cache is auto-wiped & re-downloaded;
any `/data` failure 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_STYLE` constant in `image_generator.py` is the single source of truth for
the art style; `scripts/generate_dataset.py` imports 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
|