numzoo / CLAUDE.md
goumsss's picture
Docs: update CLAUDE.md + README for FLUX.2-klein-4B
2b1f846
|
Raw
History Blame Contribute Delete
5.5 kB

A newer version of the Gradio SDK is available: 6.25.0

Upgrade

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