This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
An independent bookshop sells by conversation. Someone walks in and asks for “something to read when your heart is broken”, or “something sad, but not too sad”, and the bookseller knows where to reach. Online, the same shop has a catalogue searched by title. The reader who doesn’t know the title leaves, and the shop never learns what they were looking for.
Subtext puts a bookshop’s collection on display and lets readers ask for it in their own words. A local model reads every synopsis once; after that, the shelf answers requests by meaning, shows why each book was chosen, says when nothing fits, and keeps what readers asked for, without who asked, so the bookseller knows what to stock.
It has two sides:
- For readers, the collection on display. Ask for “something melancholic, but comforting” and the closest books step out of their niches, while a note in the margin says how the request was heard. Open a book and it unfolds as its dust jacket: how it reads, why it answers your request (with a line quoted from its own synopsis), its price, and a reservation. Ask for “vegetarian recipes” in a literary bookshop and it tells you the collection has nothing for that, instead of inventing an answer.
- For the bookshop, the counter. Each book carries the AI’s reading (atmospheres, pace, density, voice, themes) together with the passages it relied on. The bookseller can correct any of it, and from then on the reading is theirs; the AI never overwrites it. The Demand room groups every search by meaning: what the shelf could not answer, as a list to buy from, and the moods readers ask for against the moods the shelf actually holds.
Who would pay for it
Independent bookshops. They already sell by recommendation; Subtext gives them that conversation online, and a monthly reading of what their readers want.
- More of the catalogue gets found: readers who don’t know a title can still reach a book, and the jacket ends in a reservation.
- A buying list written by readers: what was asked for and not found, and which moods are asked for more than they are stocked.
- Cataloguing help that stays the bookseller’s: new books are read on arrival, and corrections are kept.
- A way into the catalogue for staff who haven’t read it yet. This one came from the bookseller I showed it to (below).
Because it runs on a computer the shop already has, there is no cost per search, so the price can be a flat subscription for installing, updating and supporting it. The bookseller I showed it to named around R$ 50 a month. If a shop keeps about a third of a R$ 60 book, that is paid for by three more books sold a month.
Demo
Music: “Cylinder Five” by Chris Zabriskie (chriszabriskie.com/cylinders), licensed under CC BY 4.0.
The catalogue in the demo has 277 real books with demo prices and stock, and 21 sample bookseller notes. The searches in the Demand room are sample requests run through the real shelf. All of them are marked as such in the interface.
Code
Subtext
A shelf that understands what readers want to feel, for independent bookshops. Open models, running in the shop.
An independent bookshop sells by conversation: “something sad, but not too sad”, “like that one I read last summer”. Online, its catalogue is a list searched by title. The reader who doesn’t know the title leaves, and the bookshop never learns what they were looking for.
Subtext puts the shop’s collection on display and lets readers ask for it in their own words. A local model reads every synopsis once; the shelf then answers requests by meaning, shows its reasons, says when nothing fits, and keeps what readers asked for, without who asked, so the bookshop knows what to stock.
What it does
For readers, the collection on display.
- Ask in your own words: “something melancholic, but comforting”, “a book that feels like the sea”, “for those who liked Clarice Lispector”…
Next.js, SQLite and Ollama. The README has the setup, and DEMO.md is a four-minute tour of every room.
How I Built It
Everything runs through Ollama on the shop’s own machine, with two open-weight Gemma models doing different jobs:
-
gemma4:e4breads each book once, in the background. For every synopsis it returns the book’s atmospheres in order of predominance (from a fixed set of twelve, like melancholic, comforting, ironic, luminous), its pace, density, voice and themes, and the passages from the synopsis that support each one. On a Mac with an M4 chip and 16 GB of memory, the first 200 books took 47 minutes, four at a time. -
embeddinggemmaanswers requests. A request is embedded and compared with the books; then the shelf reads the request itself (“melancholic, but comforting”, “not too long”, “like Clarice Lispector”) and reranks by each book’s reading. A new request is answered in about 30 ms (median of eleven), with a 621 MB model. - Confidence decides what is shown. When the best match is weak, the shelf shows fewer books and says the match is loose; when nothing comes close, it declines.
Does it actually find better?
I put the same 31 requests to three engines: keyword search (SQLite FTS5), plain embeddings over the synopsis, and the shelf. A third Gemma model, gemma4:12b, graded the first five answers of each from 0 to 2. Twenty-four requests are graded; the other seven ask for things a bookshop can’t answer, and there I only checked whether the shelf declines.
| engine | nDCG@5 | P@5 |
|---|---|---|
| keywords | 0.41 | 0.48 |
| embeddings only | 0.63 | 0.70 |
| shelf | 0.67 | 0.74 |
The honest reading: the shelf beats keywords clearly (better on 15 of 24 requests, worse on 6), and is level with plain embeddings overall (better on 9, worse on 8; the gap is smaller than one request can move the mean). Where it pulls ahead is the point of the project: emotional requests (0.86 against 0.62) and requests with a “but”. It trails plain embeddings when a request names an author (0.56 against 0.70). It declined all 7 out-of-scope requests. The judge is a model, not a reader, so the numbers are a lead; the counter has a Search evaluation room with every query and its three lists, so anyone can check them.
Why Does Open Innovation Matter?
For a bookshop, open models aren’t a philosophical choice; they’re what makes the product possible.
- Readers’ requests are the shop’s most valuable data, and they stay in the shop. The Demand room is built entirely on what people ask for. With a closed API, every one of those requests would leave the shop to be processed on someone else’s server. Here, no request, catalogue entry or note leaves the computer, and searches are kept for 90 days without who made them.
- No fee per search. A bookshop can’t price a product around per-call API costs that grow with how much its readers browse. Answering a request needs a 621 MB embedding model running locally, so the marginal cost of a search is zero, and the subscription can be flat.
- The AI can be checked, not just trusted. Every reading quotes the passages it relied on, and every answer lists its reasons. When the bookseller disagrees, they correct it, and their correction wins. That works because the whole pipeline is ours to shape: prompts, taxonomy, reranking and thresholds.
- No lock-in. No account, no API key, no vendor. The models are swapped with two environment variables, so the shop can move to better open models as they come out.
Where open worked better than closed, concretely: the reading step asks the model to justify its reading with passages from the synopsis, and the app keeps a passage only if it is found there word for word; a reading with fewer verified passages gets a lower confidence. Because the model runs locally, re-reading the whole catalogue after changing the prompt or the model is one command (npm run ai:enrich -- --all) and costs nothing but time.
My First User
I showed Subtext to a bookseller at an independent bookshop in Fortaleza, to find out whether searching by feeling, in plain words, would be useful behind a real counter.
I asked her to explore it freely, as if she were helping a customer. She asked for a lighthearted but meaningful story for someone going through a difficult time. The recommendations caught her attention, the explanations most of all.
Then I opened the AI’s reading of a book she already knew. She partly agreed with it, and asked the question I keep coming back to: how could she trust a reading of a book she hadn’t read?
The most useful moment came when I asked whether she would actually use it:
“I already know the books I sell pretty well. This would probably be more useful for someone new working here who hasn’t learned the catalog yet.”
She said she might pay around R$ 50 a month, as long as it worked with her own inventory.
What I learned. I had assumed Subtext would help experienced booksellers recommend. The conversation pointed somewhere more specific: helping new staff learn the catalogue, and recommend books they haven’t read yet with some confidence.
What I haven’t solved yet.
- An AI reading can sound convincing and still be wrong. The quoted passages let the bookseller check it, and her corrections win, but checking still takes someone who knows the book.
- Her inventory is the real catalogue. The counter already imports a shop’s spreadsheet, but the shelf can only read a book that comes with a synopsis, and a shop’s stock list rarely has one. Finding synopses for an arbitrary inventory isn’t built yet.
This was one conversation, not enough to validate demand. But it gave me something more useful than praise: a concrete hypothesis about who would benefit.
Prize Categories
Best Use of Gemma: Subtext runs on three Gemma models. embeddinggemma answers every search, gemma4:e4b reads every book and quotes its evidence, and gemma4:12b judges the search evaluation. All three run locally through Ollama.





Top comments (0)