Hammock: my group chat in two lines, read by Gemma inside a browser tab š¦„
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady What I Built Hammock 𦄠turns a group chat you have been ignoring into two lines: What happened, written by Gemma.
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
What I Built
Hammock 𦄠turns a group chat you have been ignoring into two lines:
- What happened, written by Gemma.
- What needs you, found by code and phrased by Gemma as a short to-do, with three one-tap reply drafts (š On it, ā³ Later, š Kind no).
You export a WhatsApp group (ā® ā More ā Export chat ā Without media), drop the .txt on the page, type your name, and lie back. Everything runs inside the browser tab: no server, no account, no API key. After the first visit it opens and runs with the Wi-Fi off.
It is for the person who opens a family or college group after a busy day, sees "312 unread", and closes the app again. You only need to know two things: did anything change, and is anyone waiting on me? Hammock answers those and skips the rest.
The laziest part needs no AI at all. Before a model wakes up, plain code:
- finds your last message and only looks at what came after it,
- skips the good-mornings, forwards, stickers, emoji-only lines, "very nice š" and "This message was deleted", and tells you how many it skipped,
- pulls out every message that mentions your name or nickname.
Only what survives goes to Gemma.
Demo
Try it: https://aniruddhaadak80.github.io/API_projects/hammock/
Use Chrome or Edge on a laptop with WebGPU for the real experience. Two made-up sample chats are built in (a family group in the Android export format, a hackathon team in the iOS format), so you can try it without sharing anything. The first run downloads Gemma once (about 0.8 GB) and your browser caches it.
Code
aniruddhaadak80
/
API_projects
Deployment URL of API_projects
Hammock lives in the hammock/ folder (MIT). No build step: one index.html, plain ES modules, a Web Worker for the model, and a small service worker for offline use.
src/core.js parser, noise filter, "since you last spoke" window, mention finder,
prompts, output cleaners, number check (pure functions, no DOM)
src/worker.js the only file that touches a model (Transformers.js, WebGPU or WASM)
src/app.js UI and worker plumbing
test/ 14 node --test unit tests for core.js
docs/eval.md what I measured, including where the small model gets it wrong
How I Built It
Open pieces at the core
-
Gemma 3 1B IT (4-bit ONNX,
onnx-community/gemma-3-1b-it-ONNX) is the default "Nap" model. On WebGPU it usesq4f16, orq4when the GPU has no fp16 support. - Gemma 4 E2B IT (4-bit ONNX, about 3 GB) is the optional "Deep sleep" model, WebGPU only.
- Transformers.js on ONNX Runtime Web runs both, inside a Web Worker so the page stays responsive. Weights come from the Hugging Face Hub once and stay in the browser cache.
The design rule: code decides, Gemma writes.
Small models are good at compressing a messy thread into plain language. They are bad at who did what and at digits. So I split the work:
| Job | Who does it |
|---|---|
| Parse Android / iOS exports, 12h and 24h clocks, day-first and month-first dates | Code |
| Find your last message, skip the noise, count what was skipped | Code |
| Decide which messages are aimed at you | Code (word match on your name and nicknames) |
| Write "what happened" | Gemma |
| Turn each "needs you" message into a to-do | Gemma, then a cleaner that rejects bad output |
| Check every number in the summary against the chat | Code, and the UI flags anything Gemma made up |
| Draft a reply in the tone you tap | Gemma |
Long chats are split into chunks the 1B model can hold, summarised part by part, then merged.
What I measured (all on made-up chats, on a 2-vCPU sandbox with no GPU)
With the 4-bit weights the WebGPU path uses (run in Node with onnxruntime-node, greedy decoding, the exact prompts from src/core.js):
| Gemma 3 1B | Gemma 4 E2B | |
|---|---|---|
| Family group, line one | "The wedding of Rini is moved to December 12th due to a double booking. Train bookings are being finalized for December 11th." (9.9 s) | "Rini's wedding date has changed from 5 Dec to 12 Dec due to a double booking. A gift contribution of 2000 each is due by 1 Dec." (23.6 s) |
| Hackathon team, line one | Got the gist, but blamed the wrong person for a broken build ā | Right people, and it said Meera "fixed" the Safari import when it was her cache (21.9 s) |
| To-dos | "Book your leave for the wedding", "Call Dida this week", "Start the how-it-works section" (about 2.5 s each) | similar, a bit more specific, about 2x slower |
That wrong-person mistake is the reason "what needs you" is never decided by the model.
The honest bit: the CPU fallback. Without WebGPU, Hammock falls back to WASM. ONNX Runtime Web's WASM backend has no kernel for the GatherBlockQuantized op in the 4-bit export (I also tried the 8-bit Gemma 3 270M export, same error), so the CPU path uses the 1 GB 8-bit 1B export. I ran the full app in headless Chromium on that path and it exposed two real bugs: the summary came back empty, and one to-do was the prompt's own instruction ("Start with a verb, under 12 words"). Now:
- empty summary ā one retry on the last 8 messages; if that fails too, the page says so instead of pretending,
- to-dos that echo the instructions, start with "Okay, here'sā¦", or ramble are rejected and replaced with a plain excerpt of the message,
- "Ma is asking you to book leave" is rewritten to "Book leave".
After the fixes the CPU run finished end to end in 132 s for the family sample. The retried summary was fluent but wrong about who did what, and I put that exact screenshot in the README rather than hiding it:
Not measured yet: speed on a real laptop GPU, Safari, Firefox, phones. If you try it, I would love a comment with your browser, GPU and how long it took.
Why Does Open Innovation Matter?
Group chats are the most personal text most of us have: family plans, money, health updates, people's phone numbers. A closed API would mean uploading all of that to someone else's server just to be told "the wedding moved".
With open weights running in the tab:
-
The chat never leaves the device. The file is read with
FileReader. In the headless runs, every request the page made was a GET for the app's own files, Transformers.js from jsDelivr, or the model weights from Hugging Face. No request carried chat text. You can check yourself in DevTools ā Network, or just switch the Wi-Fi off after the first run. - It costs nothing to run, forever. No key, no quota, no bill. Set it up once and it keeps working.
- It works offline. Train, flight, bad signal, it does not care.
- I can see why it fails. Because I control the model, the prompts and the decoding, I could measure exactly where the 1B model goes wrong and build the guard rails (code-found mentions, number check, output rejection) around it. With a black-box API I would only see the final text.
The Lazy Payoff
On the made-up family chat: 74 messages since "Arjun" last spoke. Code skips 64 of them without reading (19 greetings, 16 emoji-only, 13 photos or stickers, 12 one-word reactions, 2 deleted, 2 forwards), Gemma reads the remaining 10, and you get back two lines plus two to-dos, each with a reply draft one tap away.
I have only measured this on the two sample chats so far, so I am not going to claim minutes saved per day. What I can say: the skip and mention counts are deterministic and pinned by tests, so the "you can ignore these 64" part does not depend on the model being right.
Prize Categories
- Best Use of Gemma: Gemma 3 1B (default) and Gemma 4 E2B (optional) run entirely in the browser through Transformers.js and ONNX Runtime Web. Gemma writes the summary, the to-dos and the replies; code checks its work.
Thanks for reading. Now go lie down. š¦„
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes ā full credit and traffic to the original publisher.

