Spaces:
Running on Zero
Running on Zero
fix: align space metadata
Browse filesCo-authored-by: Codex <noreply@openai.com>
- .gitattributes +3 -35
- .gitignore +181 -0
- DESIGN.md +492 -0
- LICENSE +21 -0
- README.md +55 -6
- app.py +99 -0
- data/projects.json +2116 -0
- hackathon_advisor/__init__.py +5 -0
- hackathon_advisor/agent.py +238 -0
- hackathon_advisor/aliases.py +80 -0
- hackathon_advisor/data.py +245 -0
- hackathon_advisor/scoring.py +88 -0
- hackathon_advisor/tools.py +117 -0
- pyproject.toml +24 -0
- requirements.txt +1 -0
- scripts/crawl_hf_spaces.py +81 -0
- scripts/make_assets.py +50 -0
- static/app.js +130 -0
- static/assets/parchment.png +3 -0
- static/index.html +53 -0
- static/styles.css +271 -0
- tests/test_agent.py +37 -0
- tests/test_aliases.py +10 -0
- tests/test_data.py +21 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
*
|
| 3 |
-
|
| 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:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.16.0
|
| 8 |
-
python_version:
|
| 9 |
app_file: app.py
|
| 10 |
-
pinned:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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("&", "&")
|
| 126 |
+
.replaceAll("<", "<")
|
| 127 |
+
.replaceAll(">", ">")
|
| 128 |
+
.replaceAll('"', """)
|
| 129 |
+
.replaceAll("'", "'");
|
| 130 |
+
}
|
static/assets/parchment.png
ADDED
|
Git LFS Details
|
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)
|