JacobLinCool Codex commited on
Commit
f44aac9
·
verified ·
1 Parent(s): 320c305

fix: align space metadata

Browse files

Co-authored-by: Codex <noreply@openai.com>

.gitattributes CHANGED
@@ -1,35 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
3
+ static/assets/parchment.png filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Ruff stuff:
171
+ .ruff_cache/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
175
+
176
+ # Cursor
177
+ # Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
178
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
179
+ # refer to https://docs.cursor.com/context/ignore-files
180
+ .cursorignore
181
+ .cursorindexingignore
DESIGN.md ADDED
@@ -0,0 +1,492 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Build Small Hackathon Advisor — Design & Implementation Notes
2
+
3
+ > A **small-model agent** (text-first; voice is a later bonus) that investigates what other people have already built
4
+ > for the [Build Small Hackathon](https://huggingface.co/build-small-hackathon) and brainstorms an original new design
5
+ > *with you*. Output = streaming text + live visuals (no TTS). All models small, open-weight, run locally.
6
+ >
7
+ > The literal "advisor" is the **engine**; the user-facing experience is **The Unwritten Almanac** — Mothback, an
8
+ > owl-moth archivist, keeps the Wood's book of fates and divines you a still-unwritten project page (ink **bleeds +
9
+ > cites real Spaces** if you overlap, **blooms gold** if it's new). This project is itself a Build Small submission
10
+ > (hack window 2026-06-05 → 2026-06-15).
11
+
12
+ ---
13
+
14
+ ## 1. Locked decisions & review corrections (2026-06-07)
15
+
16
+ A multi-agent adversarial review (5 dimensions, web-verified) set the direction below. **This section is the
17
+ authoritative decision log; the rest of the doc is written to be consistent with it.**
18
+
19
+ **Locked decisions (Jacob):**
20
+ 1. **Concept = The Unwritten Almanac** (chosen 2026-06-07 from a 12-concept brainstorm). Mothback the owl-moth
21
+ archivist divines a fate-page; ink **bleeds and cites the real Spaces** you overlap (page 47, page 112…), or
22
+ **blooms gold + sprouts a leaf** when it's unwritten. Engine unchanged underneath (crawl → whitespace/originality →
23
+ score). The dry "advisor" stays under the hood. Full spec + de-risking grafts in §2.
24
+ 2. **Text-first.** Ship a fully demoable text-input (type / button) vertical slice first. Voice (push-to-talk → batch
25
+ ASR) is a later BONUS layer. Real-time streaming + in-browser turn detection are **deferred** (see §13 appendix).
26
+ 3. **Add a 🎯 Well-Tuned fine-tune** — a small LoRA (MiniCPM5 advisor persona / tool-calling), trained on Modal,
27
+ published to the Hub → 6/6 badges → strong shot at 🎖️ Bonus Quest Champion ($2,000).
28
+ 4. **ASR = Nemotron primary (batch) + Parakeet fallback.** `nvidia/nemotron-speech-streaming-en-0.6b` used in BATCH
29
+ per-turn mode (`transcribe([wav])` — simple API). Day-1 spike confirms it builds + runs in one `@spaces.GPU` call;
30
+ if the ZeroGPU *environment* fights it, drop in `nvidia/parakeet-tdt-0.6b-v3` (native transformers, same batch API).
31
+
32
+ **Verified corrections:**
33
+ - **Drop SGLang.** It needs a persistent GPU process → incompatible with ZeroGPU (same root cause as vLLM). Run
34
+ MiniCPM5 via plain `transformers` inside `@spaces.GPU` and parse its XML tool calls in our own code.
35
+ - **gr.Server SSE generator streaming IS shipped** (the launch blog only deferred the *explanation*). On ZeroGPU the
36
+ browser MUST call endpoints via **`@gradio/client`** (`client.predict`/`submit`) — it forwards the HF iframe auth
37
+ headers for GPU quota; a raw `fetch`/`EventSource` POST silently breaks quota.
38
+ - **OpenAI Track has NO model requirement** ("OpenAI's own podium across all submissions") → auto-entered; a free
39
+ lottery ticket. Do NOT add gpt-oss (breaks Tiny Titan, dilutes the small-model thesis). Deliberate non-target.
40
+ - **Badges = 6 total** (Tiny Titan is a $1.5k *special award*, not a badge). Decision #3 takes us from 5/6 → 6/6.
41
+ - **Tiny Titan** = "best ≤4B model"; our largest single model is MiniCPM5 (1.08B), total stack ~1.9B → eligible.
42
+
43
+ **New build requirements surfaced by the review (designed into the sections below):**
44
+ - **Jargon alias layer (§7):** a 0.6B ASR mistranscribes our own vocab (Nemotron, MiniCPM5, EmbeddingGemma, ZeroGPU…).
45
+ Deterministic code-side fuzzy/alias map over our small CLOSED vocab, applied before any tool call and before display.
46
+ Surface "heard: neutron → Nemotron" as a delightful trust moment. (Active once voice is added.)
47
+ - **Tool-call degradation ladder (§8):** the 1B brain WILL emit broken tool calls (MiniCPM5-1B has a documented
48
+ "broken tool calling" report). Wrap parse in try/except, retry once at low temp, validate name+args vs JSON-Schema in
49
+ code (reject-and-repair), canned lines for empty results, a token **watchdog** that shows "trying again" instead of
50
+ dead air (the screen is the only feedback channel — no TTS).
51
+ - **Latency / optimistic UI (§9/§11):** ZeroGPU cold start + 1B generation = seconds of potential dead air. Optimistic
52
+ UI on submit, pre-animate the project wall, set a latency budget. (The torch.compile cold-start penalty does NOT
53
+ apply — we don't use it.)
54
+
55
+ **Day-1 go/no-go spikes (before any feature work):**
56
+ - Trivial `@spaces.GPU` hello-cuda build GREEN on torch 2.8+, deps pinned, heavy deps added one at a time.
57
+ - `gr.Server` minimal: static `index.html` + one `@app.api()` generator streaming tokens, called via `@gradio/client`,
58
+ on the real ZeroGPU Space.
59
+ - Nemotron `nemo_toolkit[asr]` install + one batch `transcribe()` inside `@spaces.GPU` (decision #4; else Parakeet).
60
+
61
+ ---
62
+
63
+ ## 2. Concept — The Unwritten Almanac (text-first)
64
+
65
+ The engine, regardless of skin:
66
+
67
+ 1. **Investigate** the `build-small-hackathon` HF org — what Spaces exist, which models, what's saturated, and where
68
+ the **whitespace** is — using a local EmbeddingGemma index.
69
+ 2. **Brainstorm** with the user: propose ideas, **score** them against a fixed rubric (originality vs. existing
70
+ projects, delight, AI-necessity, feasibility, param budget, prize-fit), and maintain an **idea board**.
71
+ 3. **Respond** as streaming text + live visuals in a custom `gr.Server` frontend (no TTS — the visual is the "voice").
72
+
73
+ **The skin (chosen): The Unwritten Almanac.** **Mothback**, a dusty owl-moth archivist, keeps the Wood's *book of
74
+ fates*. Every project already built in the org is an inked page; she divines you a destined entry on a still-blank page,
75
+ the ink writing itself live.
76
+
77
+ **The two-beat wow (this IS the engine, rendered):**
78
+ - You type one line about yourself / your idea. Inked pages riffle past (each = a real crawled Space).
79
+ - **Bleed:** if your idea overlaps existing work, the ink **seeps blood-red** and cites the exact real Spaces — "the
80
+ Wood already wrote this, on page 47 and page 112" (= `get_project` overlap on the top retrieval hits). The burn is
81
+ **factual**, so it can't fall flat the way a 1B's invented joke can.
82
+ - **Bloom:** you say "write bolder"; the next entry flows **gold**, a green leaf sprouts — "this page has never been
83
+ inked" (= a `find_whitespace` gold candidate).
84
+ - A **wax seal** presses in, lighting five quadrants as the idea qualifies (= `score_idea`: Originality, Delight,
85
+ AI-Necessity, Feasibility, Prize-Fit).
86
+
87
+ **Engine ↔ skin mapping:** `search_projects`/`get_project` overlap → the bleed + citations; `find_whitespace` → the
88
+ blank/gold pages; `score_idea` → the wax-seal quadrants; `save_idea` → the written fate-page; agent persona =
89
+ **Mothback** (Layer A system prompt + the 🎯 Well-Tuned LoRA = her voice).
90
+
91
+ **Shareable artifact (Community Choice):** the page exports as a PNG that looks **torn from an ancient grimoire** —
92
+ aged parchment, a coined fate-name as title, the self-written prophecy, the five-quadrant seal, and a verdict stamp
93
+ (**"UNWRITTEN · 0 echoes"** vs **"ECHO ×3"**). Built-in caption: "Mothback inked my fate page for #BuildSmall —
94
+ UNWRITTEN." People compile draws into a "chapter" and dare friends to get a page that doesn't bleed.
95
+
96
+ **Grafted de-risking (from runner-up concepts):**
97
+ - **Tone = dry-but-benevolent** (Roastleaf's whiplash): the bleed-citation gently stings, the gold-bloom is sincerely
98
+ delighted; the burn is true-by-construction (real cited Spaces).
99
+ - **Templated structure (key risk-killer):** bank entry/roast templates (citation + dry verdict + redemptive branch);
100
+ the 1B only fills in real Space titles + the idea — **never improvises whole comedy**.
101
+ - **Latin-binomial fate-names** (e.g. "Ludus Vocalis Infantium") via templated scaffolds — built-in wit, backstops a
102
+ 1B that might produce corny names.
103
+ - **"You vs the Wood" margin glyph:** a tiny cluster-dot thumbnail on the page showing your gold page among the inked
104
+ crowd — cheap SVG, visual PROOF the gap is real.
105
+ - **Thin-org mitigation (load-bearing):** precompute whitespace clusters at Modal build-time and pin several DISTINCT
106
+ blank-page candidates so "write bolder" always lands on a real, varied gap (the org may be only ~30–60 Spaces). Tune
107
+ the echo threshold toward *more frequent bleed* so the demo always has its "low" before the "wow".
108
+
109
+ **Defaults (revisit if time):** single-page artifact first (chapter compiler later); page-numbers visible, real titles
110
+ on hover (keep the burn aimed at the idea, not a named builder); seal animation = safe typewriter + static-stamp floor
111
+ first, bespoke ink-reveal last (graceful degradation); voice input stays text-first, a post-MVP flourish.
112
+
113
+ Input is **text-first**; the experience is fully delightful with typed input alone.
114
+
115
+ AI is genuinely **load-bearing**: embeddings power the whitespace/originality analysis and the LLM drives the
116
+ investigate → ideate → score loop — the experience collapses without the models (supports 🤖 Best Agent + TTW
117
+ "AI necessity").
118
+
119
+ ---
120
+
121
+ ## 3. Model stack (confirmed exact repo IDs)
122
+
123
+ | Role | Model | Params | Runtime | License | Prize hook |
124
+ |---|---|---|---|---|---|
125
+ | STT (batch, voice-later) | **`nvidia/nemotron-speech-streaming-en-0.6b`** (used in batch) | 0.6B | NeMo, GPU+CUDA | NVIDIA Open Model (commercial OK) | 🟩 NVIDIA Nemotron Quest |
126
+ | ↳ fallback | `nvidia/parakeet-tdt-0.6b-v3` | 0.6B | **transformers** (no NeMo) | CC-BY-4.0 | 🟩 (Quest brand — verify, §5.1) |
127
+ | LLM brain | **`openbmb/MiniCPM5-1B`** ("OpenCPM5") | 1.08B | **transformers** (self-parse XML) / llama.cpp | **Apache-2.0** | 🏮 OpenBMB |
128
+ | Turn detection (voice-later) | **`pipecat-ai/smart-turn-v3`** | ~8M | ONNX Runtime (browser) | BSD-2 | (natural voice UX) |
129
+ | Embedder | **`google/embeddinggemma-300m`** | ~300M | sentence-transformers / llama.cpp | Gemma (gated) | 🔌 Off the Grid · 🦙 Llama Champion |
130
+ | Fine-tune (to add) | LoRA on MiniCPM5 → published to Hub | — | PEFT / Modal | — | 🎯 Well-Tuned |
131
+
132
+ **Total ≈ 1.9B params → ≤4B → 🐜 Tiny Titan eligible.** All open-weight, all runnable locally → 🔌 Off the Grid.
133
+
134
+ > Naming: "OpenCPM5 1B" = `openbmb/MiniCPM5-1B` (MiniCPM 5.0, ~May 2026). "EmbeddingGemma 270M" =
135
+ > `google/embeddinggemma-300m` (308M total; 270M = non-embedding transformer params). **SGLang dropped** (ZeroGPU
136
+ > incompatible). STT used in **batch per-turn** mode, not streaming.
137
+
138
+ ---
139
+
140
+ ## 4. Deployment & architecture (single path)
141
+
142
+ With **text-first + batch ASR**, the old "streaming ASR vs ZeroGPU" Config A/B tension dissolves — there is one path:
143
+
144
+ - **ZeroGPU Gradio-SDK Space** (free). GPU is attached only inside `@spaces.GPU` calls (default 60s, max ~120s,
145
+ RTX Pro 6000 Blackwell, `large`=48 GB). Per-turn inference fits this model exactly.
146
+ - **Text-first runtime loop:** user types → `gr.Server` `@app.api()` endpoint (called via `@gradio/client`) → one
147
+ `@spaces.GPU` call runs MiniCPM5 (tool loop, in `transformers`) → SSE-stream text tokens + drive live visuals.
148
+ - **Voice (later bonus):** push-to-talk records an utterance → POST blob → the same `@spaces.GPU` call also runs
149
+ Nemotron/Parakeet ASR (batch) before the brain. No persistent stream, no WebRTC, **no TURN server**.
150
+ - **Modal (build-time only):** crawl the org + build the EmbeddingGemma index offline; the Space ships with the index
151
+ artifact. Runtime never calls Modal → 🔌 Off the Grid holds (see §10).
152
+
153
+ > Off the Grid = no proprietary cloud inference APIs. Open weights on an HF GPU Space / local box / Modal all qualify.
154
+
155
+ **Deferred (voice-later appendix, §13):** real-time streaming ASR (`conformer_stream_step`), in-browser Smart Turn +
156
+ Silero VAD turn detection, FastRTC. Documented but not on the text-first critical path.
157
+
158
+ ---
159
+
160
+ ## 5. Per-model implementation notes
161
+
162
+ ### 5.1 ASR — `nvidia/nemotron-speech-streaming-en-0.6b` (batch) · fallback `parakeet-tdt-0.6b-v3`
163
+
164
+ - **Primary, batch usage (simple):**
165
+ ```python
166
+ import nemo.collections.asr as nemo_asr
167
+ asr = nemo_asr.models.ASRModel.from_pretrained("nvidia/nemotron-speech-streaming-en-0.6b")
168
+ text = asr.transcribe(["utterance.wav"]) # 16 kHz mono WAV in; punctuated EN text out
169
+ ```
170
+ Install (heavy; CUDA): `apt-get install -y libsndfile1 ffmpeg` + `pip install Cython packaging` +
171
+ `pip install "git+https://github.com/NVIDIA/NeMo.git@main#egg=nemo_toolkit[asr]"` (NeMo ≥ 25.11).
172
+ **Day-1 spike** must confirm this installs + runs in one `@spaces.GPU` call (load with `map_location="cpu"` at module
173
+ level, `.to("cuda")` inside the decorator; watch NeMo CUDA-at-import + the RNNT CUDA-graphs path).
174
+ - **Fallback (if the ZeroGPU env fights NeMo):** `nvidia/parakeet-tdt-0.6b-v3` has **native transformers** support —
175
+ ```python
176
+ from transformers import pipeline
177
+ asr = pipeline("automatic-speech-recognition", model="nvidia/parakeet-tdt-0.6b-v3")
178
+ text = asr("utterance.wav")
179
+ ```
180
+ Same 0.6B, CC-BY-4.0, 25 languages, one clean `@spaces.GPU` batch call, no NeMo/git build.
181
+ - **Quest caveat:** the award says "standout **Nemotron** builds." Parakeet is the encoder *inside* the Nemotron-Speech
182
+ family but is branded "Parakeet" → if we ship Parakeet, **confirm Quest eligibility with organizers**, or keep
183
+ Nemotron documented as the Quest narrative. Hosted NVIDIA NIM API would break Off the Grid — never in the badge demo.
184
+
185
+ ### 5.2 MiniCPM5-1B brain — `openbmb/MiniCPM5-1B` (transformers, self-parsed XML)
186
+
187
+ - Context 128K, bilingual (EN/ZH), Apache-2.0. `enable_thinking=False`, `temperature=0.7, top_p=0.95` for fast tool calls.
188
+ ```python
189
+ from transformers import AutoModelForCausalLM, AutoTokenizer
190
+ tok = AutoTokenizer.from_pretrained("openbmb/MiniCPM5-1B")
191
+ model = AutoModelForCausalLM.from_pretrained("openbmb/MiniCPM5-1B", torch_dtype="auto", device_map="auto")
192
+ inputs = tok.apply_chat_template(messages, tools=TOOLS, add_generation_prompt=True, enable_thinking=False,
193
+ tokenize=True, return_dict=True, return_tensors="pt").to(model.device)
194
+ ```
195
+ - **Tool calling:** pass JSON-Schema tools via the chat template `tools=` arg; the model emits **XML**
196
+ `<function name="get_weather">{"city":"New York"}</function>`. **Parse this ourselves** (SGLang dropped). Wrap parse
197
+ in try/except and validate against the schema — see the degradation ladder (§8).
198
+ - **Local / CPU & llama.cpp (Off the Grid · Llama Champion):** `openbmb/MiniCPM5-1B-GGUF:Q4_K_M` (688 MB) via llama.cpp
199
+ or Ollama (CPU-viable). fp16 ≈ 3–4 GB VRAM. `openbmb/MiniCPM5-1B-MLX` for Apple Silicon. (llama.cpp MiniCPM5
200
+ tool-calling is a pending PR — verify before relying on it for the badge runtime.)
201
+ - **1B discipline:** small tool schemas, few params each, clear descriptions, low temp, single-hop tool calls.
202
+
203
+ ### 5.3 Smart Turn v3 — `pipecat-ai/smart-turn-v3` (voice-later)
204
+
205
+ - Whisper-tiny encoder + linear head, ~8M params, ONNX (`smart-turn-v3.1.onnx`), 8 MB int8 / 32 MB fp32, BSD-2.
206
+ - Input: 16 kHz mono PCM float32, ≤8 s (front-padded). Output: sigmoid prob; **>0.5 = "user finished."** ~12 ms CPU.
207
+ ```python
208
+ import numpy as np, onnxruntime as ort
209
+ from transformers import WhisperFeatureExtractor
210
+ fe = WhisperFeatureExtractor(chunk_length=8); sess = ort.InferenceSession("smart-turn-v3.1.onnx")
211
+ def turn_complete(audio):
212
+ x = fe(audio, sampling_rate=16000, return_tensors="np", padding="max_length",
213
+ max_length=8*16000, truncation=True, do_normalize=True).input_features.astype("float32")
214
+ return sess.run(None, {"input_features": x})[0][0].item() > 0.5
215
+ ```
216
+ - **Browser path (voice-later):** runs via ONNX Runtime Web / Transformers.js. The `.onnx` needs Whisper **log-mel
217
+ input_features** (128 mel bins, 8 s, NOT whisper-tiny's 80-mel) — no upstream JS example exists, so a small POC is
218
+ required before relying on it; fallbacks: port pipecat's numpy-only mel extractor to JS, or do feature-extraction +
219
+ onnx **server-side** per posted blob. Pair with `@ricky0123/vad-web` (Silero) for the speech start/stop gate.
220
+
221
+ ### 5.4 EmbeddingGemma — `google/embeddinggemma-300m`
222
+
223
+ - **Gated** — accept Gemma terms + `HF_TOKEN`. 2048-token ctx, 100+ langs, mean pooling, **fp32/bf16 only (no fp16)**.
224
+ ```python
225
+ from sentence_transformers import SentenceTransformer
226
+ m = SentenceTransformer("google/embeddinggemma-300m", truncate_dim=256) # Matryoshka 768→512→256→128
227
+ q = m.encode_query("voice game for kids") # prefix: "task: search result | query: "
228
+ d = m.encode_document(project_descriptions) # prefix: "title: none | text: "
229
+ ```
230
+ - **Exact prefixes matter:** query → `task: search result | query: `; document → `title: {title} | text: `; whitespace
231
+ clustering → prompt `Clustering` (`task: clustering | query: `). 256-dim is a good speed/quality tradeoff.
232
+ - Footprint ~1.2 GB fp32 / ~0.6 GB bf16; QAT Q4_0/Q8_0 + ONNX (`onnx-community/embeddinggemma-300m-ONNX`).
233
+
234
+ ### 5.5 llama.cpp support (🦙 Llama Champion)
235
+
236
+ The two **language** models run on llama.cpp; the two **audio** models use their own runtimes. Running the core LLM on
237
+ llama.cpp earns the badge.
238
+
239
+ | Model | llama.cpp? | Runtime | Notes |
240
+ |---|---|---|---|
241
+ | `openbmb/MiniCPM5-1B` | ✅ | llama.cpp / Ollama | `openbmb/MiniCPM5-1B-GGUF` (Q4_K_M 688 MB); standard Llama arch |
242
+ | `google/embeddinggemma-300m` | ✅ | `llama-embedding` | `gemma-embedding` arch (build ≥ b6384); `ggml-org/embeddinggemma-300M-GGUF` |
243
+ | ASR (Nemotron / Parakeet) | ❌ | NeMo / transformers | FastConformer-RNNT |
244
+ | `pipecat-ai/smart-turn-v3` | ❌ | ONNX Runtime | Whisper encoder + classifier head |
245
+
246
+ Verify-before-ship: EmbeddingGemma GGUF quant accuracy drifts ([#19040](https://github.com/ggml-org/llama.cpp/issues/19040))
247
+ → prefer Q8_0 or keep the embedder on sentence-transformers; MiniCPM5 tool-calling via llama.cpp is a pending PR.
248
+
249
+ ---
250
+
251
+ ## 6. Agent context design (built for a 1B brain)
252
+
253
+ Core principle: **the 1B model is a router + arg-filler. All heavy work (crawl, summarize, score, rank, dedup) lives in
254
+ code.** Keep live context to ~800–1200 tokens of *curated* view, never raw data.
255
+
256
+ - **Layer A — System (static, ~250 tok):** identity/character; hackathon hard rules (≤32B, Gradio Space, demo video) so
257
+ it self-filters infeasible ideas; targeted prizes (biases ideation); reply style (short, one question at a time);
258
+ explicit tool-use instructions + the canonical jargon vocabulary (so it can self-correct, §7).
259
+ - **Layer B — Session state (re-rendered each turn by code, ~300 tok):** user profile; locked decisions (track, side
260
+ quests, models); **idea board** (2–3 candidates, one line + scores); compact "projects already seen" summary.
261
+ - **Layer C — Ephemeral (~300 tok):** last 2–3 turns; the most recent tool result as a **refined card** (not raw JSON).
262
+
263
+ ---
264
+
265
+ ## 7. Agent tool design
266
+
267
+ Few tools, few params each, short descriptions (1B-friendly). Heavy logic in code.
268
+
269
+ **Jargon alias layer (input normalization).** Before any tool call and before display, run ASR/user text through a
270
+ deterministic fuzzy/alias map over our small CLOSED vocab (model names, prize names, side quests) — e.g. RapidFuzz
271
+ `token_set_ratio` / double-metaphone — mapping "neutron"/"nemo tron" → Nemotron, "mini cpm" → MiniCPM5, "zero gpu" →
272
+ ZeroGPU. Surface the correction ("heard: neutron → Nemotron") as a trust-building, slightly delightful moment.
273
+
274
+ **Research — investigate existing projects (the core value).** Data = `build-small-hackathon` org Spaces, pre-crawled
275
+ into a local snapshot + EmbeddingGemma index (keeps Off the Grid at runtime).
276
+
277
+ | Tool | Signature | Returns (refined) | Heavy work |
278
+ |---|---|---|---|
279
+ | `list_projects` | `(track?, sort?)` | top-N project cards | HF Hub API + summarize |
280
+ | `search_projects` | `(query)` | top 5 cards | EmbeddingGemma retrieval |
281
+ | `get_project` | `(id)` | card + overlap-vs-board verdict | code computes overlap |
282
+ | `find_whitespace` | `()` | under-explored niches | cluster the index, find gaps |
283
+
284
+ `find_whitespace` is the originality engine (TTW judges originality) — it names where nobody has built yet.
285
+
286
+ **Ideation / state.**
287
+
288
+ | Tool | Signature | Purpose |
289
+ |---|---|---|
290
+ | `save_idea` | `(title, pitch, track, models[], side_quests[])` | add/update a candidate on the idea board |
291
+ | `score_idea` | `(id)` | fixed (hardcoded) rubric → scores + gaps; the 1B only triggers + verbalizes |
292
+ | `compare_ideas` | `()` | rank the board, articulate tradeoffs |
293
+ | `make_plan` | `(id)` | build plan + side quests it picks up "for free" |
294
+ | `update_profile` | `(field, value)` | record skills/time/prefs → Layer B |
295
+ | `set_target` | `(side_quests[])` | change targeted prizes → updates Layer A bias |
296
+
297
+ ---
298
+
299
+ ## 8. Agent loop (single-hop + degradation ladder)
300
+
301
+ ```
302
+ on user input (text; or voice → batch ASR → text):
303
+ normalize via jargon alias layer
304
+ ctx = LayerA + render_state(LayerB) + last_turns + last_tool_card
305
+ out = MiniCPM5(ctx, tools=TOOLS, enable_thinking=False, temp=0.7) # → tool_call | reply
306
+ try: parse XML tool call
307
+ except / invalid name|args (vs JSON-Schema): # degradation ladder
308
+ retry once (temp≈0.3, "emit ONLY one valid tool call")
309
+ still bad → run a safe default tool (find_whitespace) so the screen never freezes
310
+ if tool_call: card = run_tool(out); reply = MiniCPM5(ctx + card) # single follow-up, no long ReAct
311
+ empty/zero result → canned advisor line (never say nothing)
312
+ stream reply tokens → custom UI | token watchdog: no token in N s → "trying again" visual (not dead air)
313
+ update_state(LayerB)
314
+ ```
315
+
316
+ **Max one tool-call then reply.** A 1B can't sustain multi-step ReAct; wrap multi-step flows (`search → get_project →
317
+ score`) into one *code* "research" action the model calls once. The degradation ladder is a **first-class UX surface**
318
+ (§11), not an error branch — the screen is the only feedback channel (no TTS).
319
+
320
+ ---
321
+
322
+ ## 9. ZeroGPU deployment notes
323
+
324
+ - `import spaces; @spaces.GPU(duration=…)`. GPU only inside decorated fns; **Gradio-SDK Space only** (no Docker ZeroGPU).
325
+ - Load models at **module level**, `.to('cuda')` once (emulated until first real GPU call); real compute inside the
326
+ decorator. torch 2.8+; **no `torch.compile`** (use AOT). Quota PRO ~40 min/day → never idle-hold the GPU.
327
+ - **Frontend → backend via `@gradio/client`** (`client.predict`/`submit`), NOT raw fetch — it forwards the HF iframe
328
+ auth headers ZeroGPU needs for quota. Generator `@app.api()` endpoints stream tokens over SSE.
329
+ - All four models fit in `large` (48 GB). Keep each `@spaces.GPU` call short for queue priority.
330
+
331
+ ---
332
+
333
+ ## 10. Modal — offline pipeline (build-time only → preserves Off the Grid)
334
+
335
+ Modal = build-time; runtime never calls it. This is how we claim **both** 🟢 Modal **and** 🔌 Off the Grid. Modal also
336
+ trains the 🎯 Well-Tuned LoRA. Crawl org Spaces → embed with EmbeddingGemma → build vector index → commit to a Volume;
337
+ the Space ships the index artifact and searches locally.
338
+
339
+ ```python
340
+ import modal
341
+ app = modal.App("bsh-advisor-index")
342
+ CACHE = "/cache"
343
+ hf_vol = modal.Volume.from_name("hf-cache", create_if_missing=True)
344
+ index_vol = modal.Volume.from_name("bsh-index", create_if_missing=True)
345
+ image = (modal.Image.debian_slim("3.12")
346
+ .pip_install("sentence-transformers", "huggingface_hub", "requests", "numpy", "faiss-cpu")
347
+ .env({"HF_HUB_ENABLE_HF_TRANSFER": "1", "HF_HOME": CACHE}))
348
+
349
+ @app.function(image=image) # CPU: crawl one Space
350
+ def crawl(space_id):
351
+ import requests
352
+ m = requests.get(f"https://huggingface.co/api/spaces/{space_id}").json()
353
+ return {"id": space_id, "text": m.get("cardData", {}).get("short_description", "")}
354
+
355
+ @app.cls(image=image, gpu="T4", volumes={CACHE: hf_vol}, scaledown_window=120)
356
+ class Embedder:
357
+ @modal.enter()
358
+ def load(self):
359
+ from sentence_transformers import SentenceTransformer
360
+ self.m = SentenceTransformer("google/embeddinggemma-300m", cache_folder=CACHE, truncate_dim=256)
361
+ @modal.method()
362
+ def embed(self, docs): return self.m.encode_document(docs).tolist()
363
+
364
+ @app.local_entrypoint()
365
+ def main(org="build-small-hackathon"):
366
+ import requests
367
+ ids = [s["id"] for s in requests.get(f"https://huggingface.co/api/spaces?author={org}").json()]
368
+ docs = [d for d in crawl.map(ids) if d["text"]]
369
+ vecs = Embedder().embed.remote([d["text"] for d in docs])
370
+ # build FAISS index → write to index_vol → index_vol.commit()
371
+ ```
372
+
373
+ - T4/CPU is plenty (pennies; $30/mo free credits). `gpu="T4"`/`"L4"` (note `"A10"`, not `"A10G"`). `volume.commit()`
374
+ after writing. `HF_TOKEN` via `modal.Secret` for the gated EmbeddingGemma download. Crawl on CPU, embed on GPU.
375
+
376
+ ---
377
+
378
+ ## 11. Frontend — `gr.Server` custom UI (🎨 Off-Brand)
379
+
380
+ No TTS → the **visual output is the agent's "voice"**; it must carry the delight (this is what earns Off-Brand, and the
381
+ TTW polish + Best Demo score). The visual world is **The Unwritten Almanac** (§2): a candlelit tree-hollow with a heavy
382
+ open grimoire as the hero component.
383
+
384
+ - `gradio.Server` is a FastAPI subclass keeping Gradio's queue/SSE/ZeroGPU/`gradio_client` engine while serving **your
385
+ own frontend**. `@app.api(name=...)` fns are queued + client-callable + ZeroGPU-aware; plain `@app.post()` are not.
386
+ ```python
387
+ from gradio import Server
388
+ from fastapi.responses import HTMLResponse
389
+ app = Server()
390
+
391
+ @app.api(name="agent_turn", concurrency_limit=2)
392
+ async def agent_turn(message: str):
393
+ for token in run_agent_stream(message): # generator → SSE
394
+ yield token
395
+
396
+ @app.get("/", response_class=HTMLResponse) # custom UI replaces Gradio's default page
397
+ async def home(): return open("index.html").read()
398
+ app.launch()
399
+ ```
400
+ - Frontend calls via `@gradio/client`: `client.predict("/agent_turn", { message })` (NOT raw fetch — ZeroGPU auth).
401
+ - **UI surfaces (the grimoire is the canvas):** streaming reply = ink writing itself (typewriter on already-streaming
402
+ tokens); `search_projects`/overlap → **bleed** animation + page-number citations (real titles on hover);
403
+ `find_whitespace` → **gold bloom** + sprouting leaf + a one-shaft light-mask ("the page chooses you");
404
+ `score_idea` → **wax-seal** five-quadrant stamp; the riffling inked pages (fast page-flip of real Spaces) double as
405
+ the project-wall; export = the torn-grimoire PNG artifact (§2). Jargon-correction toasts (§7) read as Mothback's
406
+ margin notes; optimistic-UI loading + watchdog states (§8) are her "the page is choosing its words…". Cheap SFX:
407
+ page-flip, quill scratch, wax-seal thunk.
408
+ - **Build the animation floor first:** safe typewriter + static stamp ships first (graceful degradation — the judges
409
+ credited this); upgrade the ink-bleed / gold-bloom / seal-press last.
410
+ - **Fallback:** the backend (`tools.py`/`agent.py`) is UI-agnostic — if gr.Server misbehaves, fall back to
411
+ `gr.Blocks` + `gr.HTML`, losing only the $1500 Off-Brand badge, never the submission.
412
+
413
+ ---
414
+
415
+ ## 12. Prize mapping
416
+
417
+ | Target | How it's earned |
418
+ |---|---|
419
+ | 🍄 Thousand Token Wood | **The Unwritten Almanac** (§2) — the bleed-citation wow IS the engine rendered; AI load-bearing; original |
420
+ | 🐜 Tiny Titan (special, $1.5k) | total ~1.9B, every model ≤4B; largest single = MiniCPM5 1.08B |
421
+ | 🔌 Off the Grid (badge) | all open weights run locally; offline index; no cloud inference at runtime |
422
+ | 🎯 Well-Tuned (badge) | published LoRA fine-tune of MiniCPM5 on the Hub (§10) → **6/6 badges** |
423
+ | 🎨 Off-Brand (badge + $1.5k) | `gr.Server` custom UI is the agent's output surface |
424
+ | 🏮 OpenBMB ($10k) | brain = MiniCPM5-1B ("OpenBMB pick") |
425
+ | 🟩 NVIDIA Quest (2× RTX 5080) | ASR = Nemotron (verify if Parakeet qualifies, §5.1) |
426
+ | 🦙 Llama Champion (badge) | MiniCPM5 + EmbeddingGemma run through llama.cpp (§5.5) |
427
+ | 📡 Sharing is Caring (badge) | publish the agent's tool-call trace to the Hub |
428
+ | 📓 Field Notes (badge) | this DESIGN.md → a build blog post |
429
+ | 🎖️ Bonus Quest Champion ($2k) | 6/6 badges (needs the Well-Tuned fine-tune) |
430
+ | 🤖 Best Agent ($1k) | real multi-tool loop: investigate → ideate → score → plan |
431
+ | 🟢 Modal ($20k credits) | offline crawl+embed + LoRA training on Modal (build-time, separated from runtime) |
432
+ | 🎬 Best Demo ($1k) | the mandatory demo video, made to sing (shared artifact + wow beat) |
433
+ | 🌀 OpenAI ($10k) | auto-entered ("across all submissions"); free lottery ticket, not a target |
434
+ | ❤️ Community Choice ($2k) | shareable tweetable artifact from the experience |
435
+
436
+ **6 badges** = Off the Grid, Well-Tuned, Off-Brand, Llama Champion, Sharing is Caring, Field Notes. Awards stack across
437
+ categories. Single-winner awards (Tiny Titan, Best Agent, Off-Brand, Best Demo) are eligibility ≠ win — the shared
438
+ lever is §11 custom-UI polish.
439
+
440
+ ---
441
+
442
+ ## 13. Risks / open items
443
+
444
+ 1. **Day-1 spikes are go/no-go** (§1): ZeroGPU hello-cuda build; gr.Server `@gradio/client` SSE streaming; Nemotron
445
+ batch in `@spaces.GPU` (else Parakeet). Do these before feature work.
446
+ 2. **EmbeddingGemma is gated** — accept Gemma terms + `HF_TOKEN` before any crawl/build.
447
+ 3. **MiniCPM5 tool-call reliability at 1B** — covered by the degradation ladder (§8); validate name+args in code.
448
+ 4. **NVIDIA Quest brand** — Parakeet is not "Nemotron"-branded; confirm eligibility with organizers or keep Nemotron
449
+ primary (§5.1).
450
+ 5. **Concept skin** — **chosen: The Unwritten Almanac** (§2). Make-or-break is the bleed/bloom hero animation; build the
451
+ safe typewriter + static-stamp floor first (graceful degradation), upgrade ink last. Watch the thin-org echo
452
+ threshold + the dry-but-benevolent tone (real cited Spaces, never punch at a named builder).
453
+ 6. **Param-budget claim** — document the 1.9B total in the README/Space card for Tiny Titan judging.
454
+
455
+ ---
456
+
457
+ ## 14. Build order
458
+
459
+ **Text-first vertical slice first; voice as a bonus layer.** Always keep a demoable artifact.
460
+
461
+ 0. **Day-1 spikes** (§1) — get the three go/no-go builds green.
462
+ 1. **`crawler.py` + Modal index** — crawl the org, embed with EmbeddingGemma, build the local index. *You immediately
463
+ see what everyone's building and where the whitespace is.*
464
+ 2. **`tools.py`** — research + ideation tools + the hardcoded `score_idea` rubric + the jargon alias layer, over the index.
465
+ 3. **`agent.py`** — 3-layer context + single-hop loop + degradation ladder, MiniCPM5 via `transformers` (self-parsed XML).
466
+ 4. **`app.py`** — `gr.Server` custom frontend (idea board, project/whitespace wall, streaming text), called via
467
+ `@gradio/client`; concept skin applied.
468
+ 5. **Well-Tuned LoRA** — small fine-tune on Modal → publish to Hub (→ 6/6 badges).
469
+ 6. **Polish + submission** — demo video + social post (Best Demo / Community Choice), publish agent trace (📡),
470
+ write up Field Notes (📓).
471
+
472
+ **Voice bonus (only if time):** push-to-talk record → batch ASR (Nemotron/Parakeet) in the existing `@spaces.GPU` call.
473
+ **Deferred:** real-time streaming ASR, in-browser Smart Turn + Silero VAD, FastRTC + TURN. (Original streaming design is
474
+ preserved in git history if revisited.)
475
+
476
+ ---
477
+
478
+ ## 15. Sources
479
+
480
+ **Models:** [nemotron-speech-streaming-en-0.6b](https://huggingface.co/nvidia/nemotron-speech-streaming-en-0.6b) ·
481
+ [parakeet-tdt-0.6b-v3](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3) ·
482
+ [parakeet transformers doc](https://huggingface.co/docs/transformers/en/model_doc/parakeet) ·
483
+ [MiniCPM5-1B](https://huggingface.co/openbmb/MiniCPM5-1B) · [MiniCPM5-1B-GGUF](https://huggingface.co/openbmb/MiniCPM5-1B-GGUF) ·
484
+ [smart-turn-v3](https://huggingface.co/pipecat-ai/smart-turn-v3) · [embeddinggemma-300m](https://huggingface.co/google/embeddinggemma-300m)
485
+
486
+ **Platforms:** [ZeroGPU docs](https://huggingface.co/docs/hub/spaces-zerogpu) ·
487
+ [Introducing gradio.Server](https://huggingface.co/blog/introducing-gradio-server) · [Gradio Server Mode guide](https://www.gradio.app/guides/server-mode) ·
488
+ [Modal GPU](https://modal.com/docs/guide/gpu) · [Modal model weights](https://modal.com/docs/guide/model-weights) · [Modal pricing](https://modal.com/pricing) ·
489
+ [Build Small Hackathon](https://huggingface.co/build-small-hackathon)
490
+
491
+ *Verify-before-ship: Nemotron-in-ZeroGPU (Day-1 spike); MiniCPM5 license on the live card; NVIDIA Quest eligibility for
492
+ Parakeet; Smart Turn in-browser feature extraction (voice-later); llama.cpp MiniCPM5 tool-calling (pending PR).*
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JacobLinCool
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,13 +1,62 @@
1
  ---
2
  title: Hackathon Advisor
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: yellow
6
  sdk: gradio
7
  sdk_version: 6.16.0
8
- python_version: '3.13'
9
  app_file: app.py
10
- pinned: false
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Hackathon Advisor
3
+ emoji: "📜"
4
+ colorFrom: yellow
5
+ colorTo: green
6
  sdk: gradio
7
  sdk_version: 6.16.0
8
+ python_version: "3.11"
9
  app_file: app.py
10
+ pinned: true
11
+ license: mit
12
+ short_description: Originality advisor for Build Small.
13
+ tags:
14
+ - gradio
15
+ - build-small-hackathon
16
+ - small-models
17
+ - agent
18
+ - originality
19
+ - off-the-grid
20
  ---
21
 
22
+ # Hackathon Advisor
23
+
24
+ **Hackathon Advisor** is a text-first project advisor for the Build Small Hackathon. The user-facing experience is
25
+ **The Unwritten Almanac**: Mothback, an archivist of unwritten project pages, compares your idea against real Spaces in
26
+ the `build-small-hackathon` organization, finds under-explored territory, scores the idea, and drafts a practical build
27
+ plan.
28
+
29
+ The current milestone is a deployable, deterministic vertical slice:
30
+
31
+ - Local snapshot of public `build-small-hackathon` Spaces.
32
+ - Offline search over project titles, tags, models, and descriptions.
33
+ - Jargon correction for hackathon/model terms.
34
+ - One-turn advisor loop with overlap citations, whitespace suggestions, scoring, and plans.
35
+ - Custom `gradio.Server` frontend with streaming API events.
36
+
37
+ See [DESIGN.md](DESIGN.md) for the full product and model plan.
38
+
39
+ ## Run Locally
40
+
41
+ ```bash
42
+ python3.11 -m venv .venv
43
+ . .venv/bin/activate
44
+ pip install -r requirements.txt
45
+ python app.py
46
+ ```
47
+
48
+ Then open <http://127.0.0.1:7860>.
49
+
50
+ ## Refresh The Project Snapshot
51
+
52
+ ```bash
53
+ python scripts/crawl_hf_spaces.py --org build-small-hackathon --out data/projects.json
54
+ ```
55
+
56
+ The app uses `data/projects.json` at runtime, so deployed builds remain usable without live crawl calls.
57
+
58
+ ## Test
59
+
60
+ ```bash
61
+ pytest
62
+ ```
app.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Iterator
7
+
8
+ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
9
+ from gradio import Server
10
+
11
+ from hackathon_advisor.agent import AdvisorEngine
12
+ from hackathon_advisor.data import ProjectIndex
13
+
14
+
15
+ ROOT = Path(__file__).parent
16
+ STATIC_DIR = ROOT / "static"
17
+ DATA_PATH = ROOT / "data" / "projects.json"
18
+
19
+ index = ProjectIndex.from_file(DATA_PATH)
20
+ engine = AdvisorEngine(index)
21
+ app = Server()
22
+
23
+
24
+ def _json_event(payload: dict) -> str:
25
+ return json.dumps(payload, ensure_ascii=False)
26
+
27
+
28
+ @app.get("/", response_class=HTMLResponse)
29
+ def home() -> FileResponse:
30
+ return FileResponse(STATIC_DIR / "index.html")
31
+
32
+
33
+ @app.get("/static/{path:path}")
34
+ def static_file(path: str) -> FileResponse:
35
+ target = (STATIC_DIR / path).resolve()
36
+ if not str(target).startswith(str(STATIC_DIR.resolve())) or not target.is_file():
37
+ return JSONResponse({"error": "not found"}, status_code=404)
38
+ return FileResponse(target)
39
+
40
+
41
+ @app.get("/health")
42
+ def health() -> dict:
43
+ return {
44
+ "ok": True,
45
+ "projects": len(index.projects),
46
+ "snapshot_generated_at": index.generated_at,
47
+ }
48
+
49
+
50
+ @app.get("/api/bootstrap")
51
+ def bootstrap() -> dict:
52
+ return {
53
+ "project_count": len(index.projects),
54
+ "snapshot_generated_at": index.generated_at,
55
+ "top_projects": [project.to_public_dict() for project in index.top_projects(limit=8)],
56
+ "whitespace": [item.to_dict() for item in index.find_whitespace(limit=5)],
57
+ }
58
+
59
+
60
+ @app.api(name="agent_turn", concurrency_limit=4, stream_every=0.04)
61
+ def agent_turn(message: str, session_json: str = "{}") -> Iterator[str]:
62
+ try:
63
+ session = json.loads(session_json or "{}")
64
+ except json.JSONDecodeError:
65
+ session = {}
66
+
67
+ result = engine.turn(message, session)
68
+ yield _json_event(
69
+ {
70
+ "type": "start",
71
+ "corrections": [correction.to_dict() for correction in result.corrections],
72
+ "normalized_text": result.normalized_text,
73
+ "tool_events": [event.to_dict() for event in result.tool_events],
74
+ }
75
+ )
76
+
77
+ for chunk in result.stream_chunks():
78
+ yield _json_event({"type": "token", "text": chunk})
79
+
80
+ yield _json_event(
81
+ {
82
+ "type": "done",
83
+ "state": result.state,
84
+ "response": result.response,
85
+ "projects": [project.to_public_dict() for project in result.projects],
86
+ "whitespace": [item.to_dict() for item in result.whitespace],
87
+ "score": result.score.to_dict() if result.score else None,
88
+ "plan": result.plan,
89
+ "artifact": result.artifact,
90
+ }
91
+ )
92
+
93
+
94
+ if __name__ == "__main__":
95
+ app.launch(
96
+ server_name=os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"),
97
+ server_port=int(os.environ.get("GRADIO_SERVER_PORT", "7860")),
98
+ show_error=True,
99
+ )
data/projects.json ADDED
@@ -0,0 +1,2116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "generated_at": "2026-06-06T19:20:47+00:00",
3
+ "source": "https://huggingface.co/api/spaces?author=build-small-hackathon&limit=100",
4
+ "projects": [
5
+ {
6
+ "id": "build-small-hackathon/Advent_of_a_World_of_Flowering_Trees",
7
+ "title": "Advent Of A World Of Flowering Trees",
8
+ "summary": "This space is for Huggingface build small hackathon",
9
+ "tags": [
10
+ "gradio",
11
+ "region:us"
12
+ ],
13
+ "models": [
14
+ "CohereLabs/tiny-aya-global-GGUF"
15
+ ],
16
+ "datasets": [],
17
+ "likes": 1,
18
+ "sdk": "gradio",
19
+ "license": "mit",
20
+ "created_at": "2026-06-05T12:21:42.000Z",
21
+ "last_modified": "2026-06-05T19:53:45.000Z",
22
+ "host": "https://build-small-hackathon-advent-of-a-world-of-flowe-468ebe3.hf.space",
23
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Advent_of_a_World_of_Flowering_Trees"
24
+ },
25
+ {
26
+ "id": "build-small-hackathon/agent-swarm-workbench",
27
+ "title": "Backyard Demo Builder",
28
+ "summary": "Build tiny real-person demos before scaling custom software.",
29
+ "tags": [
30
+ "agents",
31
+ "ai-agents",
32
+ "backyard-ai",
33
+ "build-small-hackathon",
34
+ "demo-builder",
35
+ "gradio",
36
+ "real-estate",
37
+ "small-language-model"
38
+ ],
39
+ "models": [
40
+ "unsloth/gemma-4-12B-it-qat-GGUF",
41
+ "Qwen/Qwen2.5-7B-Instruct",
42
+ "nvidia/Nemotron-3.5-Content-Safety"
43
+ ],
44
+ "datasets": [],
45
+ "likes": 0,
46
+ "sdk": "gradio",
47
+ "license": "",
48
+ "created_at": "2026-06-03T07:06:14.000Z",
49
+ "last_modified": "2026-06-06T13:57:48.000Z",
50
+ "host": "https://build-small-hackathon-agent-swarm-workbench.hf.space",
51
+ "url": "https://huggingface.co/spaces/build-small-hackathon/agent-swarm-workbench"
52
+ },
53
+ {
54
+ "id": "build-small-hackathon/AI-agent-Evaluation-pipeline",
55
+ "title": "ai agent evaluation pipeline",
56
+ "summary": "Evaluate AI agents at Session, Trace & Span levels",
57
+ "tags": [
58
+ "agents",
59
+ "evaluation",
60
+ "gradio",
61
+ "llm",
62
+ "observability"
63
+ ],
64
+ "models": [
65
+ "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16",
66
+ "Qwen/Qwen3.6-27B",
67
+ "Qwen/QwQ-32B"
68
+ ],
69
+ "datasets": [
70
+ "build-small-hackathon/agent-eval-golden-dataset"
71
+ ],
72
+ "likes": 0,
73
+ "sdk": "gradio",
74
+ "license": "mit",
75
+ "created_at": "2026-06-05T13:27:06.000Z",
76
+ "last_modified": "2026-06-06T16:22:19.000Z",
77
+ "host": "https://build-small-hackathon-ai-agent-evaluation-pipeline.hf.space",
78
+ "url": "https://huggingface.co/spaces/build-small-hackathon/AI-agent-Evaluation-pipeline"
79
+ },
80
+ {
81
+ "id": "build-small-hackathon/AI-Puppet-Theater",
82
+ "title": "AI Puppet Theater",
83
+ "summary": "",
84
+ "tags": [
85
+ "gradio",
86
+ "region:us"
87
+ ],
88
+ "models": [
89
+ "openai/gpt-oss-20b"
90
+ ],
91
+ "datasets": [],
92
+ "likes": 1,
93
+ "sdk": "gradio",
94
+ "license": "",
95
+ "created_at": "2026-06-05T17:19:57.000Z",
96
+ "last_modified": "2026-06-05T17:19:58.000Z",
97
+ "host": "https://build-small-hackathon-ai-puppet-theater.hf.space",
98
+ "url": "https://huggingface.co/spaces/build-small-hackathon/AI-Puppet-Theater"
99
+ },
100
+ {
101
+ "id": "build-small-hackathon/ai-study-buddy",
102
+ "title": "Ai Study Buddy",
103
+ "summary": "AI Study Buddy — your smart learning companion 📚 ",
104
+ "tags": [
105
+ "gradio",
106
+ "region:us"
107
+ ],
108
+ "models": [
109
+ "meta-llama/Llama-3.1-8B-Instruct"
110
+ ],
111
+ "datasets": [],
112
+ "likes": 1,
113
+ "sdk": "gradio",
114
+ "license": "apache-2.0",
115
+ "created_at": "2026-06-01T13:45:43.000Z",
116
+ "last_modified": "2026-06-05T13:42:30.000Z",
117
+ "host": "https://build-small-hackathon-ai-study-buddy.hf.space",
118
+ "url": "https://huggingface.co/spaces/build-small-hackathon/ai-study-buddy"
119
+ },
120
+ {
121
+ "id": "build-small-hackathon/amnesiac",
122
+ "title": "AMNESIAC",
123
+ "summary": "Reverse-Turing webcam interrogation game.",
124
+ "tags": [
125
+ "gradio",
126
+ "region:us"
127
+ ],
128
+ "models": [
129
+ "openbmb/MiniCPM-o-4_5"
130
+ ],
131
+ "datasets": [],
132
+ "likes": 0,
133
+ "sdk": "gradio",
134
+ "license": "apache-2.0",
135
+ "created_at": "2026-06-05T09:51:49.000Z",
136
+ "last_modified": "2026-06-05T13:44:41.000Z",
137
+ "host": "https://build-small-hackathon-amnesiac.hf.space",
138
+ "url": "https://huggingface.co/spaces/build-small-hackathon/amnesiac"
139
+ },
140
+ {
141
+ "id": "build-small-hackathon/attention-firewall",
142
+ "title": "Attention Firewall",
143
+ "summary": "",
144
+ "tags": [
145
+ "gradio",
146
+ "region:us"
147
+ ],
148
+ "models": [],
149
+ "datasets": [],
150
+ "likes": 0,
151
+ "sdk": "gradio",
152
+ "license": "",
153
+ "created_at": "2026-06-05T23:02:34.000Z",
154
+ "last_modified": "2026-06-05T23:04:42.000Z",
155
+ "host": "https://build-small-hackathon-attention-firewall.hf.space",
156
+ "url": "https://huggingface.co/spaces/build-small-hackathon/attention-firewall"
157
+ },
158
+ {
159
+ "id": "build-small-hackathon/awaaz",
160
+ "title": "Apni Awaaz",
161
+ "summary": "",
162
+ "tags": [
163
+ "backyard-ai",
164
+ "dubbing",
165
+ "hindi",
166
+ "translation",
167
+ "tts"
168
+ ],
169
+ "models": [
170
+ "openai/whisper-medium",
171
+ "Qwen/Qwen2.5-7B-Instruct"
172
+ ],
173
+ "datasets": [],
174
+ "likes": 0,
175
+ "sdk": "gradio",
176
+ "license": "mit",
177
+ "created_at": "2026-06-06T13:16:20.000Z",
178
+ "last_modified": "2026-06-06T14:14:31.000Z",
179
+ "host": "https://build-small-hackathon-awaaz.hf.space",
180
+ "url": "https://huggingface.co/spaces/build-small-hackathon/awaaz"
181
+ },
182
+ {
183
+ "id": "build-small-hackathon/backyard-dudu-destroyer",
184
+ "title": "Backyard Dudu Destroyer",
185
+ "summary": "A gradio interface for starting VLA and policy",
186
+ "tags": [
187
+ "gradio",
188
+ "region:us"
189
+ ],
190
+ "models": [],
191
+ "datasets": [],
192
+ "likes": 0,
193
+ "sdk": "gradio",
194
+ "license": "apache-2.0",
195
+ "created_at": "2026-06-05T19:51:00.000Z",
196
+ "last_modified": "2026-06-05T19:51:00.000Z",
197
+ "host": "https://build-small-hackathon-backyard-dudu-destroyer.hf.space",
198
+ "url": "https://huggingface.co/spaces/build-small-hackathon/backyard-dudu-destroyer"
199
+ },
200
+ {
201
+ "id": "build-small-hackathon/backyard-raccoon-deterrent",
202
+ "title": "Backyard Raccoon Deterrent",
203
+ "summary": "Edge-AI raccoon deterrent. Tiny YOLO, fully offline.",
204
+ "tags": [
205
+ "build-small-hackathon",
206
+ "edge-ai",
207
+ "object-detection",
208
+ "raccoon",
209
+ "yolov8"
210
+ ],
211
+ "models": [],
212
+ "datasets": [],
213
+ "likes": 0,
214
+ "sdk": "gradio",
215
+ "license": "mit",
216
+ "created_at": "2026-06-05T19:17:40.000Z",
217
+ "last_modified": "2026-06-06T14:06:45.000Z",
218
+ "host": "https://build-small-hackathon-backyard-raccoon-deterrent.hf.space",
219
+ "url": "https://huggingface.co/spaces/build-small-hackathon/backyard-raccoon-deterrent"
220
+ },
221
+ {
222
+ "id": "build-small-hackathon/borderless",
223
+ "title": "Borderless",
224
+ "summary": "",
225
+ "tags": [
226
+ "gradio",
227
+ "region:us"
228
+ ],
229
+ "models": [
230
+ "Qwen/Qwen3.6-27B"
231
+ ],
232
+ "datasets": [],
233
+ "likes": 1,
234
+ "sdk": "gradio",
235
+ "license": "",
236
+ "created_at": "2026-06-05T05:26:45.000Z",
237
+ "last_modified": "2026-06-06T07:32:19.000Z",
238
+ "host": "https://build-small-hackathon-borderless.hf.space",
239
+ "url": "https://huggingface.co/spaces/build-small-hackathon/borderless"
240
+ },
241
+ {
242
+ "id": "build-small-hackathon/bridge-troll",
243
+ "title": "Bridge Troll",
244
+ "summary": "Talk your way past a fine-tuned troll, if your argument is ",
245
+ "tags": [
246
+ "gradio",
247
+ "region:us"
248
+ ],
249
+ "models": [
250
+ "10Pratibh/gorm-lora",
251
+ "Qwen/Qwen2.5-7B-Instruct"
252
+ ],
253
+ "datasets": [],
254
+ "likes": 0,
255
+ "sdk": "gradio",
256
+ "license": "mit",
257
+ "created_at": "2026-06-05T06:01:32.000Z",
258
+ "last_modified": "2026-06-06T10:11:58.000Z",
259
+ "host": "https://build-small-hackathon-bridge-troll.hf.space",
260
+ "url": "https://huggingface.co/spaces/build-small-hackathon/bridge-troll"
261
+ },
262
+ {
263
+ "id": "build-small-hackathon/briefing-32",
264
+ "title": "briefing-32",
265
+ "summary": "A 32B-class AI-news briefing the maker runs every 2 hours.",
266
+ "tags": [
267
+ "gradio",
268
+ "region:us"
269
+ ],
270
+ "models": [
271
+ "Qwen/Qwen3-32B"
272
+ ],
273
+ "datasets": [],
274
+ "likes": 0,
275
+ "sdk": "gradio",
276
+ "license": "apache-2.0",
277
+ "created_at": "2026-05-18T19:55:29.000Z",
278
+ "last_modified": "2026-05-18T20:10:19.000Z",
279
+ "host": "https://build-small-hackathon-briefing-32.hf.space",
280
+ "url": "https://huggingface.co/spaces/build-small-hackathon/briefing-32"
281
+ },
282
+ {
283
+ "id": "build-small-hackathon/business-order-assistant",
284
+ "title": "Business Order Assistant",
285
+ "summary": "AI that gets order in any format and creates an invoice",
286
+ "tags": [
287
+ "gradio",
288
+ "region:us"
289
+ ],
290
+ "models": [],
291
+ "datasets": [],
292
+ "likes": 1,
293
+ "sdk": "gradio",
294
+ "license": "mit",
295
+ "created_at": "2026-06-05T08:14:41.000Z",
296
+ "last_modified": "2026-06-05T21:16:24.000Z",
297
+ "host": "https://build-small-hackathon-business-order-assistant.hf.space",
298
+ "url": "https://huggingface.co/spaces/build-small-hackathon/business-order-assistant"
299
+ },
300
+ {
301
+ "id": "build-small-hackathon/Case-Lantern",
302
+ "title": "Case Lantern",
303
+ "summary": "",
304
+ "tags": [
305
+ "gradio",
306
+ "region:us"
307
+ ],
308
+ "models": [
309
+ "lastmass/Qwen3.5-Medical-GSPO"
310
+ ],
311
+ "datasets": [],
312
+ "likes": 0,
313
+ "sdk": "gradio",
314
+ "license": "apache-2.0",
315
+ "created_at": "2026-06-04T04:28:14.000Z",
316
+ "last_modified": "2026-06-04T07:51:49.000Z",
317
+ "host": "https://build-small-hackathon-case-lantern.hf.space",
318
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Case-Lantern"
319
+ },
320
+ {
321
+ "id": "build-small-hackathon/chorus",
322
+ "title": "Chorus",
323
+ "summary": "Discover the signal without having to read the noise",
324
+ "tags": [
325
+ "gradio",
326
+ "region:us"
327
+ ],
328
+ "models": [],
329
+ "datasets": [],
330
+ "likes": 0,
331
+ "sdk": "gradio",
332
+ "license": "mit",
333
+ "created_at": "2026-06-06T14:29:28.000Z",
334
+ "last_modified": "2026-06-06T15:25:57.000Z",
335
+ "host": "https://build-small-hackathon-chorus.hf.space",
336
+ "url": "https://huggingface.co/spaces/build-small-hackathon/chorus"
337
+ },
338
+ {
339
+ "id": "build-small-hackathon/CodeFlow",
340
+ "title": "CodeFlow",
341
+ "summary": "Turn Python code into a readable Mermaid.js flowchart 📊",
342
+ "tags": [
343
+ "gradio",
344
+ "region:us"
345
+ ],
346
+ "models": [
347
+ "unsloth/Qwen3-Coder-30B-A3B-Instruct-GGUF"
348
+ ],
349
+ "datasets": [],
350
+ "likes": 0,
351
+ "sdk": "gradio",
352
+ "license": "mit",
353
+ "created_at": "2026-06-05T14:31:59.000Z",
354
+ "last_modified": "2026-06-06T19:12:05.000Z",
355
+ "host": "https://build-small-hackathon-codeflow.hf.space",
356
+ "url": "https://huggingface.co/spaces/build-small-hackathon/CodeFlow"
357
+ },
358
+ {
359
+ "id": "build-small-hackathon/come-and-compare",
360
+ "title": "Come And Compare",
361
+ "summary": "Real-time price comparison across Amazon, Flipkart & Myntra",
362
+ "tags": [
363
+ "gradio",
364
+ "region:us"
365
+ ],
366
+ "models": [
367
+ "Qwen/Qwen2.5-7B-Instruct"
368
+ ],
369
+ "datasets": [],
370
+ "likes": 1,
371
+ "sdk": "gradio",
372
+ "license": "mit",
373
+ "created_at": "2026-05-27T07:54:43.000Z",
374
+ "last_modified": "2026-06-06T12:22:50.000Z",
375
+ "host": "https://build-small-hackathon-come-and-compare.hf.space",
376
+ "url": "https://huggingface.co/spaces/build-small-hackathon/come-and-compare"
377
+ },
378
+ {
379
+ "id": "build-small-hackathon/compliment-forest",
380
+ "title": "The Compliment Forest",
381
+ "summary": "Walk through a watercolor path of grounded encouragement.",
382
+ "tags": [
383
+ "build-small-hackathon",
384
+ "gradio",
385
+ "llama.cpp",
386
+ "local-first",
387
+ "watercolor"
388
+ ],
389
+ "models": [
390
+ "build-small-hackathon/compliment-forest-minicpm5-1b",
391
+ "build-small-hackathon/compliment-forest-flux-lora"
392
+ ],
393
+ "datasets": [
394
+ "build-small-hackathon/compliment-forest-sft",
395
+ "build-small-hackathon/compliment-forest-watercolor",
396
+ "build-small-hackathon/compliment-forest-traces"
397
+ ],
398
+ "likes": 0,
399
+ "sdk": "gradio",
400
+ "license": "",
401
+ "created_at": "2026-06-06T09:06:20.000Z",
402
+ "last_modified": "2026-06-06T09:16:04.000Z",
403
+ "host": "https://build-small-hackathon-compliment-forest.hf.space",
404
+ "url": "https://huggingface.co/spaces/build-small-hackathon/compliment-forest"
405
+ },
406
+ {
407
+ "id": "build-small-hackathon/Council-of-Tiny-Minds",
408
+ "title": "Council Of Tiny Minds",
409
+ "summary": "A faux chatroom where one user message wakes up a handful of",
410
+ "tags": [
411
+ "gradio",
412
+ "region:us"
413
+ ],
414
+ "models": [
415
+ "Qwen/Qwen3.5-9B"
416
+ ],
417
+ "datasets": [],
418
+ "likes": 0,
419
+ "sdk": "gradio",
420
+ "license": "mit",
421
+ "created_at": "2026-06-04T14:39:09.000Z",
422
+ "last_modified": "2026-06-04T14:45:24.000Z",
423
+ "host": "https://build-small-hackathon-council-of-tiny-minds.hf.space",
424
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Council-of-Tiny-Minds"
425
+ },
426
+ {
427
+ "id": "build-small-hackathon/Darwin-35B-A3B-Opus",
428
+ "title": "Darwin 35B A3B Opus",
429
+ "summary": "The child surpassed both parents — that is evolution",
430
+ "tags": [
431
+ "gradio",
432
+ "mcp-server",
433
+ "region:us"
434
+ ],
435
+ "models": [
436
+ "FINAL-Bench/Darwin-35B-A3B-Opus",
437
+ "huggingface/documentation-images"
438
+ ],
439
+ "datasets": [
440
+ "FINAL-Bench/ALL-Bench-Leaderboard",
441
+ "huggingface/documentation-images"
442
+ ],
443
+ "likes": 2,
444
+ "sdk": "gradio",
445
+ "license": "apache-2.0",
446
+ "created_at": "2026-05-19T21:57:08.000Z",
447
+ "last_modified": "2026-06-03T13:18:48.000Z",
448
+ "host": "https://build-small-hackathon-darwin-35b-a3b-opus.hf.space",
449
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Darwin-35B-A3B-Opus"
450
+ },
451
+ {
452
+ "id": "build-small-hackathon/dental-soap",
453
+ "title": "Dental SOAP",
454
+ "summary": "A small-model dental handoff for real patient stories.",
455
+ "tags": [
456
+ "agents",
457
+ "bilingual",
458
+ "healthcare",
459
+ "zero-gpu"
460
+ ],
461
+ "models": [
462
+ "Qwen/Qwen3-4B-Instruct-2507"
463
+ ],
464
+ "datasets": [],
465
+ "likes": 0,
466
+ "sdk": "gradio",
467
+ "license": "apache-2.0",
468
+ "created_at": "2026-06-05T08:34:32.000Z",
469
+ "last_modified": "2026-06-06T16:00:40.000Z",
470
+ "host": "https://build-small-hackathon-dental-soap.hf.space",
471
+ "url": "https://huggingface.co/spaces/build-small-hackathon/dental-soap"
472
+ },
473
+ {
474
+ "id": "build-small-hackathon/dm-order-desk",
475
+ "title": "Dm Order Desk",
476
+ "summary": "Turn messy DMs into clean orders.",
477
+ "tags": [
478
+ "gradio",
479
+ "region:us"
480
+ ],
481
+ "models": [
482
+ "Qwen/Qwen2.5-1.5B-Instruct"
483
+ ],
484
+ "datasets": [],
485
+ "likes": 0,
486
+ "sdk": "gradio",
487
+ "license": "mit",
488
+ "created_at": "2026-06-06T08:52:31.000Z",
489
+ "last_modified": "2026-06-06T11:49:44.000Z",
490
+ "host": "https://build-small-hackathon-dm-order-desk.hf.space",
491
+ "url": "https://huggingface.co/spaces/build-small-hackathon/dm-order-desk"
492
+ },
493
+ {
494
+ "id": "build-small-hackathon/dream-customs",
495
+ "title": "Dream Customs",
496
+ "summary": "Turn dream declarations into a playful next-day pact.",
497
+ "tags": [
498
+ "build-small-hackathon",
499
+ "dream-journal",
500
+ "gradio",
501
+ "minicpm"
502
+ ],
503
+ "models": [
504
+ "openbmb/MiniCPM5-1B",
505
+ "openbmb/MiniCPM-V-4.6"
506
+ ],
507
+ "datasets": [],
508
+ "likes": 0,
509
+ "sdk": "gradio",
510
+ "license": "mit",
511
+ "created_at": "2026-06-05T04:17:23.000Z",
512
+ "last_modified": "2026-06-06T09:27:58.000Z",
513
+ "host": "https://build-small-hackathon-dream-customs.hf.space",
514
+ "url": "https://huggingface.co/spaces/build-small-hackathon/dream-customs"
515
+ },
516
+ {
517
+ "id": "build-small-hackathon/dream-museum",
518
+ "title": "Dream Museum",
519
+ "summary": "Draw a dream · Describe it · Watch it materialize",
520
+ "tags": [
521
+ "gradio",
522
+ "region:us"
523
+ ],
524
+ "models": [
525
+ "stabilityai/stable-diffusion-xl-base-1.0",
526
+ "xinsir/controlnet-scribble-sdxl-1.0"
527
+ ],
528
+ "datasets": [],
529
+ "likes": 0,
530
+ "sdk": "gradio",
531
+ "license": "mit",
532
+ "created_at": "2026-06-06T11:56:15.000Z",
533
+ "last_modified": "2026-06-06T18:11:57.000Z",
534
+ "host": "https://build-small-hackathon-dream-museum.hf.space",
535
+ "url": "https://huggingface.co/spaces/build-small-hackathon/dream-museum"
536
+ },
537
+ {
538
+ "id": "build-small-hackathon/dreamwall-mc",
539
+ "title": "DreamWall MC",
540
+ "summary": "",
541
+ "tags": [
542
+ "agent-trace",
543
+ "art",
544
+ "codex",
545
+ "game",
546
+ "gradio",
547
+ "minecraft",
548
+ "small-models"
549
+ ],
550
+ "models": [],
551
+ "datasets": [],
552
+ "likes": 0,
553
+ "sdk": "gradio",
554
+ "license": "apache-2.0",
555
+ "created_at": "2026-06-05T10:11:24.000Z",
556
+ "last_modified": "2026-06-06T15:19:12.000Z",
557
+ "host": "https://build-small-hackathon-dreamwall-mc.hf.space",
558
+ "url": "https://huggingface.co/spaces/build-small-hackathon/dreamwall-mc"
559
+ },
560
+ {
561
+ "id": "build-small-hackathon/ducks-happen",
562
+ "title": "Ducks Happen",
563
+ "summary": "Rubber ducks materialize here.",
564
+ "tags": [
565
+ "art",
566
+ "flux",
567
+ "fun",
568
+ "generative-art",
569
+ "rubber-duck"
570
+ ],
571
+ "models": [
572
+ "black-forest-labs/FLUX.1-schnell"
573
+ ],
574
+ "datasets": [],
575
+ "likes": 0,
576
+ "sdk": "gradio",
577
+ "license": "mit",
578
+ "created_at": "2026-06-06T09:29:26.000Z",
579
+ "last_modified": "2026-06-06T13:27:27.000Z",
580
+ "host": "https://build-small-hackathon-ducks-happen.hf.space",
581
+ "url": "https://huggingface.co/spaces/build-small-hackathon/ducks-happen"
582
+ },
583
+ {
584
+ "id": "build-small-hackathon/espressocheese-chess-demo",
585
+ "title": "Espressocheese Chess Demo",
586
+ "summary": "",
587
+ "tags": [
588
+ "gradio",
589
+ "region:us"
590
+ ],
591
+ "models": [],
592
+ "datasets": [],
593
+ "likes": 0,
594
+ "sdk": "gradio",
595
+ "license": "",
596
+ "created_at": "2026-06-05T18:02:39.000Z",
597
+ "last_modified": "2026-06-05T18:02:39.000Z",
598
+ "host": "https://build-small-hackathon-espressocheese-chess-demo.hf.space",
599
+ "url": "https://huggingface.co/spaces/build-small-hackathon/espressocheese-chess-demo"
600
+ },
601
+ {
602
+ "id": "build-small-hackathon/exam-panic-rescue",
603
+ "title": "Exam Panic Rescue",
604
+ "summary": "",
605
+ "tags": [
606
+ "gradio",
607
+ "region:us"
608
+ ],
609
+ "models": [
610
+ "nvidia/Nemotron-Mini-4B-Instruct",
611
+ "openbmb/MiniCPM4.1-8B",
612
+ "openbmb/MiniCPM4.1-8B-GGUF"
613
+ ],
614
+ "datasets": [],
615
+ "likes": 0,
616
+ "sdk": "gradio",
617
+ "license": "mit",
618
+ "created_at": "2026-06-05T10:07:01.000Z",
619
+ "last_modified": "2026-06-06T18:14:13.000Z",
620
+ "host": "https://build-small-hackathon-exam-panic-rescue.hf.space",
621
+ "url": "https://huggingface.co/spaces/build-small-hackathon/exam-panic-rescue"
622
+ },
623
+ {
624
+ "id": "build-small-hackathon/Family-Bill-Assistant",
625
+ "title": "Family Bill Assistant",
626
+ "summary": "Smart AI Agent that simplifies and categorizes family bills",
627
+ "tags": [
628
+ "gradio",
629
+ "region:us"
630
+ ],
631
+ "models": [
632
+ "nvidia/NVIDIA-Nemotron-Parse-v1.2",
633
+ "openbmb/MiniCPM-V-4.6",
634
+ "openbmb/MiniCPM4.1-8B"
635
+ ],
636
+ "datasets": [],
637
+ "likes": 0,
638
+ "sdk": "gradio",
639
+ "license": "mit",
640
+ "created_at": "2026-06-06T10:19:08.000Z",
641
+ "last_modified": "2026-06-06T19:16:30.000Z",
642
+ "host": "https://build-small-hackathon-family-bill-assistant.hf.space",
643
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Family-Bill-Assistant"
644
+ },
645
+ {
646
+ "id": "build-small-hackathon/First-Principle-AI",
647
+ "title": "First-Principle AI",
648
+ "summary": "Phase-3 Q8 GGUF lab console with llama.cpp.",
649
+ "tags": [
650
+ "build-small-hackathon",
651
+ "chatbot",
652
+ "gguf",
653
+ "gradio",
654
+ "llama-cpp",
655
+ "model-lab",
656
+ "zerogpu"
657
+ ],
658
+ "models": [
659
+ "build-small-hackathon/phase-3-gguf"
660
+ ],
661
+ "datasets": [],
662
+ "likes": 0,
663
+ "sdk": "gradio",
664
+ "license": "mit",
665
+ "created_at": "2026-06-04T21:54:27.000Z",
666
+ "last_modified": "2026-06-06T04:54:02.000Z",
667
+ "host": "https://build-small-hackathon-first-principle-ai.hf.space",
668
+ "url": "https://huggingface.co/spaces/build-small-hackathon/First-Principle-AI"
669
+ },
670
+ {
671
+ "id": "build-small-hackathon/gemma-task-agent-trace",
672
+ "title": "Gemma Task Agent Trace",
673
+ "summary": "A lightweight task agent prototype with visual reasoning tra",
674
+ "tags": [
675
+ "gradio",
676
+ "region:us"
677
+ ],
678
+ "models": [],
679
+ "datasets": [],
680
+ "likes": 0,
681
+ "sdk": "gradio",
682
+ "license": "",
683
+ "created_at": "2026-06-02T09:07:39.000Z",
684
+ "last_modified": "2026-06-02T09:11:42.000Z",
685
+ "host": "https://build-small-hackathon-gemma-task-agent-trace.hf.space",
686
+ "url": "https://huggingface.co/spaces/build-small-hackathon/gemma-task-agent-trace"
687
+ },
688
+ {
689
+ "id": "build-small-hackathon/gemma4chat",
690
+ "title": "Gemma4chat",
691
+ "summary": "",
692
+ "tags": [
693
+ "docker",
694
+ "region:us"
695
+ ],
696
+ "models": [],
697
+ "datasets": [],
698
+ "likes": 0,
699
+ "sdk": "docker",
700
+ "license": "",
701
+ "created_at": "2026-06-06T09:09:50.000Z",
702
+ "last_modified": "2026-06-06T09:11:50.000Z",
703
+ "host": "https://build-small-hackathon-gemma4chat.hf.space",
704
+ "url": "https://huggingface.co/spaces/build-small-hackathon/gemma4chat"
705
+ },
706
+ {
707
+ "id": "build-small-hackathon/gitopadesh",
708
+ "title": "Gitopadesh",
709
+ "summary": "The Bhagavad Gita as a living advisor powered by AI",
710
+ "tags": [
711
+ "gradio",
712
+ "region:us"
713
+ ],
714
+ "models": [
715
+ "Qwen/Qwen2.5-7B-Instruct",
716
+ "sentence-transformers/all-MiniLM-L6-v2"
717
+ ],
718
+ "datasets": [],
719
+ "likes": 0,
720
+ "sdk": "gradio",
721
+ "license": "mit",
722
+ "created_at": "2026-06-05T03:38:32.000Z",
723
+ "last_modified": "2026-06-06T09:23:11.000Z",
724
+ "host": "https://build-small-hackathon-gitopadesh.hf.space",
725
+ "url": "https://huggingface.co/spaces/build-small-hackathon/gitopadesh"
726
+ },
727
+ {
728
+ "id": "build-small-hackathon/GRM-2.6-Opus",
729
+ "title": "GRM-2.6-Opus",
730
+ "summary": "",
731
+ "tags": [
732
+ "gradio",
733
+ "region:us"
734
+ ],
735
+ "models": [
736
+ "OrionLLM/GRM-2.6-Opus"
737
+ ],
738
+ "datasets": [],
739
+ "likes": 1,
740
+ "sdk": "gradio",
741
+ "license": "",
742
+ "created_at": "2026-05-19T22:04:00.000Z",
743
+ "last_modified": "2026-05-14T15:48:55.000Z",
744
+ "host": "https://build-small-hackathon-grm-2-6-opus.hf.space",
745
+ "url": "https://huggingface.co/spaces/build-small-hackathon/GRM-2.6-Opus"
746
+ },
747
+ {
748
+ "id": "build-small-hackathon/GTROX",
749
+ "title": "GTROX",
750
+ "summary": "",
751
+ "tags": [
752
+ "gradio",
753
+ "region:us"
754
+ ],
755
+ "models": [
756
+ "openai/gpt-oss-20b"
757
+ ],
758
+ "datasets": [],
759
+ "likes": 0,
760
+ "sdk": "gradio",
761
+ "license": "",
762
+ "created_at": "2026-06-04T19:08:30.000Z",
763
+ "last_modified": "2026-06-04T19:08:31.000Z",
764
+ "host": "https://build-small-hackathon-gtrox.hf.space",
765
+ "url": "https://huggingface.co/spaces/build-small-hackathon/GTROX"
766
+ },
767
+ {
768
+ "id": "build-small-hackathon/guitar-singalong",
769
+ "title": "Guitar Singalong Generator",
770
+ "summary": "",
771
+ "tags": [
772
+ "accompaniment",
773
+ "audio",
774
+ "demucs",
775
+ "guitar",
776
+ "music",
777
+ "musicgen"
778
+ ],
779
+ "models": [
780
+ "facebook/musicgen-melody"
781
+ ],
782
+ "datasets": [],
783
+ "likes": 0,
784
+ "sdk": "gradio",
785
+ "license": "mit",
786
+ "created_at": "2026-06-05T16:48:57.000Z",
787
+ "last_modified": "2026-06-05T23:31:38.000Z",
788
+ "host": "https://build-small-hackathon-guitar-singalong.hf.space",
789
+ "url": "https://huggingface.co/spaces/build-small-hackathon/guitar-singalong"
790
+ },
791
+ {
792
+ "id": "build-small-hackathon/her",
793
+ "title": "Her · हेर",
794
+ "summary": "A detective for your Claude Code sessions",
795
+ "tags": [
796
+ "gradio",
797
+ "region:us"
798
+ ],
799
+ "models": [
800
+ "nvidia/Nemotron-Mini-4B-Instruct"
801
+ ],
802
+ "datasets": [],
803
+ "likes": 2,
804
+ "sdk": "gradio",
805
+ "license": "",
806
+ "created_at": "2026-06-06T14:39:33.000Z",
807
+ "last_modified": "2026-06-06T14:39:39.000Z",
808
+ "host": "https://build-small-hackathon-her.hf.space",
809
+ "url": "https://huggingface.co/spaces/build-small-hackathon/her"
810
+ },
811
+ {
812
+ "id": "build-small-hackathon/InContext",
813
+ "title": "InContext",
814
+ "summary": "Learn reusable English expressions from real-world content.",
815
+ "tags": [
816
+ "gradio",
817
+ "region:us"
818
+ ],
819
+ "models": [
820
+ "Qwen/Qwen2.5-0.5B-Instruct"
821
+ ],
822
+ "datasets": [],
823
+ "likes": 0,
824
+ "sdk": "gradio",
825
+ "license": "mit",
826
+ "created_at": "2026-06-06T00:37:36.000Z",
827
+ "last_modified": "2026-06-06T02:50:47.000Z",
828
+ "host": "https://build-small-hackathon-incontext.hf.space",
829
+ "url": "https://huggingface.co/spaces/build-small-hackathon/InContext"
830
+ },
831
+ {
832
+ "id": "build-small-hackathon/innerspace",
833
+ "title": "InnerSpace",
834
+ "summary": "Privacy-first offline cognitive journal & reflection coach",
835
+ "tags": [
836
+ "gradio",
837
+ "region:us"
838
+ ],
839
+ "models": [
840
+ "build-small-hackathon/inner-space-1b-sft-cbt",
841
+ "openbmb/MiniCPM5-1B-SFT"
842
+ ],
843
+ "datasets": [],
844
+ "likes": 0,
845
+ "sdk": "gradio",
846
+ "license": "",
847
+ "created_at": "2026-06-06T08:39:42.000Z",
848
+ "last_modified": "2026-06-06T16:56:17.000Z",
849
+ "host": "https://build-small-hackathon-innerspace.hf.space",
850
+ "url": "https://huggingface.co/spaces/build-small-hackathon/innerspace"
851
+ },
852
+ {
853
+ "id": "build-small-hackathon/investigative-news-agent",
854
+ "title": "Investigative News Agent",
855
+ "summary": "Traceable news analysis assistant for independent journalist",
856
+ "tags": [
857
+ "gradio",
858
+ "region:us"
859
+ ],
860
+ "models": [],
861
+ "datasets": [],
862
+ "likes": 0,
863
+ "sdk": "gradio",
864
+ "license": "mit",
865
+ "created_at": "2026-06-03T23:27:21.000Z",
866
+ "last_modified": "2026-06-03T23:27:21.000Z",
867
+ "host": "https://build-small-hackathon-investigative-news-agent.hf.space",
868
+ "url": "https://huggingface.co/spaces/build-small-hackathon/investigative-news-agent"
869
+ },
870
+ {
871
+ "id": "build-small-hackathon/jackailocal",
872
+ "title": "Jackailocal",
873
+ "summary": "",
874
+ "tags": [
875
+ "gradio",
876
+ "region:us"
877
+ ],
878
+ "models": [],
879
+ "datasets": [],
880
+ "likes": 0,
881
+ "sdk": "gradio",
882
+ "license": "",
883
+ "created_at": "2026-06-04T18:38:48.000Z",
884
+ "last_modified": "2026-06-04T18:38:48.000Z",
885
+ "host": "https://build-small-hackathon-jackailocal.hf.space",
886
+ "url": "https://huggingface.co/spaces/build-small-hackathon/jackailocal"
887
+ },
888
+ {
889
+ "id": "build-small-hackathon/job-search-assistant",
890
+ "title": "Job Searcher",
891
+ "summary": "Drop your resume. Get matches with reasoning.",
892
+ "tags": [
893
+ "distillation",
894
+ "gguf",
895
+ "jobs",
896
+ "llama-cpp",
897
+ "lora",
898
+ "qwen3",
899
+ "resume"
900
+ ],
901
+ "models": [
902
+ "emrekuruu/job-searcher-qwen3-8B",
903
+ "emrekuruu/job-searcher-qwen3-8B-gguf"
904
+ ],
905
+ "datasets": [
906
+ "emrekuruu/job-search-distill"
907
+ ],
908
+ "likes": 0,
909
+ "sdk": "gradio",
910
+ "license": "",
911
+ "created_at": "2026-06-06T15:20:53.000Z",
912
+ "last_modified": "2026-06-06T14:14:51.000Z",
913
+ "host": "https://build-small-hackathon-job-search-assistant.hf.space",
914
+ "url": "https://huggingface.co/spaces/build-small-hackathon/job-search-assistant"
915
+ },
916
+ {
917
+ "id": "build-small-hackathon/karim-lab",
918
+ "title": "Karim Lab",
919
+ "summary": "Small-model legal workflow assistant prototype.",
920
+ "tags": [
921
+ "gradio",
922
+ "region:us"
923
+ ],
924
+ "models": [],
925
+ "datasets": [],
926
+ "likes": 0,
927
+ "sdk": "gradio",
928
+ "license": "mit",
929
+ "created_at": "2026-06-04T22:20:12.000Z",
930
+ "last_modified": "2026-06-04T22:26:21.000Z",
931
+ "host": "https://build-small-hackathon-karim-lab.hf.space",
932
+ "url": "https://huggingface.co/spaces/build-small-hackathon/karim-lab"
933
+ },
934
+ {
935
+ "id": "build-small-hackathon/Kasualdad_LFED",
936
+ "title": "Kasualdad LFED",
937
+ "summary": "Local First Education Data Analytics for school admins",
938
+ "tags": [
939
+ "duckdb",
940
+ "education",
941
+ "gguf",
942
+ "gradio",
943
+ "llama-cpp",
944
+ "local-first",
945
+ "text-to-sql"
946
+ ],
947
+ "models": [
948
+ "mradermacher/Qwen2.5-Coder-7B-Instruct-GGUF",
949
+ "unsloth/Qwen2.5-Coder-7B-Instruct"
950
+ ],
951
+ "datasets": [],
952
+ "likes": 0,
953
+ "sdk": "gradio",
954
+ "license": "",
955
+ "created_at": "2026-06-06T01:32:03.000Z",
956
+ "last_modified": "2026-06-06T16:36:54.000Z",
957
+ "host": "https://build-small-hackathon-kasualdad-lfed.hf.space",
958
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Kasualdad_LFED"
959
+ },
960
+ {
961
+ "id": "build-small-hackathon/Kintsugi-Garden",
962
+ "title": "The Kintsugi Garden",
963
+ "summary": "",
964
+ "tags": [
965
+ "gradio",
966
+ "region:us"
967
+ ],
968
+ "models": [
969
+ "microsoft/Phi-4-mini-instruct",
970
+ "Qwen/Qwen3-8B"
971
+ ],
972
+ "datasets": [],
973
+ "likes": 0,
974
+ "sdk": "gradio",
975
+ "license": "mit",
976
+ "created_at": "2026-06-04T13:02:44.000Z",
977
+ "last_modified": "2026-06-06T15:16:43.000Z",
978
+ "host": "https://build-small-hackathon-kintsugi-garden.hf.space",
979
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Kintsugi-Garden"
980
+ },
981
+ {
982
+ "id": "build-small-hackathon/legawa",
983
+ "title": "Legawa",
984
+ "summary": "",
985
+ "tags": [
986
+ "gradio",
987
+ "region:us"
988
+ ],
989
+ "models": [
990
+ "Qwen/Qwen3.5-27B",
991
+ "Qwen/Qwen3.5-9B"
992
+ ],
993
+ "datasets": [],
994
+ "likes": 1,
995
+ "sdk": "gradio",
996
+ "license": "mit",
997
+ "created_at": "2026-05-29T11:56:15.000Z",
998
+ "last_modified": "2026-05-29T12:41:46.000Z",
999
+ "host": "https://build-small-hackathon-legawa.hf.space",
1000
+ "url": "https://huggingface.co/spaces/build-small-hackathon/legawa"
1001
+ },
1002
+ {
1003
+ "id": "build-small-hackathon/LocalDuo",
1004
+ "title": "LocalDuo",
1005
+ "summary": "🇰🇷✨ LocalDuo - Learn Korean from Documents",
1006
+ "tags": [
1007
+ "gradio",
1008
+ "region:us"
1009
+ ],
1010
+ "models": [
1011
+ "Qwen/Qwen3.5-2B",
1012
+ "Qwen/Qwen3.5-9B"
1013
+ ],
1014
+ "datasets": [],
1015
+ "likes": 1,
1016
+ "sdk": "gradio",
1017
+ "license": "",
1018
+ "created_at": "2026-06-06T08:23:40.000Z",
1019
+ "last_modified": "2026-06-06T12:29:55.000Z",
1020
+ "host": "https://build-small-hackathon-localduo.hf.space",
1021
+ "url": "https://huggingface.co/spaces/build-small-hackathon/LocalDuo"
1022
+ },
1023
+ {
1024
+ "id": "build-small-hackathon/lolaby",
1025
+ "title": "Lolaby",
1026
+ "summary": "AI-powered lullabies.",
1027
+ "tags": [
1028
+ "agentic",
1029
+ "backyard-ai",
1030
+ "build-small-hackathon",
1031
+ "children",
1032
+ "fine-tuned",
1033
+ "gradio",
1034
+ "llama-cpp",
1035
+ "lullaby",
1036
+ "on-device",
1037
+ "small-models",
1038
+ "text-to-audio"
1039
+ ],
1040
+ "models": [
1041
+ "build-small-hackathon/lolaby-llama-3b",
1042
+ "openbmb/MiniCPM-V-4_6",
1043
+ "hexgrad/Kokoro-82M"
1044
+ ],
1045
+ "datasets": [
1046
+ "build-small-hackathon/lolaby-traces"
1047
+ ],
1048
+ "likes": 2,
1049
+ "sdk": "gradio",
1050
+ "license": "llama3.2",
1051
+ "created_at": "2026-06-05T16:18:44.000Z",
1052
+ "last_modified": "2026-06-06T09:59:58.000Z",
1053
+ "host": "https://build-small-hackathon-lolaby.hf.space",
1054
+ "url": "https://huggingface.co/spaces/build-small-hackathon/lolaby"
1055
+ },
1056
+ {
1057
+ "id": "build-small-hackathon/lore-lens",
1058
+ "title": "Local in 30s — Lore Lens",
1059
+ "summary": "Snap anything abroad. Get what locals actually know.",
1060
+ "tags": [
1061
+ "culture",
1062
+ "minicpm",
1063
+ "multimodal",
1064
+ "travel",
1065
+ "tts"
1066
+ ],
1067
+ "models": [
1068
+ "openbmb/MiniCPM-V-4.6",
1069
+ "openbmb/VoxCPM2"
1070
+ ],
1071
+ "datasets": [],
1072
+ "likes": 0,
1073
+ "sdk": "gradio",
1074
+ "license": "mit",
1075
+ "created_at": "2026-06-06T16:45:47.000Z",
1076
+ "last_modified": "2026-06-06T17:31:59.000Z",
1077
+ "host": "https://build-small-hackathon-lore-lens.hf.space",
1078
+ "url": "https://huggingface.co/spaces/build-small-hackathon/lore-lens"
1079
+ },
1080
+ {
1081
+ "id": "build-small-hackathon/lovegpt",
1082
+ "title": "loveGPT",
1083
+ "summary": "",
1084
+ "tags": [
1085
+ "gradio",
1086
+ "region:us"
1087
+ ],
1088
+ "models": [
1089
+ "microsoft/Phi-4-mini-instruct"
1090
+ ],
1091
+ "datasets": [],
1092
+ "likes": 3,
1093
+ "sdk": "gradio",
1094
+ "license": "mit",
1095
+ "created_at": "2026-06-05T11:55:29.000Z",
1096
+ "last_modified": "2026-06-06T19:13:02.000Z",
1097
+ "host": "https://build-small-hackathon-lovegpt.hf.space",
1098
+ "url": "https://huggingface.co/spaces/build-small-hackathon/lovegpt"
1099
+ },
1100
+ {
1101
+ "id": "build-small-hackathon/Mediassist",
1102
+ "title": "Mediassist",
1103
+ "summary": "Medical Reasoning Assistant for Underserved Communities",
1104
+ "tags": [
1105
+ "gradio",
1106
+ "region:us"
1107
+ ],
1108
+ "models": [
1109
+ "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
1110
+ ],
1111
+ "datasets": [],
1112
+ "likes": 0,
1113
+ "sdk": "gradio",
1114
+ "license": "mit",
1115
+ "created_at": "2026-06-05T12:33:40.000Z",
1116
+ "last_modified": "2026-06-05T13:53:13.000Z",
1117
+ "host": "https://build-small-hackathon-mediassist.hf.space",
1118
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Mediassist"
1119
+ },
1120
+ {
1121
+ "id": "build-small-hackathon/metabolic-forensics",
1122
+ "title": "Metabolic Forensics",
1123
+ "summary": "N-of-1 biosignal evidence engine. Forensics, not coaching.",
1124
+ "tags": [
1125
+ "backyard-ai",
1126
+ "build-small-hackathon"
1127
+ ],
1128
+ "models": [],
1129
+ "datasets": [],
1130
+ "likes": 0,
1131
+ "sdk": "gradio",
1132
+ "license": "mit",
1133
+ "created_at": "2026-06-04T21:48:56.000Z",
1134
+ "last_modified": "2026-06-04T21:49:01.000Z",
1135
+ "host": "https://build-small-hackathon-metabolic-forensics.hf.space",
1136
+ "url": "https://huggingface.co/spaces/build-small-hackathon/metabolic-forensics"
1137
+ },
1138
+ {
1139
+ "id": "build-small-hackathon/my-build-small-hackathon",
1140
+ "title": "My Build Small Hackathon",
1141
+ "summary": "This is my submission for the build-small-hackathon",
1142
+ "tags": [
1143
+ "gradio",
1144
+ "region:us"
1145
+ ],
1146
+ "models": [],
1147
+ "datasets": [],
1148
+ "likes": 0,
1149
+ "sdk": "gradio",
1150
+ "license": "apache-2.0",
1151
+ "created_at": "2026-06-05T07:32:13.000Z",
1152
+ "last_modified": "2026-06-05T07:50:03.000Z",
1153
+ "host": "https://build-small-hackathon-my-build-small-hackathon.hf.space",
1154
+ "url": "https://huggingface.co/spaces/build-small-hackathon/my-build-small-hackathon"
1155
+ },
1156
+ {
1157
+ "id": "build-small-hackathon/mycelium",
1158
+ "title": "Mycelium",
1159
+ "summary": "Local-first personal knowledge agent",
1160
+ "tags": [
1161
+ "gradio",
1162
+ "region:us"
1163
+ ],
1164
+ "models": [
1165
+ "nvidia/Nemotron-Mini-4B-Instruct",
1166
+ "Qwen/Qwen2.5-VL-7B-Instruct",
1167
+ "sentence-transformers/all-MiniLM-L6-v2"
1168
+ ],
1169
+ "datasets": [],
1170
+ "likes": 0,
1171
+ "sdk": "gradio",
1172
+ "license": "mit",
1173
+ "created_at": "2026-06-06T04:29:40.000Z",
1174
+ "last_modified": "2026-06-06T05:36:51.000Z",
1175
+ "host": "https://build-small-hackathon-mycelium.hf.space",
1176
+ "url": "https://huggingface.co/spaces/build-small-hackathon/mycelium"
1177
+ },
1178
+ {
1179
+ "id": "build-small-hackathon/Myspace",
1180
+ "title": "Myspace",
1181
+ "summary": "Todo",
1182
+ "tags": [
1183
+ "gradio",
1184
+ "region:us"
1185
+ ],
1186
+ "models": [
1187
+ "openai/gpt-oss-20b"
1188
+ ],
1189
+ "datasets": [],
1190
+ "likes": 0,
1191
+ "sdk": "gradio",
1192
+ "license": "mit",
1193
+ "created_at": "2026-06-04T20:20:12.000Z",
1194
+ "last_modified": "2026-06-04T20:20:13.000Z",
1195
+ "host": "https://build-small-hackathon-myspace.hf.space",
1196
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Myspace"
1197
+ },
1198
+ {
1199
+ "id": "build-small-hackathon/mythograph-atelier",
1200
+ "title": "Mythograph Atelier",
1201
+ "summary": "AI abstract art with personal meaning",
1202
+ "tags": [
1203
+ "gradio",
1204
+ "region:us"
1205
+ ],
1206
+ "models": [
1207
+ "black-forest-labs/FLUX.2-klein-4B",
1208
+ "lmstudio-community/Qwen3.5-0.8B-GGUF"
1209
+ ],
1210
+ "datasets": [],
1211
+ "likes": 0,
1212
+ "sdk": "gradio",
1213
+ "license": "",
1214
+ "created_at": "2026-06-06T09:28:40.000Z",
1215
+ "last_modified": "2026-06-06T18:49:06.000Z",
1216
+ "host": "https://build-small-hackathon-mythograph-atelier.hf.space",
1217
+ "url": "https://huggingface.co/spaces/build-small-hackathon/mythograph-atelier"
1218
+ },
1219
+ {
1220
+ "id": "build-small-hackathon/neighbourhood-guide",
1221
+ "title": "Neighbourhood Guide",
1222
+ "summary": "",
1223
+ "tags": [
1224
+ "gradio",
1225
+ "region:us"
1226
+ ],
1227
+ "models": [
1228
+ "CohereLabs/tiny-aya-global",
1229
+ "CohereLabs/cohere-transcribe-03-2026",
1230
+ "nvidia/magpie_tts_multilingual_357m"
1231
+ ],
1232
+ "datasets": [],
1233
+ "likes": 0,
1234
+ "sdk": "gradio",
1235
+ "license": "apache-2.0",
1236
+ "created_at": "2026-06-06T04:15:44.000Z",
1237
+ "last_modified": "2026-06-06T18:36:24.000Z",
1238
+ "host": "https://build-small-hackathon-neighbourhood-guide.hf.space",
1239
+ "url": "https://huggingface.co/spaces/build-small-hackathon/neighbourhood-guide"
1240
+ },
1241
+ {
1242
+ "id": "build-small-hackathon/neilA",
1243
+ "title": "First Contact",
1244
+ "summary": "Teach an alien that knows words but has never lived a life.",
1245
+ "tags": [
1246
+ "gradio",
1247
+ "region:us"
1248
+ ],
1249
+ "models": [
1250
+ "Qwen/Qwen2.5-7B-Instruct"
1251
+ ],
1252
+ "datasets": [],
1253
+ "likes": 0,
1254
+ "sdk": "gradio",
1255
+ "license": "mit",
1256
+ "created_at": "2026-06-05T15:41:30.000Z",
1257
+ "last_modified": "2026-06-05T21:55:20.000Z",
1258
+ "host": "https://build-small-hackathon-neila.hf.space",
1259
+ "url": "https://huggingface.co/spaces/build-small-hackathon/neilA"
1260
+ },
1261
+ {
1262
+ "id": "build-small-hackathon/NextClue",
1263
+ "title": "NextClue",
1264
+ "summary": "Research assistant to help design the next-best experiments ",
1265
+ "tags": [
1266
+ "gradio",
1267
+ "region:us"
1268
+ ],
1269
+ "models": [],
1270
+ "datasets": [],
1271
+ "likes": 0,
1272
+ "sdk": "gradio",
1273
+ "license": "",
1274
+ "created_at": "2026-06-04T17:44:39.000Z",
1275
+ "last_modified": "2026-06-04T17:44:40.000Z",
1276
+ "host": "https://build-small-hackathon-nextclue.hf.space",
1277
+ "url": "https://huggingface.co/spaces/build-small-hackathon/NextClue"
1278
+ },
1279
+ {
1280
+ "id": "build-small-hackathon/NEXUS-Visual-Weaver",
1281
+ "title": "NEXUS Visual Weaver",
1282
+ "summary": "hackaton project from NEXUS OS and doppleground foundation",
1283
+ "tags": [
1284
+ "gradio",
1285
+ "region:us"
1286
+ ],
1287
+ "models": [
1288
+ "black-forest-labs/FLUX.2-klein-4B"
1289
+ ],
1290
+ "datasets": [],
1291
+ "likes": 1,
1292
+ "sdk": "gradio",
1293
+ "license": "apache-2.0",
1294
+ "created_at": "2026-06-06T06:12:35.000Z",
1295
+ "last_modified": "2026-06-06T08:22:34.000Z",
1296
+ "host": "https://build-small-hackathon-nexus-visual-weaver.hf.space",
1297
+ "url": "https://huggingface.co/spaces/build-small-hackathon/NEXUS-Visual-Weaver"
1298
+ },
1299
+ {
1300
+ "id": "build-small-hackathon/Objection-Your-Honour",
1301
+ "title": "Objection Your Honour",
1302
+ "summary": "A whimsical game",
1303
+ "tags": [
1304
+ "gradio",
1305
+ "region:us"
1306
+ ],
1307
+ "models": [
1308
+ "openai/gpt-oss-20b"
1309
+ ],
1310
+ "datasets": [],
1311
+ "likes": 0,
1312
+ "sdk": "gradio",
1313
+ "license": "",
1314
+ "created_at": "2026-06-04T06:55:49.000Z",
1315
+ "last_modified": "2026-06-04T06:55:51.000Z",
1316
+ "host": "https://build-small-hackathon-objection-your-honour.hf.space",
1317
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Objection-Your-Honour"
1318
+ },
1319
+ {
1320
+ "id": "build-small-hackathon/octopus-ai",
1321
+ "title": "Octopus AI — Stress Test the Octopus",
1322
+ "summary": "Can you break a self-monitoring modular AI?",
1323
+ "tags": [
1324
+ "gradio",
1325
+ "region:us"
1326
+ ],
1327
+ "models": [
1328
+ "CognitiveEngineering/octopus-dev-checkpoints",
1329
+ "mistralai/Mixtral-8x7B-v0.1"
1330
+ ],
1331
+ "datasets": [],
1332
+ "likes": 1,
1333
+ "sdk": "gradio",
1334
+ "license": "apache-2.0",
1335
+ "created_at": "2026-06-05T17:47:03.000Z",
1336
+ "last_modified": "2026-06-06T12:09:30.000Z",
1337
+ "host": "https://build-small-hackathon-octopus-ai.hf.space",
1338
+ "url": "https://huggingface.co/spaces/build-small-hackathon/octopus-ai"
1339
+ },
1340
+ {
1341
+ "id": "build-small-hackathon/oneiros",
1342
+ "title": "Oneiros",
1343
+ "summary": "Map your dreams with a small model — no ChatGPT API.",
1344
+ "tags": [
1345
+ "gradio",
1346
+ "region:us"
1347
+ ],
1348
+ "models": [
1349
+ "Qwen/Qwen2.5-7B-Instruct-GGUF"
1350
+ ],
1351
+ "datasets": [
1352
+ "adindamochamad/oneiros-agent-traces"
1353
+ ],
1354
+ "likes": 0,
1355
+ "sdk": "gradio",
1356
+ "license": "",
1357
+ "created_at": "2026-06-03T02:34:28.000Z",
1358
+ "last_modified": "2026-06-03T13:12:12.000Z",
1359
+ "host": "https://build-small-hackathon-oneiros.hf.space",
1360
+ "url": "https://huggingface.co/spaces/build-small-hackathon/oneiros"
1361
+ },
1362
+ {
1363
+ "id": "build-small-hackathon/oracle-ternary-flame",
1364
+ "title": "Oracle Ternary Flame",
1365
+ "summary": "Cryptic oracle speaking in cosmic, elemental poetry.",
1366
+ "tags": [
1367
+ "gradio",
1368
+ "region:us"
1369
+ ],
1370
+ "models": [
1371
+ "google/gemma-4-12B-it",
1372
+ "keypa/oracle-gemma4-12b-lora"
1373
+ ],
1374
+ "datasets": [],
1375
+ "likes": 0,
1376
+ "sdk": "gradio",
1377
+ "license": "mit",
1378
+ "created_at": "2026-06-05T10:03:17.000Z",
1379
+ "last_modified": "2026-06-05T11:03:19.000Z",
1380
+ "host": "https://build-small-hackathon-oracle-ternary-flame.hf.space",
1381
+ "url": "https://huggingface.co/spaces/build-small-hackathon/oracle-ternary-flame"
1382
+ },
1383
+ {
1384
+ "id": "build-small-hackathon/patient-virtuel-dentiste",
1385
+ "title": "Patient Virtuel · Hygiéniste Pro",
1386
+ "summary": "",
1387
+ "tags": [
1388
+ "gradio",
1389
+ "region:us"
1390
+ ],
1391
+ "models": [
1392
+ "Qwen/Qwen3.6-27B"
1393
+ ],
1394
+ "datasets": [],
1395
+ "likes": 0,
1396
+ "sdk": "gradio",
1397
+ "license": "apache-2.0",
1398
+ "created_at": "2026-06-04T10:23:14.000Z",
1399
+ "last_modified": "2026-06-04T15:33:38.000Z",
1400
+ "host": "https://build-small-hackathon-patient-virtuel-dentiste.hf.space",
1401
+ "url": "https://huggingface.co/spaces/build-small-hackathon/patient-virtuel-dentiste"
1402
+ },
1403
+ {
1404
+ "id": "build-small-hackathon/pawmap",
1405
+ "title": "PawMap",
1406
+ "summary": "Mapeamento colaborativo de animais de rua com IA",
1407
+ "tags": [
1408
+ "docker",
1409
+ "region:us"
1410
+ ],
1411
+ "models": [
1412
+ "meta-llama/Llama-3.2-11B-Vision-Instruct"
1413
+ ],
1414
+ "datasets": [],
1415
+ "likes": 0,
1416
+ "sdk": "docker",
1417
+ "license": "mit",
1418
+ "created_at": "2026-06-04T17:55:07.000Z",
1419
+ "last_modified": "2026-06-06T16:37:03.000Z",
1420
+ "host": "https://build-small-hackathon-pawmap.hf.space",
1421
+ "url": "https://huggingface.co/spaces/build-small-hackathon/pawmap"
1422
+ },
1423
+ {
1424
+ "id": "build-small-hackathon/persona-atlas",
1425
+ "title": "Persona Atlas",
1426
+ "summary": "Build personas of public figures and compare how they think",
1427
+ "tags": [
1428
+ "gradio",
1429
+ "region:us"
1430
+ ],
1431
+ "models": [
1432
+ "google/gemma-4-26B-A4B-it",
1433
+ "microsoft/harrier-oss-v1-0.6b"
1434
+ ],
1435
+ "datasets": [],
1436
+ "likes": 0,
1437
+ "sdk": "gradio",
1438
+ "license": "",
1439
+ "created_at": "2026-06-06T06:46:34.000Z",
1440
+ "last_modified": "2026-06-06T11:53:48.000Z",
1441
+ "host": "https://build-small-hackathon-persona-atlas.hf.space",
1442
+ "url": "https://huggingface.co/spaces/build-small-hackathon/persona-atlas"
1443
+ },
1444
+ {
1445
+ "id": "build-small-hackathon/planpalette",
1446
+ "title": "PlanPalette",
1447
+ "summary": "",
1448
+ "tags": [
1449
+ "gradio",
1450
+ "region:us"
1451
+ ],
1452
+ "models": [
1453
+ "Lykon/dreamshaper-xl-lightning"
1454
+ ],
1455
+ "datasets": [],
1456
+ "likes": 0,
1457
+ "sdk": "gradio",
1458
+ "license": "",
1459
+ "created_at": "2026-06-06T12:33:33.000Z",
1460
+ "last_modified": "2026-06-06T18:11:05.000Z",
1461
+ "host": "https://build-small-hackathon-planpalette.hf.space",
1462
+ "url": "https://huggingface.co/spaces/build-small-hackathon/planpalette"
1463
+ },
1464
+ {
1465
+ "id": "build-small-hackathon/pocket-weather-theater",
1466
+ "title": "Pocket Weather Theater",
1467
+ "summary": "Tiny local weather plays from pocket props.",
1468
+ "tags": [
1469
+ "build-small-hackathon",
1470
+ "gradio",
1471
+ "local-inference",
1472
+ "thousand-token-wood",
1473
+ "tiny-models",
1474
+ "transformers"
1475
+ ],
1476
+ "models": [
1477
+ "HuggingFaceTB/SmolLM2-135M-Instruct",
1478
+ "PratikBuilds/pocket-weather-theater-smollm2-135m-lora"
1479
+ ],
1480
+ "datasets": [],
1481
+ "likes": 0,
1482
+ "sdk": "gradio",
1483
+ "license": "mit",
1484
+ "created_at": "2026-06-05T20:54:06.000Z",
1485
+ "last_modified": "2026-06-06T16:13:34.000Z",
1486
+ "host": "https://build-small-hackathon-pocket-weather-theater.hf.space",
1487
+ "url": "https://huggingface.co/spaces/build-small-hackathon/pocket-weather-theater"
1488
+ },
1489
+ {
1490
+ "id": "build-small-hackathon/rarebirds",
1491
+ "title": "rarebirds",
1492
+ "summary": "Aircraft rarity classifier with live ADS-B map",
1493
+ "tags": [
1494
+ "ads-b",
1495
+ "aircraft",
1496
+ "gemma",
1497
+ "gradio"
1498
+ ],
1499
+ "models": [
1500
+ "google/gemma-3-27b-it"
1501
+ ],
1502
+ "datasets": [],
1503
+ "likes": 0,
1504
+ "sdk": "gradio",
1505
+ "license": "",
1506
+ "created_at": "2026-06-04T04:50:56.000Z",
1507
+ "last_modified": "2026-06-06T05:03:49.000Z",
1508
+ "host": "https://build-small-hackathon-rarebirds.hf.space",
1509
+ "url": "https://huggingface.co/spaces/build-small-hackathon/rarebirds"
1510
+ },
1511
+ {
1512
+ "id": "build-small-hackathon/receipt_scanner",
1513
+ "title": "Receipt Scanner",
1514
+ "summary": "",
1515
+ "tags": [
1516
+ "gradio",
1517
+ "region:us"
1518
+ ],
1519
+ "models": [
1520
+ "openbmb/MiniCPM-V",
1521
+ "openbmb/MiniCPM-V-4.6"
1522
+ ],
1523
+ "datasets": [],
1524
+ "likes": 0,
1525
+ "sdk": "gradio",
1526
+ "license": "",
1527
+ "created_at": "2026-06-05T19:57:46.000Z",
1528
+ "last_modified": "2026-06-06T14:51:10.000Z",
1529
+ "host": "https://build-small-hackathon-receipt-scanner.hf.space",
1530
+ "url": "https://huggingface.co/spaces/build-small-hackathon/receipt_scanner"
1531
+ },
1532
+ {
1533
+ "id": "build-small-hackathon/Retail-Insight-AI",
1534
+ "title": "Retail Insight AI Pro",
1535
+ "summary": "",
1536
+ "tags": [
1537
+ "gradio",
1538
+ "region:us"
1539
+ ],
1540
+ "models": [],
1541
+ "datasets": [],
1542
+ "likes": 0,
1543
+ "sdk": "gradio",
1544
+ "license": "mit",
1545
+ "created_at": "2026-06-03T14:00:54.000Z",
1546
+ "last_modified": "2026-06-05T15:12:12.000Z",
1547
+ "host": "https://build-small-hackathon-retail-insight-ai.hf.space",
1548
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Retail-Insight-AI"
1549
+ },
1550
+ {
1551
+ "id": "build-small-hackathon/roadb-other-screen",
1552
+ "title": "Road B: The Other Screen",
1553
+ "summary": "Talk to the self who chose differently.",
1554
+ "tags": [
1555
+ "build-small-hackathon",
1556
+ "custom-frontend",
1557
+ "gguf",
1558
+ "gradio",
1559
+ "gradio-server",
1560
+ "interactive-fiction",
1561
+ "llama-cpp",
1562
+ "modal",
1563
+ "qwen",
1564
+ "small-models"
1565
+ ],
1566
+ "models": [
1567
+ "unsloth/Qwen3.5-9B-GGUF"
1568
+ ],
1569
+ "datasets": [],
1570
+ "likes": 1,
1571
+ "sdk": "gradio",
1572
+ "license": "mit",
1573
+ "created_at": "2026-05-26T00:51:52.000Z",
1574
+ "last_modified": "2026-06-05T07:11:03.000Z",
1575
+ "host": "https://build-small-hackathon-roadb-other-screen.hf.space",
1576
+ "url": "https://huggingface.co/spaces/build-small-hackathon/roadb-other-screen"
1577
+ },
1578
+ {
1579
+ "id": "build-small-hackathon/roast-my-repo",
1580
+ "title": "Roast My Repo",
1581
+ "summary": "AI-powered brutal code review for your GitHub repos",
1582
+ "tags": [
1583
+ "gradio",
1584
+ "region:us"
1585
+ ],
1586
+ "models": [
1587
+ "openbmb/MiniCPM4-8B"
1588
+ ],
1589
+ "datasets": [],
1590
+ "likes": 0,
1591
+ "sdk": "gradio",
1592
+ "license": "mit",
1593
+ "created_at": "2026-06-04T07:33:38.000Z",
1594
+ "last_modified": "2026-06-06T11:14:59.000Z",
1595
+ "host": "https://build-small-hackathon-roast-my-repo.hf.space",
1596
+ "url": "https://huggingface.co/spaces/build-small-hackathon/roast-my-repo"
1597
+ },
1598
+ {
1599
+ "id": "build-small-hackathon/SlideAI",
1600
+ "title": "SlideAI",
1601
+ "summary": "",
1602
+ "tags": [
1603
+ "gradio",
1604
+ "region:us"
1605
+ ],
1606
+ "models": [
1607
+ "Qwen/Qwen2.5-7B-Instruct"
1608
+ ],
1609
+ "datasets": [],
1610
+ "likes": 1,
1611
+ "sdk": "gradio",
1612
+ "license": "mit",
1613
+ "created_at": "2026-06-06T10:01:57.000Z",
1614
+ "last_modified": "2026-06-06T12:20:39.000Z",
1615
+ "host": "https://build-small-hackathon-slideai.hf.space",
1616
+ "url": "https://huggingface.co/spaces/build-small-hackathon/SlideAI"
1617
+ },
1618
+ {
1619
+ "id": "build-small-hackathon/Spooky-From-a-Distance",
1620
+ "title": "Spooky From A Distance",
1621
+ "summary": "",
1622
+ "tags": [
1623
+ "gradio",
1624
+ "region:us"
1625
+ ],
1626
+ "models": [],
1627
+ "datasets": [],
1628
+ "likes": 0,
1629
+ "sdk": "gradio",
1630
+ "license": "apache-2.0",
1631
+ "created_at": "2026-06-05T01:38:09.000Z",
1632
+ "last_modified": "2026-06-05T01:38:09.000Z",
1633
+ "host": "https://build-small-hackathon-spooky-from-a-distance.hf.space",
1634
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Spooky-From-a-Distance"
1635
+ },
1636
+ {
1637
+ "id": "build-small-hackathon/storybook",
1638
+ "title": "Storybook",
1639
+ "summary": "",
1640
+ "tags": [
1641
+ "gradio",
1642
+ "region:us"
1643
+ ],
1644
+ "models": [],
1645
+ "datasets": [],
1646
+ "likes": 0,
1647
+ "sdk": "gradio",
1648
+ "license": "",
1649
+ "created_at": "2026-06-04T21:27:20.000Z",
1650
+ "last_modified": "2026-06-04T21:27:20.000Z",
1651
+ "host": "https://build-small-hackathon-storybook.hf.space",
1652
+ "url": "https://huggingface.co/spaces/build-small-hackathon/storybook"
1653
+ },
1654
+ {
1655
+ "id": "build-small-hackathon/Structured-Data-Rescuer",
1656
+ "title": "Structured Data Rescuer",
1657
+ "summary": "Unstructured data is entered and structured data is returned",
1658
+ "tags": [
1659
+ "gradio",
1660
+ "region:us"
1661
+ ],
1662
+ "models": [
1663
+ "meta-llama/Llama-3.1-8B-Instruct"
1664
+ ],
1665
+ "datasets": [],
1666
+ "likes": 0,
1667
+ "sdk": "gradio",
1668
+ "license": "apache-2.0",
1669
+ "created_at": "2026-06-05T16:51:25.000Z",
1670
+ "last_modified": "2026-06-06T18:09:14.000Z",
1671
+ "host": "https://build-small-hackathon-structured-data-rescuer.hf.space",
1672
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Structured-Data-Rescuer"
1673
+ },
1674
+ {
1675
+ "id": "build-small-hackathon/surgical-tissue-segmentation",
1676
+ "title": "Surgical Tissue Segmentation",
1677
+ "summary": "",
1678
+ "tags": [
1679
+ "gradio",
1680
+ "region:us"
1681
+ ],
1682
+ "models": [
1683
+ "meta-llama/Llama-3.1-8B-Instruct",
1684
+ "sugan04/cholec-yolo26n-seg"
1685
+ ],
1686
+ "datasets": [],
1687
+ "likes": 1,
1688
+ "sdk": "gradio",
1689
+ "license": "mit",
1690
+ "created_at": "2026-06-03T06:44:42.000Z",
1691
+ "last_modified": "2026-06-06T08:02:16.000Z",
1692
+ "host": "https://build-small-hackathon-surgical-tissue-segmentation.hf.space",
1693
+ "url": "https://huggingface.co/spaces/build-small-hackathon/surgical-tissue-segmentation"
1694
+ },
1695
+ {
1696
+ "id": "build-small-hackathon/tarook",
1697
+ "title": "Tarook",
1698
+ "summary": "",
1699
+ "tags": [
1700
+ "docker",
1701
+ "region:us"
1702
+ ],
1703
+ "models": [],
1704
+ "datasets": [],
1705
+ "likes": 0,
1706
+ "sdk": "docker",
1707
+ "license": "",
1708
+ "created_at": "2026-05-14T23:10:44.000Z",
1709
+ "last_modified": "2026-05-14T23:11:42.000Z",
1710
+ "host": "https://build-small-hackathon-tarook.hf.space",
1711
+ "url": "https://huggingface.co/spaces/build-small-hackathon/tarook"
1712
+ },
1713
+ {
1714
+ "id": "build-small-hackathon/team_lunch_app_v1",
1715
+ "title": "Team Lunch App V1",
1716
+ "summary": "Individual & Team Lunch organizer",
1717
+ "tags": [
1718
+ "gradio",
1719
+ "region:us"
1720
+ ],
1721
+ "models": [
1722
+ "Qwen/Qwen2.5-1.5B-Instruct"
1723
+ ],
1724
+ "datasets": [],
1725
+ "likes": 0,
1726
+ "sdk": "gradio",
1727
+ "license": "apache-2.0",
1728
+ "created_at": "2026-06-05T08:39:39.000Z",
1729
+ "last_modified": "2026-06-06T00:01:47.000Z",
1730
+ "host": "https://build-small-hackathon-team-lunch-app-v1.hf.space",
1731
+ "url": "https://huggingface.co/spaces/build-small-hackathon/team_lunch_app_v1"
1732
+ },
1733
+ {
1734
+ "id": "build-small-hackathon/the-echo",
1735
+ "title": "The Echo",
1736
+ "summary": "an agentic tree of the lives you didn't live",
1737
+ "tags": [
1738
+ "gradio",
1739
+ "region:us"
1740
+ ],
1741
+ "models": [
1742
+ "Qwen/Qwen2.5-3B-Instruct"
1743
+ ],
1744
+ "datasets": [],
1745
+ "likes": 1,
1746
+ "sdk": "gradio",
1747
+ "license": "",
1748
+ "created_at": "2026-06-05T21:02:40.000Z",
1749
+ "last_modified": "2026-06-05T23:29:29.000Z",
1750
+ "host": "https://build-small-hackathon-the-echo.hf.space",
1751
+ "url": "https://huggingface.co/spaces/build-small-hackathon/the-echo"
1752
+ },
1753
+ {
1754
+ "id": "build-small-hackathon/the-i3-ghost-matrix-v5",
1755
+ "title": "The i3 Ghost Matrix v5.2",
1756
+ "summary": "A delightfully weird off-the-grid i3 ghost agent.",
1757
+ "tags": [
1758
+ "gradio",
1759
+ "region:us"
1760
+ ],
1761
+ "models": [],
1762
+ "datasets": [],
1763
+ "likes": 0,
1764
+ "sdk": "gradio",
1765
+ "license": "apache-2.0",
1766
+ "created_at": "2026-06-05T15:39:16.000Z",
1767
+ "last_modified": "2026-06-06T13:40:59.000Z",
1768
+ "host": "https://build-small-hackathon-the-i3-ghost-matrix-v5.hf.space",
1769
+ "url": "https://huggingface.co/spaces/build-small-hackathon/the-i3-ghost-matrix-v5"
1770
+ },
1771
+ {
1772
+ "id": "build-small-hackathon/the-pixelforge-klein",
1773
+ "title": "The Pixelforge Klein",
1774
+ "summary": "A tiny retro pixel-art game asset generator tool.",
1775
+ "tags": [
1776
+ "gradio",
1777
+ "region:us"
1778
+ ],
1779
+ "models": [],
1780
+ "datasets": [],
1781
+ "likes": 0,
1782
+ "sdk": "gradio",
1783
+ "license": "apache-2.0",
1784
+ "created_at": "2026-06-06T05:50:56.000Z",
1785
+ "last_modified": "2026-06-06T08:36:42.000Z",
1786
+ "host": "https://build-small-hackathon-the-pixelforge-klein.hf.space",
1787
+ "url": "https://huggingface.co/spaces/build-small-hackathon/the-pixelforge-klein"
1788
+ },
1789
+ {
1790
+ "id": "build-small-hackathon/The-Shrine",
1791
+ "title": "The Shrine",
1792
+ "summary": "",
1793
+ "tags": [
1794
+ "gradio",
1795
+ "region:us"
1796
+ ],
1797
+ "models": [],
1798
+ "datasets": [],
1799
+ "likes": 0,
1800
+ "sdk": "gradio",
1801
+ "license": "",
1802
+ "created_at": "2026-06-03T04:50:34.000Z",
1803
+ "last_modified": "2026-06-03T09:58:20.000Z",
1804
+ "host": "https://build-small-hackathon-the-shrine.hf.space",
1805
+ "url": "https://huggingface.co/spaces/build-small-hackathon/The-Shrine"
1806
+ },
1807
+ {
1808
+ "id": "build-small-hackathon/thousand-token-wood",
1809
+ "title": "Thousand Token Wood",
1810
+ "summary": "",
1811
+ "tags": [
1812
+ "gradio",
1813
+ "region:us"
1814
+ ],
1815
+ "models": [],
1816
+ "datasets": [],
1817
+ "likes": 0,
1818
+ "sdk": "gradio",
1819
+ "license": "",
1820
+ "created_at": "2026-06-05T19:18:01.000Z",
1821
+ "last_modified": "2026-06-05T19:27:14.000Z",
1822
+ "host": "https://build-small-hackathon-thousand-token-wood.hf.space",
1823
+ "url": "https://huggingface.co/spaces/build-small-hackathon/thousand-token-wood"
1824
+ },
1825
+ {
1826
+ "id": "build-small-hackathon/thousand-token-wood-sim",
1827
+ "title": "Thousand Token Wood",
1828
+ "summary": "",
1829
+ "tags": [
1830
+ "gradio",
1831
+ "region:us"
1832
+ ],
1833
+ "models": [
1834
+ "AdmiralTaco/ttw-trader-0.5b",
1835
+ "nvidia/Nemotron-Mini-4B-Instruct",
1836
+ "openai/gpt-oss-20b",
1837
+ "openbmb/MiniCPM3-4B",
1838
+ "Qwen/Qwen2.5-0.5B-Instruct",
1839
+ "Qwen/Qwen2.5-1.5B-Instruct",
1840
+ "Qwen/Qwen2.5-3B-Instruct",
1841
+ "Qwen/Qwen2.5-7B-Instruct"
1842
+ ],
1843
+ "datasets": [
1844
+ "build-small-hackathon/thousand-token-wood-traces"
1845
+ ],
1846
+ "likes": 0,
1847
+ "sdk": "gradio",
1848
+ "license": "mit",
1849
+ "created_at": "2026-06-05T21:47:45.000Z",
1850
+ "last_modified": "2026-06-06T17:03:53.000Z",
1851
+ "host": "https://build-small-hackathon-thousand-token-wood-sim.hf.space",
1852
+ "url": "https://huggingface.co/spaces/build-small-hackathon/thousand-token-wood-sim"
1853
+ },
1854
+ {
1855
+ "id": "build-small-hackathon/tiny-army",
1856
+ "title": "Tiny Army",
1857
+ "summary": "Tiny Army — fighters write their own true legends",
1858
+ "tags": [
1859
+ "docker",
1860
+ "region:us"
1861
+ ],
1862
+ "models": [
1863
+ "black-forest-labs/FLUX.1-dev",
1864
+ "black-forest-labs/FLUX.1-schnell",
1865
+ "black-forest-labs/FLUX.2-klein-4B",
1866
+ "prism-ml/bonsai-image-binary-4B-mlx-1bit",
1867
+ "prism-ml/bonsai-image-ternary-4B-mlx-2bit",
1868
+ "Qwen/Qwen2.5-0.5B-Instruct-GGUF",
1869
+ "Qwen/Qwen3-TTS-12Hz-1.7B-Base",
1870
+ "Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign",
1871
+ "Tongyi-MAI/Z-Image-Turbo"
1872
+ ],
1873
+ "datasets": [],
1874
+ "likes": 0,
1875
+ "sdk": "docker",
1876
+ "license": "mit",
1877
+ "created_at": "2026-06-03T15:28:19.000Z",
1878
+ "last_modified": "2026-06-06T19:20:04.000Z",
1879
+ "host": "https://build-small-hackathon-tiny-army.hf.space",
1880
+ "url": "https://huggingface.co/spaces/build-small-hackathon/tiny-army"
1881
+ },
1882
+ {
1883
+ "id": "build-small-hackathon/tiny-dispatch-coach",
1884
+ "title": "Tiny Dispatch Coach",
1885
+ "summary": "Small-model route coach",
1886
+ "tags": [
1887
+ "gradio",
1888
+ "hackathon",
1889
+ "logistics",
1890
+ "operations-research",
1891
+ "small-models"
1892
+ ],
1893
+ "models": [],
1894
+ "datasets": [],
1895
+ "likes": 0,
1896
+ "sdk": "gradio",
1897
+ "license": "mit",
1898
+ "created_at": "2026-06-02T02:04:10.000Z",
1899
+ "last_modified": "2026-06-02T02:17:05.000Z",
1900
+ "host": "https://build-small-hackathon-tiny-dispatch-coach.hf.space",
1901
+ "url": "https://huggingface.co/spaces/build-small-hackathon/tiny-dispatch-coach"
1902
+ },
1903
+ {
1904
+ "id": "build-small-hackathon/tricket",
1905
+ "title": "Tricket",
1906
+ "summary": "",
1907
+ "tags": [
1908
+ "gradio",
1909
+ "region:us"
1910
+ ],
1911
+ "models": [
1912
+ "openai/gpt-oss-20b"
1913
+ ],
1914
+ "datasets": [],
1915
+ "likes": 1,
1916
+ "sdk": "gradio",
1917
+ "license": "",
1918
+ "created_at": "2026-06-04T02:53:12.000Z",
1919
+ "last_modified": "2026-06-04T02:53:13.000Z",
1920
+ "host": "https://build-small-hackathon-tricket.hf.space",
1921
+ "url": "https://huggingface.co/spaces/build-small-hackathon/tricket"
1922
+ },
1923
+ {
1924
+ "id": "build-small-hackathon/Trollsona",
1925
+ "title": "Trollsona",
1926
+ "summary": "",
1927
+ "tags": [
1928
+ "gradio",
1929
+ "region:us"
1930
+ ],
1931
+ "models": [
1932
+ "Qwen/Qwen2.5-0.5B-Instruct",
1933
+ "RthItalia/nano_compact_3b_qkvfp16"
1934
+ ],
1935
+ "datasets": [],
1936
+ "likes": 1,
1937
+ "sdk": "gradio",
1938
+ "license": "",
1939
+ "created_at": "2026-06-05T19:08:56.000Z",
1940
+ "last_modified": "2026-06-05T19:37:07.000Z",
1941
+ "host": "https://build-small-hackathon-trollsona.hf.space",
1942
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Trollsona"
1943
+ },
1944
+ {
1945
+ "id": "build-small-hackathon/ux-crime-scene",
1946
+ "title": "UX Crime Scene",
1947
+ "summary": "A noir detective investigates your UI as a crime scene.",
1948
+ "tags": [
1949
+ "gradio",
1950
+ "region:us"
1951
+ ],
1952
+ "models": [],
1953
+ "datasets": [],
1954
+ "likes": 0,
1955
+ "sdk": "gradio",
1956
+ "license": "mit",
1957
+ "created_at": "2026-06-06T15:02:25.000Z",
1958
+ "last_modified": "2026-06-06T15:35:57.000Z",
1959
+ "host": "https://build-small-hackathon-ux-crime-scene.hf.space",
1960
+ "url": "https://huggingface.co/spaces/build-small-hackathon/ux-crime-scene"
1961
+ },
1962
+ {
1963
+ "id": "build-small-hackathon/voice-sales-logger",
1964
+ "title": "Voice Sales Logger",
1965
+ "summary": "",
1966
+ "tags": [
1967
+ "gradio",
1968
+ "region:us"
1969
+ ],
1970
+ "models": [
1971
+ "nvidia/nemotron-3.5-asr-streaming-0.6b",
1972
+ "Qwen/Qwen2.5-1.5B-Instruct"
1973
+ ],
1974
+ "datasets": [],
1975
+ "likes": 0,
1976
+ "sdk": "gradio",
1977
+ "license": "",
1978
+ "created_at": "2026-06-06T02:11:31.000Z",
1979
+ "last_modified": "2026-06-06T04:16:09.000Z",
1980
+ "host": "https://build-small-hackathon-voice-sales-logger.hf.space",
1981
+ "url": "https://huggingface.co/spaces/build-small-hackathon/voice-sales-logger"
1982
+ },
1983
+ {
1984
+ "id": "build-small-hackathon/VoiceGate",
1985
+ "title": "VoiceGate",
1986
+ "summary": "Multilingual dubbing with subtitles and ambience.",
1987
+ "tags": [
1988
+ "gradio",
1989
+ "region:us"
1990
+ ],
1991
+ "models": [
1992
+ "Kijai/MelBandRoFormer_comfy",
1993
+ "openbmb/VoxCPM2",
1994
+ "Qwen/Qwen3-ASR-1.7B",
1995
+ "Qwen/Qwen3-ForcedAligner-0.6B"
1996
+ ],
1997
+ "datasets": [],
1998
+ "likes": 1,
1999
+ "sdk": "gradio",
2000
+ "license": "",
2001
+ "created_at": "2026-06-04T22:15:11.000Z",
2002
+ "last_modified": "2026-06-06T07:36:54.000Z",
2003
+ "host": "https://build-small-hackathon-voicegate.hf.space",
2004
+ "url": "https://huggingface.co/spaces/build-small-hackathon/VoiceGate"
2005
+ },
2006
+ {
2007
+ "id": "build-small-hackathon/wan2-2-fp8da-aoti-14B-fast",
2008
+ "title": "Wan2.2 14B Fast Preview",
2009
+ "summary": "generate a video from an image with a text prompt",
2010
+ "tags": [
2011
+ "gradio",
2012
+ "mcp-server",
2013
+ "region:us"
2014
+ ],
2015
+ "models": [
2016
+ "cbensimon/WanTransformer3DModel-sm120-cu130-raa",
2017
+ "r3gm/RIFE",
2018
+ "TestOrganizationPleaseIgnore/wamu-tools",
2019
+ "Wan-AI/Wan2.2-I2V-A14B-Diffusers"
2020
+ ],
2021
+ "datasets": [],
2022
+ "likes": 0,
2023
+ "sdk": "gradio",
2024
+ "license": "",
2025
+ "created_at": "2026-06-04T11:21:36.000Z",
2026
+ "last_modified": "2026-05-16T22:14:21.000Z",
2027
+ "host": "https://build-small-hackathon-wan2-2-fp8da-aoti-14b-fast.hf.space",
2028
+ "url": "https://huggingface.co/spaces/build-small-hackathon/wan2-2-fp8da-aoti-14B-fast"
2029
+ },
2030
+ {
2031
+ "id": "build-small-hackathon/WitGym",
2032
+ "title": "WitGym",
2033
+ "summary": "",
2034
+ "tags": [
2035
+ "gradio",
2036
+ "region:us"
2037
+ ],
2038
+ "models": [
2039
+ "BAAI/bge-small-en-v1.5",
2040
+ "cross-encoder/ettin-reranker-32m-v1",
2041
+ "Qwen/Qwen3.5-9B"
2042
+ ],
2043
+ "datasets": [
2044
+ "jxm/the_office_lines"
2045
+ ],
2046
+ "likes": 0,
2047
+ "sdk": "gradio",
2048
+ "license": "apache-2.0",
2049
+ "created_at": "2026-06-04T10:53:06.000Z",
2050
+ "last_modified": "2026-06-05T11:12:44.000Z",
2051
+ "host": "https://build-small-hackathon-witgym.hf.space",
2052
+ "url": "https://huggingface.co/spaces/build-small-hackathon/WitGym"
2053
+ },
2054
+ {
2055
+ "id": "build-small-hackathon/wonderland",
2056
+ "title": "wonderland",
2057
+ "summary": "A text adventure with a 1000 token model guiding you.",
2058
+ "tags": [
2059
+ "gradio",
2060
+ "region:us"
2061
+ ],
2062
+ "models": [
2063
+ "nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16",
2064
+ "nvidia/NVIDIA-Nemotron-3-Nano-4B-BF16",
2065
+ "nvidia/NVIDIA-Nemotron-3-Nano-4B-FP8",
2066
+ "nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF"
2067
+ ],
2068
+ "datasets": [],
2069
+ "likes": 0,
2070
+ "sdk": "gradio",
2071
+ "license": "mit",
2072
+ "created_at": "2026-06-05T18:46:14.000Z",
2073
+ "last_modified": "2026-06-06T05:37:37.000Z",
2074
+ "host": "https://build-small-hackathon-wonderland.hf.space",
2075
+ "url": "https://huggingface.co/spaces/build-small-hackathon/wonderland"
2076
+ },
2077
+ {
2078
+ "id": "build-small-hackathon/wpl-discovery",
2079
+ "title": "Wpl Discovery",
2080
+ "summary": "Discover what your library actually offers",
2081
+ "tags": [
2082
+ "gradio",
2083
+ "region:us"
2084
+ ],
2085
+ "models": [],
2086
+ "datasets": [],
2087
+ "likes": 0,
2088
+ "sdk": "gradio",
2089
+ "license": "mit",
2090
+ "created_at": "2026-06-06T14:50:27.000Z",
2091
+ "last_modified": "2026-06-06T14:50:27.000Z",
2092
+ "host": "https://build-small-hackathon-wpl-discovery.hf.space",
2093
+ "url": "https://huggingface.co/spaces/build-small-hackathon/wpl-discovery"
2094
+ },
2095
+ {
2096
+ "id": "build-small-hackathon/Yui-Home-Assisstant",
2097
+ "title": "Yui Home Assisstant",
2098
+ "summary": "Local voice assistant configured for home asssitant",
2099
+ "tags": [
2100
+ "gradio",
2101
+ "region:us"
2102
+ ],
2103
+ "models": [
2104
+ "openai/gpt-oss-20b"
2105
+ ],
2106
+ "datasets": [],
2107
+ "likes": 0,
2108
+ "sdk": "gradio",
2109
+ "license": "mit",
2110
+ "created_at": "2026-06-06T08:54:43.000Z",
2111
+ "last_modified": "2026-06-06T08:54:44.000Z",
2112
+ "host": "https://build-small-hackathon-yui-home-assisstant.hf.space",
2113
+ "url": "https://huggingface.co/spaces/build-small-hackathon/Yui-Home-Assisstant"
2114
+ }
2115
+ ]
2116
+ }
hackathon_advisor/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """Build Small Hackathon Advisor."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
hackathon_advisor/agent.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+ import re
6
+
7
+ from hackathon_advisor.aliases import Correction, normalize_text
8
+ from hackathon_advisor.data import Project, ProjectIndex, WhitespaceItem
9
+ from hackathon_advisor.scoring import ScoreCard
10
+ from hackathon_advisor.tools import AdvisorTools, Idea, ToolEvent, idea_from_text
11
+
12
+
13
+ PLAN_RE = re.compile(r"\b(plan|build order|roadmap|next step|milestone)\b", re.IGNORECASE)
14
+ COMPARE_RE = re.compile(r"\b(compare|choose|rank)\b", re.IGNORECASE)
15
+ WHITESPACE_RE = re.compile(r"\b(whitespace|original|new|bolder|unwritten|gap)\b", re.IGNORECASE)
16
+ SEARCH_RE = re.compile(r"\b(search|similar|already|existing|overlap|echo)\b", re.IGNORECASE)
17
+
18
+
19
+ @dataclass
20
+ class TurnResult:
21
+ normalized_text: str
22
+ corrections: list[Correction]
23
+ response: str
24
+ state: dict[str, Any]
25
+ tool_events: list[ToolEvent]
26
+ projects: list[Project]
27
+ whitespace: list[WhitespaceItem]
28
+ score: ScoreCard | None
29
+ plan: list[str]
30
+ artifact: dict[str, Any]
31
+
32
+ def stream_chunks(self) -> list[str]:
33
+ words = self.response.split(" ")
34
+ chunks: list[str] = []
35
+ current: list[str] = []
36
+ for word in words:
37
+ current.append(word)
38
+ if len(" ".join(current)) >= 28:
39
+ chunks.append(" ".join(current) + " ")
40
+ current = []
41
+ if current:
42
+ chunks.append(" ".join(current))
43
+ return chunks
44
+
45
+
46
+ class AdvisorEngine:
47
+ def __init__(self, index: ProjectIndex) -> None:
48
+ self.index = index
49
+ self.tools = AdvisorTools(index)
50
+
51
+ def turn(self, message: str, state: dict[str, Any] | None = None) -> TurnResult:
52
+ state = dict(state or {})
53
+ state.setdefault("ideas", [])
54
+ normalized, corrections = normalize_text(message)
55
+ tool_events: list[ToolEvent] = []
56
+ projects: list[Project] = []
57
+ whitespace: list[WhitespaceItem] = []
58
+ score: ScoreCard | None = None
59
+ plan: list[str] = []
60
+
61
+ if not normalized.strip():
62
+ projects, event = self.tools.list_projects(limit=6)
63
+ tool_events.append(event)
64
+ response = self._opening_response(projects)
65
+ return self._result(normalized, corrections, response, state, tool_events, projects, [], None, [], {})
66
+
67
+ if COMPARE_RE.search(normalized) and state.get("ideas"):
68
+ response = self._compare_response(state)
69
+ tool_events.append(ToolEvent("compare_ideas", "Compared the current idea board."))
70
+ return self._result(normalized, corrections, response, state, tool_events, [], [], None, [], {})
71
+
72
+ title, pitch = idea_from_text(normalized)
73
+ idea, event = self.tools.save_idea(state, title, pitch)
74
+ tool_events.append(event)
75
+
76
+ if PLAN_RE.search(normalized):
77
+ score, event = self.tools.score_idea(idea)
78
+ self._store_idea(state, idea)
79
+ tool_events.append(event)
80
+ plan, event = self.tools.make_plan(idea)
81
+ tool_events.append(event)
82
+ response = self._plan_response(idea, score, plan)
83
+ artifact = self._artifact(idea, score)
84
+ return self._result(normalized, corrections, response, state, tool_events, [], [], score, plan, artifact)
85
+
86
+ if WHITESPACE_RE.search(normalized):
87
+ whitespace, event = self.tools.find_whitespace(limit=4)
88
+ tool_events.append(event)
89
+ if whitespace:
90
+ idea.title = whitespace[0].label
91
+ idea.pitch = whitespace[0].pitch
92
+ state["ideas"] = [
93
+ idea.to_dict() if item.get("id") == idea.id else item for item in state.get("ideas", [])
94
+ ]
95
+ score, event = self.tools.score_idea(idea)
96
+ self._store_idea(state, idea)
97
+ tool_events.append(event)
98
+ response = self._whitespace_response(idea, whitespace, score)
99
+ artifact = self._artifact(idea, score)
100
+ return self._result(
101
+ normalized,
102
+ corrections,
103
+ response,
104
+ state,
105
+ tool_events,
106
+ [],
107
+ whitespace,
108
+ score,
109
+ [],
110
+ artifact,
111
+ )
112
+
113
+ hits = self.index.search(normalized, limit=5)
114
+ projects = [hit.project for hit in hits]
115
+ tool_events.append(ToolEvent("search_projects", f"Checked {len(projects)} closest project echoes."))
116
+ score, event = self.tools.score_idea(idea)
117
+ self._store_idea(state, idea)
118
+ tool_events.append(event)
119
+
120
+ if SEARCH_RE.search(normalized) or projects:
121
+ response = self._overlap_response(idea, projects, score)
122
+ else:
123
+ whitespace, event = self.tools.find_whitespace(limit=3)
124
+ tool_events.append(event)
125
+ response = self._whitespace_response(idea, whitespace, score)
126
+
127
+ artifact = self._artifact(idea, score)
128
+ return self._result(
129
+ normalized,
130
+ corrections,
131
+ response,
132
+ state,
133
+ tool_events,
134
+ projects,
135
+ whitespace,
136
+ score,
137
+ plan,
138
+ artifact,
139
+ )
140
+
141
+ def _result(
142
+ self,
143
+ normalized_text: str,
144
+ corrections: list[Correction],
145
+ response: str,
146
+ state: dict[str, Any],
147
+ tool_events: list[ToolEvent],
148
+ projects: list[Project],
149
+ whitespace: list[WhitespaceItem],
150
+ score: ScoreCard | None,
151
+ plan: list[str],
152
+ artifact: dict[str, Any],
153
+ ) -> TurnResult:
154
+ return TurnResult(
155
+ normalized_text=normalized_text,
156
+ corrections=corrections,
157
+ response=response,
158
+ state=state,
159
+ tool_events=tool_events,
160
+ projects=projects,
161
+ whitespace=whitespace,
162
+ score=score,
163
+ plan=plan,
164
+ artifact=artifact,
165
+ )
166
+
167
+ def _store_idea(self, state: dict[str, Any], idea: Idea) -> None:
168
+ state["ideas"] = [
169
+ idea.to_dict() if item.get("id") == idea.id else item for item in state.get("ideas", [])
170
+ ]
171
+
172
+ def _opening_response(self, projects: list[Project]) -> str:
173
+ names = ", ".join(project.title for project in projects[:4])
174
+ return (
175
+ "Mothback opens the Almanac. The Wood is already inked with "
176
+ f"{len(self.index.projects)} project pages; the brightest current echoes include {names}. "
177
+ "Give me one project instinct and I will test whether it bleeds red or blooms gold."
178
+ )
179
+
180
+ def _overlap_response(self, idea: Idea, projects: list[Project], score: ScoreCard) -> str:
181
+ if score.verdict.startswith("UNWRITTEN"):
182
+ nearby = ", ".join(project.title for project in projects[:2]) or "no close pages"
183
+ return (
184
+ f"The page for {idea.title} does not bleed much. I found {nearby}, but the seal reads "
185
+ f"{score.verdict} at {score.overall}/10. Push the AI necessity harder: make the model decide, rank, "
186
+ "or personalize something a static app cannot."
187
+ )
188
+ citations = "; ".join(f"page {idx + 1}: {project.title}" for idx, project in enumerate(projects[:3]))
189
+ return (
190
+ f"The ink bleeds around {idea.title}. Closest echoes: {citations}. The seal reads "
191
+ f"{score.verdict} at {score.overall}/10. Keep the audience, but change the mechanism or artifact so the "
192
+ "demo proves a gap instead of joining a cluster."
193
+ )
194
+
195
+ def _whitespace_response(
196
+ self,
197
+ idea: Idea,
198
+ whitespace: list[WhitespaceItem],
199
+ score: ScoreCard,
200
+ ) -> str:
201
+ if not whitespace:
202
+ return (
203
+ f"The page for {idea.title} stays pale: I could not find a strong whitespace candidate in the "
204
+ "snapshot. Narrow the user and the moment, then ask again."
205
+ )
206
+ lead = whitespace[0]
207
+ return (
208
+ f"Gold gathers on {lead.label}. {lead.pitch} {lead.evidence} The seal reads "
209
+ f"{score.verdict} at {score.overall}/10. The next move is to make one concrete before/after scene and "
210
+ "cite the two weakest nearby echoes in the margin."
211
+ )
212
+
213
+ def _plan_response(self, idea: Idea, score: ScoreCard, plan: list[str]) -> str:
214
+ steps = " ".join(f"{idx + 1}. {step}" for idx, step in enumerate(plan))
215
+ return (
216
+ f"Mothback presses the wax for {idea.title}: {score.overall}/10, {score.verdict}. "
217
+ f"The build path is: {steps}"
218
+ )
219
+
220
+ def _compare_response(self, state: dict[str, Any]) -> str:
221
+ ideas = state.get("ideas", [])
222
+ if not ideas:
223
+ return "There are no written pages on the board yet."
224
+ scored = sorted(
225
+ ideas,
226
+ key=lambda item: ((item.get("score") or {}).get("overall") or 0, item.get("title") or ""),
227
+ reverse=True,
228
+ )
229
+ names = ", ".join(item.get("title", "Untitled") for item in scored[:3])
230
+ return f"The board tilts toward {names}. Keep the top page only if its artifact can be understood in ten seconds."
231
+
232
+ def _artifact(self, idea: Idea, score: ScoreCard) -> dict[str, Any]:
233
+ return {
234
+ "title": idea.title,
235
+ "verdict": score.verdict,
236
+ "caption": f"Mothback inked my Build Small fate page: {idea.title} - {score.verdict}.",
237
+ "seal": score.to_dict(),
238
+ }
hackathon_advisor/aliases.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from difflib import SequenceMatcher
5
+ import re
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Correction:
10
+ original: str
11
+ canonical: str
12
+ confidence: float
13
+
14
+ def to_dict(self) -> dict:
15
+ return {
16
+ "original": self.original,
17
+ "canonical": self.canonical,
18
+ "confidence": round(self.confidence, 3),
19
+ }
20
+
21
+
22
+ ALIASES: dict[str, tuple[str, ...]] = {
23
+ "Nemotron": ("nemotron", "nemo tron", "neutron", "nemotran", "nemo-tron"),
24
+ "MiniCPM5": ("minicpm5", "mini cpm5", "mini cpm", "open cpm", "opencpm5", "cpm five"),
25
+ "EmbeddingGemma": ("embedding gemma", "embeddinggemma", "gemma embedding", "embedded gemma"),
26
+ "ZeroGPU": ("zero gpu", "zerogpu", "zero-gpu", "zero g p u"),
27
+ "Gradio Server": ("gradio server", "gradio.server", "server mode"),
28
+ "Build Small Hackathon": ("build small", "build-small", "small hackathon"),
29
+ "Off the Grid": ("off the grid", "off-grid", "offline badge"),
30
+ "Well-Tuned": ("well tuned", "well-tuned", "fine tune", "finetune", "lora"),
31
+ "Tiny Titan": ("tiny titan", "tiny tight end", "tiny-titan"),
32
+ "Llama Champion": ("llama champion", "llama.cpp", "llama cpp", "llama badge"),
33
+ }
34
+
35
+ _TOKEN_RE = re.compile(r"[a-z0-9]+(?:[.-][a-z0-9]+)?", re.IGNORECASE)
36
+
37
+
38
+ def normalize_text(text: str) -> tuple[str, list[Correction]]:
39
+ normalized = text
40
+ corrections: list[Correction] = []
41
+ spans = _candidate_spans(text)
42
+ used: set[str] = set()
43
+
44
+ for canonical, aliases in ALIASES.items():
45
+ best: tuple[str, float] | None = None
46
+ for alias in aliases:
47
+ for span in spans:
48
+ confidence = _similarity(alias, span)
49
+ if confidence >= 0.88 and (best is None or confidence > best[1]):
50
+ best = (span, confidence)
51
+ if not best:
52
+ continue
53
+
54
+ original, confidence = best
55
+ if original.lower() in used or original == canonical:
56
+ continue
57
+ used.add(original.lower())
58
+ normalized = re.sub(re.escape(original), canonical, normalized, count=1, flags=re.IGNORECASE)
59
+ corrections.append(Correction(original=original, canonical=canonical, confidence=confidence))
60
+
61
+ return normalized, corrections
62
+
63
+
64
+ def _candidate_spans(text: str) -> list[str]:
65
+ tokens = _TOKEN_RE.findall(text.lower())
66
+ spans = set(tokens)
67
+ for size in (2, 3):
68
+ for index in range(max(0, len(tokens) - size + 1)):
69
+ spans.add(" ".join(tokens[index : index + size]))
70
+ return sorted(spans, key=len, reverse=True)
71
+
72
+
73
+ def _similarity(left: str, right: str) -> float:
74
+ compact_left = re.sub(r"[^a-z0-9]", "", left.lower())
75
+ compact_right = re.sub(r"[^a-z0-9]", "", right.lower())
76
+ if not compact_left or not compact_right:
77
+ return 0.0
78
+ if compact_left == compact_right:
79
+ return 1.0
80
+ return SequenceMatcher(None, compact_left, compact_right).ratio()
hackathon_advisor/data.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter
4
+ from dataclasses import dataclass
5
+ import json
6
+ import math
7
+ from pathlib import Path
8
+ import re
9
+
10
+
11
+ TOKEN_RE = re.compile(r"[a-z0-9][a-z0-9.+_-]*", re.IGNORECASE)
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class Project:
16
+ id: str
17
+ title: str
18
+ summary: str
19
+ tags: tuple[str, ...]
20
+ models: tuple[str, ...]
21
+ datasets: tuple[str, ...]
22
+ likes: int
23
+ sdk: str
24
+ license: str
25
+ created_at: str
26
+ last_modified: str
27
+ host: str
28
+ url: str
29
+
30
+ @classmethod
31
+ def from_dict(cls, data: dict) -> "Project":
32
+ return cls(
33
+ id=str(data["id"]),
34
+ title=str(data.get("title") or data["id"].rsplit("/", 1)[-1]),
35
+ summary=str(data.get("summary") or ""),
36
+ tags=tuple(data.get("tags") or ()),
37
+ models=tuple(data.get("models") or ()),
38
+ datasets=tuple(data.get("datasets") or ()),
39
+ likes=int(data.get("likes") or 0),
40
+ sdk=str(data.get("sdk") or ""),
41
+ license=str(data.get("license") or ""),
42
+ created_at=str(data.get("created_at") or ""),
43
+ last_modified=str(data.get("last_modified") or ""),
44
+ host=str(data.get("host") or ""),
45
+ url=str(data.get("url") or f"https://huggingface.co/spaces/{data['id']}"),
46
+ )
47
+
48
+ @property
49
+ def slug(self) -> str:
50
+ return self.id.rsplit("/", 1)[-1]
51
+
52
+ @property
53
+ def searchable_text(self) -> str:
54
+ return " ".join(
55
+ [
56
+ self.title,
57
+ self.slug.replace("-", " ").replace("_", " "),
58
+ self.summary,
59
+ " ".join(self.tags),
60
+ " ".join(self.models),
61
+ " ".join(self.datasets),
62
+ ]
63
+ )
64
+
65
+ def to_public_dict(self) -> dict:
66
+ return {
67
+ "id": self.id,
68
+ "title": self.title,
69
+ "summary": self.summary,
70
+ "tags": list(self.tags),
71
+ "models": list(self.models),
72
+ "datasets": list(self.datasets),
73
+ "likes": self.likes,
74
+ "sdk": self.sdk,
75
+ "license": self.license,
76
+ "created_at": self.created_at,
77
+ "last_modified": self.last_modified,
78
+ "host": self.host,
79
+ "url": self.url,
80
+ }
81
+
82
+
83
+ @dataclass(frozen=True)
84
+ class SearchHit:
85
+ project: Project
86
+ score: float
87
+ matched_terms: tuple[str, ...]
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class WhitespaceItem:
92
+ label: str
93
+ pitch: str
94
+ evidence: str
95
+ score: float
96
+ nearby_projects: tuple[Project, ...]
97
+
98
+ def to_dict(self) -> dict:
99
+ return {
100
+ "label": self.label,
101
+ "pitch": self.pitch,
102
+ "evidence": self.evidence,
103
+ "score": round(self.score, 3),
104
+ "nearby_projects": [project.to_public_dict() for project in self.nearby_projects],
105
+ }
106
+
107
+
108
+ @dataclass(frozen=True)
109
+ class WhitespaceSeed:
110
+ label: str
111
+ query: str
112
+ pitch: str
113
+
114
+
115
+ WHITESPACE_SEEDS: tuple[WhitespaceSeed, ...] = (
116
+ WhitespaceSeed(
117
+ "Tiny civic repair desk",
118
+ "local government forms benefits tenant aid accessibility paperwork",
119
+ "A small agent that turns intimidating public-service forms into one-page action plans.",
120
+ ),
121
+ WhitespaceSeed(
122
+ "Hands-on science coach",
123
+ "kitchen science experiment kids sensor notebook classroom",
124
+ "A lab-notebook companion that designs safe experiments from household materials.",
125
+ ),
126
+ WhitespaceSeed(
127
+ "Offline field translator",
128
+ "offline translation field guide travel emergency low connectivity",
129
+ "A local-first phrase and intent helper for stressful travel or field-work moments.",
130
+ ),
131
+ WhitespaceSeed(
132
+ "Personal archive cartographer",
133
+ "photos notes memories archive timeline family history scrapbook",
134
+ "A tiny model that maps a private archive into stories without sending it to cloud APIs.",
135
+ ),
136
+ WhitespaceSeed(
137
+ "Small-team incident scribe",
138
+ "incident retrospective logs on call debugging timeline root cause",
139
+ "A local incident historian that turns messy notes into a calm timeline and next actions.",
140
+ ),
141
+ WhitespaceSeed(
142
+ "Accessibility rehearsal room",
143
+ "accessibility captions alt text screen reader rehearsal inclusive design",
144
+ "A practice space that lets makers rehearse their demo for captions, contrast, and clarity.",
145
+ ),
146
+ WhitespaceSeed(
147
+ "Neighborhood seed library",
148
+ "garden plants seed library neighborhood seasons climate local exchange",
149
+ "An advisor for hyperlocal seed swaps, planting plans, and community garden knowledge.",
150
+ ),
151
+ )
152
+
153
+
154
+ class ProjectIndex:
155
+ def __init__(self, projects: list[Project], generated_at: str, source: str) -> None:
156
+ if not projects:
157
+ raise ValueError("project index requires at least one project")
158
+ self.projects = projects
159
+ self.generated_at = generated_at
160
+ self.source = source
161
+ self._documents = [Counter(tokenize(project.searchable_text)) for project in projects]
162
+ self._df = Counter(term for doc in self._documents for term in doc)
163
+ self._idf = {
164
+ term: math.log((1 + len(self._documents)) / (1 + freq)) + 1.0
165
+ for term, freq in self._df.items()
166
+ }
167
+ self._norms = [self._norm(doc) for doc in self._documents]
168
+
169
+ @classmethod
170
+ def from_file(cls, path: Path) -> "ProjectIndex":
171
+ data = json.loads(path.read_text(encoding="utf-8"))
172
+ projects = [Project.from_dict(item) for item in data["projects"]]
173
+ return cls(
174
+ projects=projects,
175
+ generated_at=str(data.get("generated_at") or ""),
176
+ source=str(data.get("source") or ""),
177
+ )
178
+
179
+ def top_projects(self, limit: int = 8) -> list[Project]:
180
+ return sorted(
181
+ self.projects,
182
+ key=lambda project: (project.likes, project.last_modified, project.title.lower()),
183
+ reverse=True,
184
+ )[:limit]
185
+
186
+ def search(self, query: str, limit: int = 5) -> list[SearchHit]:
187
+ query_terms = tokenize(query)
188
+ if not query_terms:
189
+ return []
190
+ query_doc = Counter(query_terms)
191
+ query_norm = self._norm(query_doc)
192
+ hits: list[SearchHit] = []
193
+ for project, doc, doc_norm in zip(self.projects, self._documents, self._norms, strict=True):
194
+ if doc_norm == 0.0 or query_norm == 0.0:
195
+ continue
196
+ raw = 0.0
197
+ matched: list[str] = []
198
+ for term, count in query_doc.items():
199
+ if term not in doc:
200
+ continue
201
+ raw += count * doc[term] * self._idf.get(term, 1.0) ** 2
202
+ matched.append(term)
203
+ if not matched:
204
+ continue
205
+ title_bonus = sum(0.08 for term in matched if term in tokenize(project.title))
206
+ tag_bonus = sum(0.05 for term in matched if term in tokenize(" ".join(project.tags)))
207
+ score = raw / (query_norm * doc_norm) + title_bonus + tag_bonus
208
+ hits.append(SearchHit(project=project, score=score, matched_terms=tuple(sorted(matched))))
209
+ hits.sort(key=lambda hit: (hit.score, hit.project.likes), reverse=True)
210
+ return hits[:limit]
211
+
212
+ def get(self, project_id: str) -> Project | None:
213
+ for project in self.projects:
214
+ if project.id == project_id or project.slug == project_id:
215
+ return project
216
+ return None
217
+
218
+ def find_whitespace(self, limit: int = 5) -> list[WhitespaceItem]:
219
+ items: list[WhitespaceItem] = []
220
+ for seed in WHITESPACE_SEEDS:
221
+ hits = self.search(seed.query, limit=3)
222
+ saturation = sum(hit.score for hit in hits) / max(len(hits), 1)
223
+ score = max(0.0, 1.0 - min(saturation, 0.95))
224
+ if hits:
225
+ evidence = f"Nearest echoes are weak: {', '.join(hit.project.title for hit in hits[:2])}."
226
+ else:
227
+ evidence = "No close project echoes in the current snapshot."
228
+ items.append(
229
+ WhitespaceItem(
230
+ label=seed.label,
231
+ pitch=seed.pitch,
232
+ evidence=evidence,
233
+ score=score,
234
+ nearby_projects=tuple(hit.project for hit in hits),
235
+ )
236
+ )
237
+ items.sort(key=lambda item: item.score, reverse=True)
238
+ return items[:limit]
239
+
240
+ def _norm(self, doc: Counter[str]) -> float:
241
+ return math.sqrt(sum((count * self._idf.get(term, 1.0)) ** 2 for term, count in doc.items()))
242
+
243
+
244
+ def tokenize(text: str) -> list[str]:
245
+ return [token.lower().strip("._-+") for token in TOKEN_RE.findall(text) if len(token.strip("._-+")) > 1]
hackathon_advisor/scoring.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from hackathon_advisor.data import ProjectIndex, SearchHit, tokenize
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class ScoreCard:
10
+ originality: int
11
+ delight: int
12
+ ai_necessity: int
13
+ feasibility: int
14
+ prize_fit: int
15
+ verdict: str
16
+ echoes: tuple[SearchHit, ...]
17
+
18
+ @property
19
+ def overall(self) -> float:
20
+ return round(
21
+ (
22
+ self.originality * 0.30
23
+ + self.delight * 0.20
24
+ + self.ai_necessity * 0.20
25
+ + self.feasibility * 0.15
26
+ + self.prize_fit * 0.15
27
+ ),
28
+ 1,
29
+ )
30
+
31
+ def to_dict(self) -> dict:
32
+ return {
33
+ "originality": self.originality,
34
+ "delight": self.delight,
35
+ "ai_necessity": self.ai_necessity,
36
+ "feasibility": self.feasibility,
37
+ "prize_fit": self.prize_fit,
38
+ "overall": self.overall,
39
+ "verdict": self.verdict,
40
+ "echoes": [
41
+ {
42
+ "score": round(hit.score, 3),
43
+ "matched_terms": list(hit.matched_terms),
44
+ "project": hit.project.to_public_dict(),
45
+ }
46
+ for hit in self.echoes
47
+ ],
48
+ }
49
+
50
+
51
+ def score_idea(index: ProjectIndex, title: str, pitch: str, targets: list[str] | None = None) -> ScoreCard:
52
+ text = f"{title} {pitch}".strip()
53
+ hits = index.search(text, limit=4)
54
+ top_overlap = hits[0].score if hits else 0.0
55
+ tokens = set(tokenize(text))
56
+ targets = targets or []
57
+
58
+ originality = clamp_score(10 - round(top_overlap * 18))
59
+ delight = clamp_score(4 + _keyword_count(tokens, {"story", "visual", "game", "ritual", "share", "voice"}) * 2)
60
+ ai_necessity = clamp_score(
61
+ 3
62
+ + _keyword_count(tokens, {"agent", "model", "embed", "search", "personal", "speech", "local"}) * 2
63
+ )
64
+ complexity_penalty = _keyword_count(tokens, {"realtime", "video", "multiplayer", "payments", "social"})
65
+ feasibility = clamp_score(8 - complexity_penalty)
66
+ prize_fit = clamp_score(
67
+ 4
68
+ + _keyword_count(tokens, {"local", "offline", "small", "llama", "fine", "trace", "gradio"}) * 2
69
+ + min(len(targets), 3)
70
+ )
71
+ verdict = "UNWRITTEN" if top_overlap < 0.16 else f"ECHO x{sum(1 for hit in hits if hit.score >= 0.12)}"
72
+ return ScoreCard(
73
+ originality=originality,
74
+ delight=delight,
75
+ ai_necessity=ai_necessity,
76
+ feasibility=feasibility,
77
+ prize_fit=prize_fit,
78
+ verdict=verdict,
79
+ echoes=tuple(hits),
80
+ )
81
+
82
+
83
+ def clamp_score(value: int) -> int:
84
+ return max(1, min(10, value))
85
+
86
+
87
+ def _keyword_count(tokens: set[str], keywords: set[str]) -> int:
88
+ return sum(1 for keyword in keywords if any(token.startswith(keyword) for token in tokens))
hackathon_advisor/tools.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+ import uuid
6
+
7
+ from hackathon_advisor.data import Project, ProjectIndex, WhitespaceItem
8
+ from hackathon_advisor.scoring import ScoreCard, score_idea
9
+
10
+
11
+ TARGETS = [
12
+ "Off the Grid",
13
+ "Well-Tuned",
14
+ "Off-Brand",
15
+ "Llama Champion",
16
+ "Sharing is Caring",
17
+ "Field Notes",
18
+ ]
19
+
20
+
21
+ @dataclass
22
+ class Idea:
23
+ id: str
24
+ title: str
25
+ pitch: str
26
+ targets: list[str] = field(default_factory=lambda: TARGETS[:3])
27
+ score: dict | None = None
28
+
29
+ def to_dict(self) -> dict:
30
+ return {
31
+ "id": self.id,
32
+ "title": self.title,
33
+ "pitch": self.pitch,
34
+ "targets": self.targets,
35
+ "score": self.score,
36
+ }
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class ToolEvent:
41
+ name: str
42
+ summary: str
43
+
44
+ def to_dict(self) -> dict:
45
+ return {"name": self.name, "summary": self.summary}
46
+
47
+
48
+ class AdvisorTools:
49
+ def __init__(self, index: ProjectIndex) -> None:
50
+ self.index = index
51
+
52
+ def list_projects(self, limit: int = 8) -> tuple[list[Project], ToolEvent]:
53
+ projects = self.index.top_projects(limit=limit)
54
+ return projects, ToolEvent("list_projects", f"Read {len(projects)} prominent Space cards.")
55
+
56
+ def search_projects(self, query: str, limit: int = 5) -> tuple[list[Project], ToolEvent]:
57
+ hits = self.index.search(query, limit=limit)
58
+ projects = [hit.project for hit in hits]
59
+ return projects, ToolEvent("search_projects", f"Found {len(projects)} nearby Space echoes.")
60
+
61
+ def find_whitespace(self, limit: int = 5) -> tuple[list[WhitespaceItem], ToolEvent]:
62
+ items = self.index.find_whitespace(limit=limit)
63
+ return items, ToolEvent("find_whitespace", f"Ranked {len(items)} under-explored regions.")
64
+
65
+ def save_idea(self, state: dict[str, Any], title: str, pitch: str) -> tuple[Idea, ToolEvent]:
66
+ ideas = [Idea(**item) for item in state.get("ideas", [])]
67
+ current_id = state.get("current_idea_id")
68
+ idea = next((item for item in ideas if item.id == current_id), None)
69
+ if idea is None:
70
+ idea = Idea(id=uuid.uuid4().hex[:8], title=title, pitch=pitch)
71
+ ideas.append(idea)
72
+ else:
73
+ idea.title = title
74
+ idea.pitch = pitch
75
+ state["ideas"] = [item.to_dict() for item in ideas]
76
+ state["current_idea_id"] = idea.id
77
+ return idea, ToolEvent("save_idea", f"Wrote idea page '{idea.title}'.")
78
+
79
+ def score_idea(self, idea: Idea) -> tuple[ScoreCard, ToolEvent]:
80
+ score = score_idea(self.index, idea.title, idea.pitch, idea.targets)
81
+ idea.score = score.to_dict()
82
+ return score, ToolEvent("score_idea", f"Pressed a five-quadrant seal: {score.overall}/10.")
83
+
84
+ def make_plan(self, idea: Idea) -> tuple[list[str], ToolEvent]:
85
+ plan = [
86
+ "Lock a one-sentence promise and one demo input that proves originality.",
87
+ "Refresh the Space snapshot, then tune the bleed threshold against the closest echoes.",
88
+ "Build the smallest happy path: input, citations, score seal, and shareable artifact.",
89
+ "Add one prize hook only after the core loop is smooth enough to demo without narration.",
90
+ "Record the trace and write Field Notes from the exact build decisions.",
91
+ ]
92
+ if any("Well" in target for target in idea.targets):
93
+ plan.insert(4, "Prepare a tiny LoRA dataset from successful advisor turns before training.")
94
+ return plan, ToolEvent("make_plan", f"Drafted {len(plan)} build steps.")
95
+
96
+
97
+ def idea_from_text(text: str) -> tuple[str, str]:
98
+ cleaned = " ".join(text.strip().split())
99
+ if not cleaned:
100
+ return "Blank Page", "A project direction waiting for one concrete user and one concrete tension."
101
+ title = cleaned
102
+ for prefix in ("i want to build", "build", "make", "my idea is", "idea:"):
103
+ if cleaned.lower().startswith(prefix):
104
+ title = cleaned[len(prefix) :].strip(" :-")
105
+ break
106
+ title = title[:64].strip(" .") or "Unwritten Page"
107
+ if len(title) < len(cleaned):
108
+ title = f"{title[:58].strip()}..."
109
+ return _display_title(title), cleaned
110
+
111
+
112
+ def _display_title(title: str) -> str:
113
+ if not title:
114
+ return "Unwritten Page"
115
+ if any(char.isupper() or char.isdigit() for char in title):
116
+ return title[0].upper() + title[1:]
117
+ return title.capitalize()
pyproject.toml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "hackathon-advisor"
3
+ version = "0.1.0"
4
+ description = "Text-first originality advisor for the Build Small Hackathon."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11,<3.13"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Jacob LinCool" }]
9
+ dependencies = [
10
+ "gradio>=6.16.0,<7",
11
+ ]
12
+
13
+ [project.optional-dependencies]
14
+ dev = [
15
+ "pytest>=8.0,<9",
16
+ ]
17
+
18
+ [tool.pytest.ini_options]
19
+ testpaths = ["tests"]
20
+ pythonpath = ["."]
21
+
22
+ [tool.ruff]
23
+ line-length = 100
24
+ target-version = "py311"
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio>=6.16.0,<7
scripts/crawl_hf_spaces.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ from datetime import datetime, timezone
6
+ import json
7
+ from pathlib import Path
8
+ import time
9
+ from typing import Any
10
+ from urllib.error import HTTPError
11
+ from urllib.parse import quote
12
+ from urllib.request import Request, urlopen
13
+
14
+
15
+ API = "https://huggingface.co/api"
16
+
17
+
18
+ def main() -> None:
19
+ parser = argparse.ArgumentParser(description="Snapshot public Spaces in a Hugging Face org.")
20
+ parser.add_argument("--org", default="build-small-hackathon")
21
+ parser.add_argument("--out", default="data/projects.json")
22
+ parser.add_argument("--limit", type=int, default=100)
23
+ args = parser.parse_args()
24
+
25
+ spaces = fetch_json(f"{API}/spaces?author={quote(args.org)}&limit={args.limit}")
26
+ projects = []
27
+ for item in spaces:
28
+ space_id = item["id"]
29
+ detail = fetch_json(f"{API}/spaces/{quote(space_id, safe='/')}")
30
+ projects.append(project_from_detail(detail))
31
+ time.sleep(0.05)
32
+
33
+ payload = {
34
+ "generated_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
35
+ "source": f"{API}/spaces?author={args.org}&limit={args.limit}",
36
+ "projects": sorted(projects, key=lambda project: project["id"].lower()),
37
+ }
38
+ output = Path(args.out)
39
+ output.parent.mkdir(parents=True, exist_ok=True)
40
+ output.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
41
+ print(f"wrote {len(projects)} projects to {output}")
42
+
43
+
44
+ def fetch_json(url: str) -> Any:
45
+ request = Request(url, headers={"User-Agent": "hackathon-advisor-crawler/0.1"})
46
+ try:
47
+ with urlopen(request, timeout=30) as response:
48
+ return json.loads(response.read().decode("utf-8"))
49
+ except HTTPError as error:
50
+ raise RuntimeError(f"failed to fetch {url}: {error.code}") from error
51
+
52
+
53
+ def project_from_detail(detail: dict[str, Any]) -> dict[str, Any]:
54
+ card = detail.get("cardData") or {}
55
+ space_id = str(detail["id"])
56
+ title = str(card.get("title") or humanize_slug(space_id.rsplit("/", 1)[-1]))
57
+ summary = str(card.get("short_description") or card.get("description") or "")
58
+ tags = sorted(set(str(tag) for tag in (card.get("tags") or detail.get("tags") or [])))
59
+ return {
60
+ "id": space_id,
61
+ "title": title,
62
+ "summary": summary,
63
+ "tags": tags,
64
+ "models": [str(model) for model in detail.get("models") or card.get("models") or []],
65
+ "datasets": [str(dataset) for dataset in detail.get("datasets") or card.get("datasets") or []],
66
+ "likes": int(detail.get("likes") or 0),
67
+ "sdk": str(card.get("sdk") or detail.get("sdk") or ""),
68
+ "license": str(card.get("license") or ""),
69
+ "created_at": str(detail.get("createdAt") or ""),
70
+ "last_modified": str(detail.get("lastModified") or ""),
71
+ "host": str(detail.get("host") or ""),
72
+ "url": f"https://huggingface.co/spaces/{space_id}",
73
+ }
74
+
75
+
76
+ def humanize_slug(slug: str) -> str:
77
+ return " ".join(part for part in slug.replace("_", "-").split("-") if part).title()
78
+
79
+
80
+ if __name__ == "__main__":
81
+ main()
scripts/make_assets.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import random
5
+ import struct
6
+ import zlib
7
+ from pathlib import Path
8
+
9
+
10
+ def main() -> None:
11
+ out = Path("static/assets/parchment.png")
12
+ out.parent.mkdir(parents=True, exist_ok=True)
13
+ write_png(out, 1280, 760)
14
+ print(f"wrote {out}")
15
+
16
+
17
+ def write_png(path: Path, width: int, height: int) -> None:
18
+ random.seed(47)
19
+ rows = []
20
+ for y in range(height):
21
+ row = bytearray([0])
22
+ for x in range(width):
23
+ dx = abs(x / width - 0.5)
24
+ dy = abs(y / height - 0.5)
25
+ vignette = int((dx + dy) * 58)
26
+ grain = random.randint(-12, 12)
27
+ wave = int(8 * random.random() + 6 * ((x * y) % 17 == 0))
28
+ r = clamp(217 - vignette + grain + wave)
29
+ g = clamp(190 - vignette + grain)
30
+ b = clamp(137 - vignette + grain // 2)
31
+ row.extend([r, g, b, 255])
32
+ rows.append(bytes(row))
33
+ raw = b"".join(rows)
34
+ png = b"\x89PNG\r\n\x1a\n"
35
+ png += chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0))
36
+ png += chunk(b"IDAT", zlib.compress(raw, 9))
37
+ png += chunk(b"IEND", b"")
38
+ path.write_bytes(png)
39
+
40
+
41
+ def chunk(kind: bytes, data: bytes) -> bytes:
42
+ return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data) & 0xFFFFFFFF)
43
+
44
+
45
+ def clamp(value: int) -> int:
46
+ return max(0, min(255, value))
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
static/app.js ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
2
+
3
+ const form = document.querySelector("#turn-form");
4
+ const input = document.querySelector("#message");
5
+ const submit = document.querySelector("#submit");
6
+ const ink = document.querySelector("#ink");
7
+ const corrections = document.querySelector("#corrections");
8
+ const projectsEl = document.querySelector("#projects");
9
+ const whitespaceEl = document.querySelector("#whitespace");
10
+ const verdictEl = document.querySelector("#verdict");
11
+ const overallEl = document.querySelector("#overall");
12
+
13
+ let session = {};
14
+ let clientPromise = Client.connect(window.location.origin);
15
+
16
+ bootstrap();
17
+
18
+ form.addEventListener("submit", async (event) => {
19
+ event.preventDefault();
20
+ const message = input.value.trim();
21
+ if (!message) return;
22
+ input.value = "";
23
+ submit.disabled = true;
24
+ ink.textContent = "";
25
+ ink.classList.remove("bleed", "gold");
26
+ corrections.textContent = "";
27
+
28
+ try {
29
+ const client = await clientPromise;
30
+ const submission = client.submit("/agent_turn", {
31
+ message,
32
+ session_json: JSON.stringify(session),
33
+ });
34
+
35
+ for await (const event of submission) {
36
+ if (event.type !== "data") continue;
37
+ const payloads = Array.isArray(event.data) ? event.data : [event.data];
38
+ for (const raw of payloads) {
39
+ handleEvent(JSON.parse(raw));
40
+ }
41
+ }
42
+ } catch (error) {
43
+ ink.textContent = `The page tore before it could answer: ${error.message}`;
44
+ ink.classList.add("bleed");
45
+ } finally {
46
+ submit.disabled = false;
47
+ input.focus();
48
+ }
49
+ });
50
+
51
+ async function bootstrap() {
52
+ const response = await fetch("/api/bootstrap");
53
+ const data = await response.json();
54
+ renderProjects(data.top_projects || []);
55
+ renderWhitespace(data.whitespace || []);
56
+ }
57
+
58
+ function handleEvent(event) {
59
+ if (event.type === "start") {
60
+ if (event.corrections?.length) {
61
+ corrections.textContent = event.corrections
62
+ .map((item) => `heard: ${item.original} -> ${item.canonical}`)
63
+ .join(" ");
64
+ }
65
+ return;
66
+ }
67
+
68
+ if (event.type === "token") {
69
+ ink.textContent += event.text;
70
+ return;
71
+ }
72
+
73
+ if (event.type === "done") {
74
+ session = event.state || {};
75
+ if (event.projects?.length) renderProjects(event.projects);
76
+ if (event.whitespace?.length) renderWhitespace(event.whitespace);
77
+ if (event.score) {
78
+ verdictEl.textContent = event.score.verdict;
79
+ overallEl.textContent = Number(event.score.overall).toFixed(1);
80
+ ink.classList.toggle("bleed", event.score.verdict.startsWith("ECHO"));
81
+ ink.classList.toggle("gold", event.score.verdict.startsWith("UNWRITTEN"));
82
+ }
83
+ }
84
+ }
85
+
86
+ function renderProjects(projects) {
87
+ projectsEl.innerHTML = "";
88
+ if (!projects.length) {
89
+ projectsEl.innerHTML = `<div class="empty">No red ink yet.</div>`;
90
+ return;
91
+ }
92
+ for (const project of projects.slice(0, 5)) {
93
+ const item = document.createElement("a");
94
+ item.className = "project";
95
+ item.href = project.url;
96
+ item.target = "_blank";
97
+ item.rel = "noreferrer";
98
+ item.innerHTML = `
99
+ <strong>${escapeHtml(project.title)}</strong>
100
+ <p>${escapeHtml(project.summary || project.id)}</p>
101
+ `;
102
+ projectsEl.append(item);
103
+ }
104
+ }
105
+
106
+ function renderWhitespace(items) {
107
+ whitespaceEl.innerHTML = "";
108
+ if (!items.length) {
109
+ whitespaceEl.innerHTML = `<div class="empty">Gold has not gathered.</div>`;
110
+ return;
111
+ }
112
+ for (const item of items.slice(0, 4)) {
113
+ const gap = document.createElement("div");
114
+ gap.className = "gap";
115
+ gap.innerHTML = `
116
+ <strong>${escapeHtml(item.label)}</strong>
117
+ <p>${escapeHtml(item.pitch)}</p>
118
+ `;
119
+ whitespaceEl.append(gap);
120
+ }
121
+ }
122
+
123
+ function escapeHtml(value) {
124
+ return String(value)
125
+ .replaceAll("&", "&amp;")
126
+ .replaceAll("<", "&lt;")
127
+ .replaceAll(">", "&gt;")
128
+ .replaceAll('"', "&quot;")
129
+ .replaceAll("'", "&#039;");
130
+ }
static/assets/parchment.png ADDED

Git LFS Details

  • SHA256: 37f38a26af36dfab1494ccc9850021443b8ac2f149fc40c1f3080ea3fb098126
  • Pointer size: 132 Bytes
  • Size of remote file: 2.03 MB
static/index.html ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>The Unwritten Almanac</title>
7
+ <link rel="stylesheet" href="/static/styles.css" />
8
+ </head>
9
+ <body>
10
+ <main class="shell">
11
+ <section class="stage" aria-label="The Unwritten Almanac">
12
+ <div class="canopy"></div>
13
+ <div class="book">
14
+ <section class="page page-left">
15
+ <div class="kicker">The Unwritten Almanac</div>
16
+ <h1>Mothback</h1>
17
+ <p id="ink" class="ink">
18
+ The book is open. The next page waits for its first line.
19
+ </p>
20
+ <form id="turn-form" class="prompt-row">
21
+ <input
22
+ id="message"
23
+ name="message"
24
+ autocomplete="off"
25
+ placeholder="A local-first agent for..."
26
+ />
27
+ <button id="submit" type="submit" title="Ink the page">Ink</button>
28
+ </form>
29
+ <div id="corrections" class="corrections" aria-live="polite"></div>
30
+ </section>
31
+
32
+ <section class="page page-right">
33
+ <div class="seal" id="seal">
34
+ <span id="verdict">UNWRITTEN</span>
35
+ <strong id="overall">0.0</strong>
36
+ </div>
37
+ <div class="panels">
38
+ <article>
39
+ <h2>Echoes</h2>
40
+ <div id="projects" class="project-list"></div>
41
+ </article>
42
+ <article>
43
+ <h2>Gold Margins</h2>
44
+ <div id="whitespace" class="whitespace-list"></div>
45
+ </article>
46
+ </div>
47
+ </section>
48
+ </div>
49
+ </section>
50
+ </main>
51
+ <script type="module" src="/static/app.js"></script>
52
+ </body>
53
+ </html>
static/styles.css ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ color-scheme: dark;
3
+ --ink: #24160e;
4
+ --muted-ink: #6b4e35;
5
+ --paper: #d9bd83;
6
+ --paper-deep: #b48a4b;
7
+ --gold: #e6bd3f;
8
+ --red: #8d2d26;
9
+ --leaf: #2f7a49;
10
+ --night: #121612;
11
+ }
12
+
13
+ * {
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ html,
18
+ body {
19
+ margin: 0;
20
+ min-height: 100%;
21
+ background: radial-gradient(circle at 50% 16%, #33442c 0, #182117 42%, #080b08 100%);
22
+ color: #f5ead2;
23
+ font-family:
24
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
25
+ }
26
+
27
+ button,
28
+ input {
29
+ font: inherit;
30
+ }
31
+
32
+ .shell {
33
+ min-height: 100vh;
34
+ display: grid;
35
+ place-items: center;
36
+ padding: clamp(16px, 3vw, 42px);
37
+ }
38
+
39
+ .stage {
40
+ position: relative;
41
+ width: min(1180px, 100%);
42
+ min-height: min(760px, calc(100vh - 40px));
43
+ display: grid;
44
+ place-items: center;
45
+ }
46
+
47
+ .canopy {
48
+ position: absolute;
49
+ inset: 0;
50
+ background:
51
+ linear-gradient(90deg, rgba(0, 0, 0, 0.5), transparent 24%, transparent 76%, rgba(0, 0, 0, 0.55)),
52
+ radial-gradient(circle at 50% 0, rgba(231, 191, 71, 0.18), transparent 42%);
53
+ border-radius: 8px;
54
+ }
55
+
56
+ .book {
57
+ position: relative;
58
+ display: grid;
59
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
60
+ width: min(1100px, 100%);
61
+ min-height: 680px;
62
+ background:
63
+ linear-gradient(90deg, rgba(71, 42, 23, 0.55), transparent 49%, rgba(62, 34, 20, 0.7) 50%, transparent 51%),
64
+ url("/static/assets/parchment.png");
65
+ background-size: cover;
66
+ color: var(--ink);
67
+ border: 1px solid rgba(255, 232, 169, 0.28);
68
+ border-radius: 8px;
69
+ box-shadow:
70
+ 0 30px 80px rgba(0, 0, 0, 0.62),
71
+ inset 0 0 80px rgba(93, 48, 16, 0.38);
72
+ overflow: hidden;
73
+ }
74
+
75
+ .page {
76
+ min-width: 0;
77
+ padding: clamp(22px, 4vw, 56px);
78
+ }
79
+
80
+ .page-left {
81
+ display: flex;
82
+ flex-direction: column;
83
+ }
84
+
85
+ .kicker {
86
+ color: var(--muted-ink);
87
+ font-size: 0.78rem;
88
+ font-weight: 800;
89
+ letter-spacing: 0.08em;
90
+ text-transform: uppercase;
91
+ }
92
+
93
+ h1 {
94
+ margin: 10px 0 22px;
95
+ font-family: Georgia, "Times New Roman", serif;
96
+ font-size: clamp(3rem, 8vw, 6.4rem);
97
+ line-height: 0.9;
98
+ font-weight: 700;
99
+ letter-spacing: 0;
100
+ }
101
+
102
+ h2 {
103
+ margin: 0 0 10px;
104
+ color: var(--muted-ink);
105
+ font-size: 0.78rem;
106
+ line-height: 1.2;
107
+ font-weight: 900;
108
+ letter-spacing: 0.08em;
109
+ text-transform: uppercase;
110
+ }
111
+
112
+ .ink {
113
+ min-height: 214px;
114
+ margin: 0;
115
+ color: #2a170d;
116
+ font-family: Georgia, "Times New Roman", serif;
117
+ font-size: clamp(1.1rem, 1.9vw, 1.55rem);
118
+ line-height: 1.48;
119
+ }
120
+
121
+ .ink.bleed {
122
+ color: var(--red);
123
+ }
124
+
125
+ .ink.gold {
126
+ color: #6a4b00;
127
+ text-shadow: 0 0 18px rgba(230, 189, 63, 0.42);
128
+ }
129
+
130
+ .prompt-row {
131
+ display: grid;
132
+ grid-template-columns: minmax(0, 1fr) 84px;
133
+ gap: 10px;
134
+ margin-top: auto;
135
+ }
136
+
137
+ .prompt-row input {
138
+ min-width: 0;
139
+ height: 48px;
140
+ border: 1px solid rgba(80, 47, 22, 0.38);
141
+ border-radius: 8px;
142
+ padding: 0 14px;
143
+ background: rgba(255, 243, 203, 0.55);
144
+ color: var(--ink);
145
+ outline: none;
146
+ }
147
+
148
+ .prompt-row input:focus {
149
+ border-color: rgba(141, 45, 38, 0.72);
150
+ box-shadow: 0 0 0 3px rgba(141, 45, 38, 0.15);
151
+ }
152
+
153
+ .prompt-row button {
154
+ height: 48px;
155
+ border: 0;
156
+ border-radius: 8px;
157
+ background: #51311d;
158
+ color: #fff3cc;
159
+ font-weight: 800;
160
+ cursor: pointer;
161
+ }
162
+
163
+ .prompt-row button:disabled {
164
+ opacity: 0.58;
165
+ cursor: wait;
166
+ }
167
+
168
+ .corrections {
169
+ min-height: 30px;
170
+ padding-top: 10px;
171
+ color: var(--muted-ink);
172
+ font-size: 0.88rem;
173
+ }
174
+
175
+ .seal {
176
+ width: min(220px, 52vw);
177
+ aspect-ratio: 1;
178
+ margin: 0 auto 28px;
179
+ display: grid;
180
+ place-items: center;
181
+ align-content: center;
182
+ gap: 3px;
183
+ border-radius: 50%;
184
+ background:
185
+ radial-gradient(circle at 36% 30%, rgba(255, 226, 134, 0.72), transparent 26%),
186
+ radial-gradient(circle, #9b2f27 0 58%, #6e211f 59% 100%);
187
+ color: #ffe9a0;
188
+ box-shadow:
189
+ 0 14px 30px rgba(91, 22, 16, 0.35),
190
+ inset 0 0 24px rgba(53, 11, 7, 0.45);
191
+ transform: rotate(-4deg);
192
+ }
193
+
194
+ .seal span {
195
+ max-width: 150px;
196
+ text-align: center;
197
+ font-size: 0.82rem;
198
+ font-weight: 900;
199
+ letter-spacing: 0.08em;
200
+ }
201
+
202
+ .seal strong {
203
+ font-family: Georgia, "Times New Roman", serif;
204
+ font-size: 3.4rem;
205
+ line-height: 1;
206
+ }
207
+
208
+ .panels {
209
+ display: grid;
210
+ gap: 18px;
211
+ }
212
+
213
+ .project-list,
214
+ .whitespace-list {
215
+ display: grid;
216
+ gap: 10px;
217
+ }
218
+
219
+ .project,
220
+ .gap {
221
+ border-left: 3px solid rgba(80, 47, 22, 0.48);
222
+ padding: 8px 10px;
223
+ background: rgba(255, 241, 196, 0.34);
224
+ border-radius: 0 8px 8px 0;
225
+ }
226
+
227
+ .project strong,
228
+ .gap strong {
229
+ display: block;
230
+ color: #2a170d;
231
+ font-size: 0.98rem;
232
+ line-height: 1.25;
233
+ }
234
+
235
+ .project p,
236
+ .gap p {
237
+ margin: 4px 0 0;
238
+ color: var(--muted-ink);
239
+ font-size: 0.86rem;
240
+ line-height: 1.35;
241
+ }
242
+
243
+ .empty {
244
+ color: var(--muted-ink);
245
+ font-size: 0.95rem;
246
+ }
247
+
248
+ @media (max-width: 820px) {
249
+ .shell {
250
+ display: block;
251
+ padding: 0;
252
+ }
253
+
254
+ .stage {
255
+ min-height: 100vh;
256
+ }
257
+
258
+ .book {
259
+ min-height: 100vh;
260
+ grid-template-columns: 1fr;
261
+ border-radius: 0;
262
+ }
263
+
264
+ .page {
265
+ padding: 24px;
266
+ }
267
+
268
+ .ink {
269
+ min-height: 168px;
270
+ }
271
+ }
tests/test_agent.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ from hackathon_advisor.agent import AdvisorEngine
4
+ from hackathon_advisor.data import ProjectIndex
5
+
6
+
7
+ def test_agent_scores_and_persists_idea() -> None:
8
+ index = ProjectIndex.from_file(Path("data/projects.json"))
9
+ engine = AdvisorEngine(index)
10
+
11
+ result = engine.turn("A local-first archive cartographer for family photos", {})
12
+
13
+ assert result.score is not None
14
+ assert result.state["ideas"]
15
+ assert result.state["ideas"][0]["score"] is not None
16
+ assert result.response
17
+
18
+
19
+ def test_agent_finds_whitespace() -> None:
20
+ index = ProjectIndex.from_file(Path("data/projects.json"))
21
+ engine = AdvisorEngine(index)
22
+
23
+ result = engine.turn("write bolder and find whitespace", {})
24
+
25
+ assert result.whitespace
26
+ assert result.score is not None
27
+ assert result.artifact["verdict"]
28
+
29
+
30
+ def test_agent_preserves_canonical_jargon_case() -> None:
31
+ index = ProjectIndex.from_file(Path("data/projects.json"))
32
+ engine = AdvisorEngine(index)
33
+
34
+ result = engine.turn("use neutron and mini cpm on zero gpu", {})
35
+
36
+ assert "MiniCPM5" in result.artifact["title"]
37
+ assert "ZeroGPU" in result.artifact["title"]
tests/test_aliases.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from hackathon_advisor.aliases import normalize_text
2
+
3
+
4
+ def test_normalize_jargon_alias() -> None:
5
+ text, corrections = normalize_text("use neutron with mini cpm on zero gpu")
6
+
7
+ assert "Nemotron" in text
8
+ assert "MiniCPM5" in text
9
+ assert "ZeroGPU" in text
10
+ assert {item.canonical for item in corrections} >= {"Nemotron", "MiniCPM5", "ZeroGPU"}
tests/test_data.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ from hackathon_advisor.data import ProjectIndex
4
+
5
+
6
+ def test_project_index_searches_snapshot() -> None:
7
+ index = ProjectIndex.from_file(Path("data/projects.json"))
8
+
9
+ hits = index.search("lullaby children audio", limit=3)
10
+
11
+ assert hits
12
+ assert hits[0].project.id.startswith("build-small-hackathon/")
13
+
14
+
15
+ def test_project_index_whitespace() -> None:
16
+ index = ProjectIndex.from_file(Path("data/projects.json"))
17
+
18
+ items = index.find_whitespace(limit=3)
19
+
20
+ assert len(items) == 3
21
+ assert all(item.label for item in items)