BooruSwipe is a local swipe-based recommender for Gelbooru and Danbooru with a Tinder-like interface.
You swipe left/right on images, the app records tag-level feedback, and an LLM periodically turns that feedback into better search tags.
booruswipe-vid.mov
- Serves images from Gelbooru or Danbooru in a Tinder-like interface
- Records likes, dislikes, and weighted swipes
- First images are random (the amount is configurable, default: 10), then it uses an LLM to generate search tags that get used for next image selection
- If no LLM is connected, simply uses the top liked tags to query the next image
- Python 3.10+
- Booru credentials for one of:
- Gelbooru (https://gelbooru.com/index.php?page=account&s=options - api_key and user_id, bottom of the page)
- Danbooru (https://danbooru.donmai.us/profile - api key and username)
- One of:
- OpenAI-compatible LLM API
- Ollama
- LM Studio
git clone <repository-url>
cd booruswipe
pip install -e .
cp booru.conf.example booru.confEdit booru.conf and set:
BOORU_SOURCE- booru credentials for the selected source
api_keybase_urlmodel
Minimum example for Gelbooru:
BOORU_SOURCE=gelbooru
gelbooru_api_key=YOUR_GELBOORU_API_KEY
gelbooru_user_id=YOUR_GELBOORU_USER_ID
api_key=your-api-key
base_url=https://api.openai.com/v1
model=gpt-4o-miniMinimum example for Danbooru:
BOORU_SOURCE=danbooru
danbooru_api_key=YOUR_DANBOORU_API_KEY
danbooru_user_id=YOUR_DANBOORU_LOGIN
api_key=your-api-key
base_url=https://api.openai.com/v1
model=gpt-4o-miniThen start the app:
python -m booruswipeOpen http://localhost:8000 and swipe a few times before expecting the recommendations to adapt.
Run with verbose logs:
python -m booruswipe --verboseDocker is supported as an alternative local setup path.
First create the config file:
cp booru.conf.example booru.confEdit booru.conf before starting the container.
Then build and run:
docker compose build
docker compose upThe compose setup:
- mounts
./booru.confinto the container at/app/booru.conf - persists the SQLite database in a named Docker volume
- runs the app with
--verbose
Open http://localhost:8000.
Important: if your LLM runs on the host machine, do not use localhost in booru.conf when running through Docker.
Inside the container:
localhostmeans the container itself- host services should be reached via
host.docker.internal
Examples:
# LM Studio running on host
base_url=http://host.docker.internal:1234/v1
# Ollama running on host
base_url=http://host.docker.internal:11434/v1If you change only booru.conf, rebuild is not needed. Restart is enough:
docker compose restartUseful commands:
docker compose logs -f
docker compose down
docker compose down -vConfiguration lives in booru.conf.
BOORU_SOURCE=gelbooruSupported values:
danboorugelbooru
Gelbooru credentials are required.
gelbooru_api_key=YOUR_GELBOORU_API_KEY
gelbooru_user_id=YOUR_GELBOORU_USER_IDDanbooru credentials are required. danbooru_user_id should contain your Danbooru login name, despite the legacy variable name.
danbooru_api_key=YOUR_DANBOORU_API_KEY
danbooru_user_id=YOUR_DANBOORU_LOGINapi_key=your-api-key
base_url=https://api.openai.com/v1
model=gpt-4o-miniExample providers:
| Provider | api_key | base_url | model |
|---|---|---|---|
| OpenAI | sk-... |
https://api.openai.com/v1 |
gpt-4o-mini |
| Ollama | ollama |
http://localhost:11434/v1 |
llama3.2 |
| LM Studio | lm-studio |
http://localhost:1234/v1 |
local-model |
Current defaults:
LLM_MIN_SWIPES=10
LLM_MAX_TAGS=30
LLM_TAG_FILTER_MIN_COUNT=1
LLM_USE_STRUCTURED_OUTPUT=true
LLM_RECENT_POSITIVE=10
LLM_RECENT_NEGATIVE=10
LLM_RECENT_FILTER_CUMULATIVE_LIKES=true
BOORU_TAGS_PER_SEARCH=5
BOORU_TAGS_PER_SEARCH_FALLBACK=3
RANDOM_IMAGE_CHANCE=5
DOUBLE_LIKED_NEVER_IGNORE=false
BOORU_SEARCH_LIMIT=100
BOORU_SEARCH_PAGES=5
BOORU_SEARCH_SLEEP=0.15All supported settings:
| Setting | Required | Default | Meaning |
|---|---|---|---|
api_key |
No | none | API key for the LLM provider |
base_url |
Yes | https://api.openai.com/v1 |
Base URL for the LLM provider |
model |
Yes | none | Model name used for chat completions |
BOORU_SOURCE |
Yes | gelbooru |
Which booru backend to use |
danbooru_api_key |
If BOORU_SOURCE=danbooru |
none | Danbooru API key |
danbooru_user_id |
If BOORU_SOURCE=danbooru |
none | Danbooru login name |
gelbooru_api_key |
If BOORU_SOURCE=gelbooru |
none | Gelbooru API key |
gelbooru_user_id |
If BOORU_SOURCE=gelbooru |
none | Gelbooru user ID |
LLM_MIN_SWIPES |
No | 10 |
Swipes required before LLM analysis starts |
LLM_MAX_TAGS |
No | 30 |
Max number of cumulative tags sent to the LLM |
LLM_TAG_FILTER_MIN_COUNT |
No | 1 |
Minimum absolute tag score to include in LLM input |
LLM_USE_STRUCTURED_OUTPUT |
No | true |
Whether to validate LLM output against the response schema |
LLM_RECENT_POSITIVE |
No | 10 |
Number of top recent positive tags sent to the LLM |
LLM_RECENT_NEGATIVE |
No | 10 |
Number of top recent negative tags sent to the LLM |
LLM_RECENT_FILTER_CUMULATIVE_LIKES |
No | true |
Whether recent positive tags already present in cumulative likes should be filtered out before sending recent trend data to the LLM |
BOORU_TAGS_PER_SEARCH |
No | 5 |
Max tags used in the primary booru search |
BOORU_TAGS_PER_SEARCH_FALLBACK |
No | 3 |
Max number of top liked tags used in the fallback search query |
RANDOM_IMAGE_CHANCE |
No | 5 |
Percent chance to skip recommendation logic and show a random image |
DOUBLE_LIKED_NEVER_IGNORE |
No | false |
Whether double-liked images are exempt from repeat filtering |
BOORU_SEARCH_LIMIT |
No | 100 |
Images requested per booru search page |
BOORU_SEARCH_PAGES |
No | 5 |
Number of pages to scan before giving up |
BOORU_SEARCH_SLEEP |
No | 0.15 |
Delay between paginated booru requests in seconds |
Main endpoints:
GET /api/imagePOST /api/swipeGET /api/statsGET /api/settingsPOST /api/settingsPOST /api/settings/testGET /health
Notes:
- For Gelbooru, image display goes through backend proxying to avoid hotlink issues.
- For Danbooru, the frontend gets the direct image URL.
- Swipes store both
post_urland originalfile_url.
The local SQLite database stores:
swipes: each swipe event, booru source, tags, post URL, file URL, weighttag_counts: long-term like/dislike counters per tagswiped_images: IDs used to reduce repeatsdouble_liked_images: IDs exempted from repeat filteringpreference_profiles: latest saved LLM output
Run tests:
pytest -qReset the database:
python -m booruswipe --reset-dbThe selection loop is currently:
- Pull cumulative tag counts from swipe history
- Pull recent tag scores from the latest swipes
- Ask the LLM for recommended search tags
- Search the booru with those tags
- If that fails, fall back to top liked tags
- If that fails, fall back to random
Important detail: the app improves search queries, but it does not yet rank candidate images after retrieval. It is closer to "adaptive search term generation" than "best-image scoring."
- Single-user oriented: request/session state is kept in process memory
- Recommendation quality is heuristic and tag-based
- Score retrieved candidates instead of picking randomly from search results
- Track tag pairs / tag combinations, not only independent tag counts
- Add smarter exploration instead of relying mostly on random chance
- Replace global in-memory session state with per-user session handling
- Add live integration tests for Danbooru, Gelbooru, and LLM providers
MIT