DEV Community

Cover image for Sloth Recap: wake up to 2 lines instead of 200 group-chat messages (EmbeddingGemma, 100% in your browser)
asosa933
asosa933

Posted on

Sloth Recap: wake up to 2 lines instead of 200 group-chat messages (EmbeddingGemma, 100% in your browser)

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission πŸ¦₯

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady

What I Built

Sloth Recap is a small web app for one very lazy moment: waking up and finding 200 new messages in a group chat.

You drop in the chat export (WhatsApp "Export chat" .txt or .zip, or just paste the text), type your name once, and press πŸ¦₯ Catch me up. You get:

  • β˜• a 2-line recap of what happened since your last message,
  • πŸ™‹ Needs you: only the messages that mention you or ask you something,
  • 🧺 Topics: every other message tucked under the line it belongs to, in case you want the details.

Group chats never sleep. In the morning you scroll through "lol", "πŸ˜‚", "ok" and three conversations happening at the same time, just to find the one question somebody asked you. Sloth Recap does that scrolling for you, so whoever reads the group last in the morning gets to be the lazy one.

One design rule I care about: Sloth Recap only quotes real messages. It picks them, it never writes new text, so it can't invent a plan nobody made. When the recap says "Technician comes Wednesday between 9 and 12", someone really wrote that.

Demo

πŸ‘‰ Live demo: https://asosa933.github.io/sloth-recap/

Click one of the two sample chats (🏠 English flatmates or ⚽ Italian football group), then Catch me up. The first AI run downloads the model once (about 175 MB with WebGPU or about 310 MB on CPU) and then the browser keeps it cached. If you don't want the download, untick "Use local AI" and you get the simple keyword mode instantly.

Sloth Recap demo

The result for the flatmates chat: 38 new messages became 2 lines, plus the 3 messages that actually need me.

2-line recap, needs you and topics

It also works in Italian (and on mixed-language chats), because the model is multilingual:

Italian football group chat recap

Code

GitHub logo asosa933 / sloth-recap

Wake up to a 2-line recap of your group chat instead of 200 messages. 100% in-browser with open-weight EmbeddingGemma (transformers.js).

πŸ¦₯ Sloth Recap

Wake up to 2 lines, not 200 group-chat messages.

Sloth Recap reads a group chat export (WhatsApp .txt or .zip, or pasted text) and gives you:

  • a 2-line recap of what happened since your last message,
  • πŸ™‹ Needs you: the messages that mention you or ask you something,
  • 🧺 topics: every message grouped under the line it belongs to, if you want the details.

Everything runs in your browser. The open-weight model EmbeddingGemma runs locally with transformers.js. There is no server, no account and no API key, and your chat is never uploaded.

πŸ‘‰ Live demo: https://asosa933.github.io/sloth-recap/ (click a sample chat, then "Catch me up")

Sloth Recap demo: sample chat β†’ 2-line recap

Sloth Recap: 2-line recap, needs you, topics

Built for the DEV Hacktoberfest Open-Source AI Challenge, Week 3: "Slow and Steady". The project was created during the Week 3 window (October 2026).

The lazy payoff

On the built-in "Flat 3B" sample, 38 messages arrived…

How I Built It

It's plain HTML, CSS and JavaScript: no build step, no backend, hosted for free on GitHub Pages. The pipeline looks like this:

chat export β†’ parser β†’ "messages after my last one" β†’ drop "ok/lol/πŸ‘"
            β†’ EmbeddingGemma vectors (Web Worker) β†’ pick key messages β†’ group the rest
            β†’ 2 lines + "needs you" + topics
Enter fullscreen mode Exit fullscreen mode

The open-weight model: EmbeddingGemma. In a Web Worker I load onnx-community/embeddinggemma-300m-ONNX with transformers.js. It runs on WebGPU when the browser has it and on the CPU (WASM) otherwise. Every new message becomes a vector, using EmbeddingGemma's task: sentence similarity prompt. Then:

  1. Center the vectors. To a small embedding model, all chat messages look "kind of similar". On my test chats, unrelated pairs averaged a cosine similarity of about 0.58 and same-topic pairs about 0.63, which is almost no gap. Subtracting the chat's average vector moved them to about -0.07 and +0.06, so the gap got more than twice as big and topics became much easier to tell apart.
  2. Borrow context from neighbours. "Work for me" or "Ma dai 😑" mean nothing alone, so each message gets a bit of the vectors of the messages around it.
  3. Pick key messages. A message scores high if many others talk about the same thing, if it has concrete facts (a time, a day, a price) and if it's recent. I pick them one by one and penalise messages too similar to ones already picked (the classic MMR trick), so the 2 lines are about 2 different things.
  4. Group everything else under the key message it is closest to. Topics where someone asked me something go first.

The πŸ™‹ Needs you list is intentionally not AI. It's simple rules: your name as a whole word or @name, plus question patterns in English and Italian. For "did someone ask me something?" I want rules that are 100% predictable.

Deterministic fallback. If the model can't load (or you turn it off), the exact same pipeline runs on TF-IDF keyword vectors. I tested this by blocking Hugging Face in headless Chrome: the app showed "Local AI: failed to load" and still produced the recap in simple mode.

Making it fast on a normal laptop CPU was the part I learned the most from. On my CPU-only test machine (headless Chrome, 38 messages):

  • 4-bit "no_gather" model, 1 thread: about 26 s 😴
  • 8-bit model, 1 thread: about 8 s
  • 8-bit model, 4 threads: about 3 s

The 4-bit export is the smaller download, but on WASM it was about 3x slower than the 8-bit file. I don't know exactly which operator is the slow one, but the measurement was clear, so the app uses 8-bit on CPU (and 4-bit only on WebGPU). Multi-threading needs a "cross-origin isolated" page, and GitHub Pages can't set those headers. So I added a tiny service worker (sw.js, about 20 lines) that adds them to the site's own files. After the first load, the model can use several CPU threads (4 on my test machine).

Testing.

  • npm test: 11 unit tests with node:test (WhatsApp Android/iPhone formats, US/EU dates, multi-line messages, fillers, "needs you", the fallback digest, reading a .zip).
  • A Puppeteer script opens the live GitHub Pages site in headless Chrome. It downloads the model, runs both samples with the AI, checks the "needs you" counts and the copy button, loads a .zip export, and runs the no-AI mode. All the screenshots and the GIF in this post were captured from the live site.
  • eval/eval-model.mjs runs the real model in Node on 5 small chats I wrote and labelled by hand (the 2 demo chats plus 3 held-out ones, one of them mixed Italian/English):
EmbeddingGemma TF-IDF fallback
nearest message is about the same topic 67/88 (76%) 64/88 (73%)
real topics covered by the top 3 lines 14/15 14/15
messages grouped under the right line 72/81 (89%) 64/78 (82%)
mixed-language chat, grouped right 11/13 9/13

To be honest: it's a tiny dataset that I wrote myself, so it's a sanity check, not a benchmark. Both modes pick good lines on these chats. The model's advantage shows when it groups messages, especially short replies and messages that switch language ("The heating in my room is not working" / "Anche da me il termosifone Γ¨ freddo").

AI disclosure: I'm a beginner from Italy and I built this with a lot of help from an AI coding assistant (code, tests, debugging, measurements). The idea, the "never write new text" rule, the choices about what is AI and what is rules, and the testing are mine. I made sure I understand every file.

Why Does Open Innovation Matter?

For this project, open weights are the whole point:

  • Group chats are private. They're full of other people's messages, addresses, plans and jokes. Pasting your friends' messages into a cloud API without asking them isn't fair to them. With EmbeddingGemma running in the browser tab, the chat never leaves the device. There's no server that could even receive it.
  • It costs nothing to run. No API key, no per-message price, no account. The model runs on your own CPU/GPU, and the site is a static page on GitHub Pages. A "lazy" tool can't come with a monthly bill.
  • No AI service to depend on. After the first visit the model comes from the browser cache. I checked it: with Hugging Face completely blocked, the second visit still ran the AI. Nobody can rate-limit it, change its price, or shut it down.
  • I could look inside and change things. Because the weights, the ONNX exports and the runtime are all open, I could compare the 4-bit and 8-bit files on my own machine, measure that one was 3x slower, and switch. A closed API is a black box: you get one speed and one price.
  • It's swappable. The model name is one line in src/model-worker.js. If a better small open embedding model comes out next month, I can try it in an afternoon, and the TF-IDF fallback means the app never depends on any single model.

The Lazy Payoff

Measured by the app itself on the built-in flatmates chat:

  • 38 new messages arrived after my "going to bed" message.
  • Reading them all: about 1 min 27 s (word count at 200 words per minute, before even counting the scrolling).
  • Reading the recap and the "needs you" list: about 18 s, and I only have to answer 3 messages: open the door for the technician, bring the tiramisu, confirm Wednesday.
  • On my CPU-only test machine the AI needed about 4 s to read those 38 messages (after the one-time model download, which took about 30 s in my tests).

A busier group means more reading time, but the recap is still 2 lines. πŸ¦₯

Prize Categories

  • Best Use of Gemma: the AI in Sloth Recap is EmbeddingGemma, Google's open-weight Gemma embedding model. It runs locally in the browser with transformers.js (WebGPU or WASM) and powers the topic finding and grouping, with no server and no API.

Thanks for reading! If your group chat export breaks the parser (there are many date formats out there), please open an issue with a fake example and I'll fix it. πŸ¦₯

Top comments (1)

Collapse
 
arcwrightai profile image
arcwrightai •

Waking up to 2 lines instead of 200 messages sounds like a dream. Does it handle sarcasm and inside jokes?