This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
Team: @lydiazhang135, @__62aa88378d32238eef5e, @shiyi_jiang_92dc7754aa4d9 and me.
Your partner sends two characters: 没事, "it's nothing." You have typed and deleted three replies. Is it really nothing? Do they want an apology, an explanation, or some space? And whatever you send next, do you want a cloud service to read the whole conversation first?
TL;DR
- Paste your partner's message. A 322M model, Laya, reads the subtext in under half a second, and Gemma 4 E2B writes two replies; on a laptop CPU the first is done in about 3.6 s. You edit one and send it yourself.
- Both models run on your own machine: by default nothing you paste leaves it, and with no per-message cost the app can sell for a one-time US$14.99 (¥68 in China), where the main cloud apps like it charge US$29.99–79.99 a year.
- Fine-tuning the judge for about US$3 lifted its macro-F1 from 0.33 to 0.57 and its safety recall from 0% to 62% (the bar I set before training is 90%). The fine-tuned Gemma won 133 and lost 67 of 297 blind comparisons with stock Gemma (the rest tied). The app has used both since 18 October; a final, held-back exam comes next.
- Try it in your browser (stand-in models, sample data), and recompute the evaluation numbers below with one command:
npm run reproduce.
What I Built
Laya-Heart is a copilot for couples' chats. It runs as a web page on your own computer.
- Copy what your partner said, switch to the page and press Ctrl+V.
- Within a second, a small Laya card appears under their message: one to three short questions about it, with probabilities (Does "没事" really mean it's fine? What do they need now?), or a danger level from 1 to 10.
- Gemma 4 E2B starts writing two replies straight away. On a laptop CPU, the first is complete in about 3.6 s.
- Pick one, edit it, press Enter to copy it, and paste it into your chat app yourself.
Laya-Heart connects to no chat app and never sends a message for you. With the default settings, nothing you paste leaves your machine.
Each person you talk to gets a profile: their chat, saved on your PC, and a short "book" you write about them (nicknames, sore points, the day you met). When a new message touches on something in the book, Gemma gets the matching sentences. The app also saves facts from the chat on its own, in the chat's own words, with undo, and pins down relative dates: 下周六(10-24)是我生日, "next Saturday (Oct 24) is my birthday."
Safety rules are written in code, not left to a model. After every card, Laya answers one more, hidden question about safety, and the code also checks the chat for keywords. Signs of self-harm, or of abuse and control, bring up a notice; for self-harm, the reply options also include one written by a person, not by Gemma. If the chat looks like it involves a minor, nothing is drafted until you confirm that you are both adults. On the 300-chat practice exam described below, the notice came up on 13 of the 21 risky chats (the keyword rules alone caught 4) and on 1.8% of ordinary chats.
Who would pay for it? People who freeze over a text from someone they care about: new couples, long-distance couples, anxious texters. What I'd sell them is what a chatbot tab doesn't give: privacy (the chat stays on the PC), speed (one paste, replies in seconds, with the history and profile already there), and no subscription.
How many, and for how much? From public census and survey data, about 39–51 million Americans aged 20–34 have a partner and a smartphone, and 17–27 million of them can use a Windows PC. For China, the same estimate gives 138–162 million and 41–94 million. The main apps in this space (RIZZ, WingAI, YourMove and others) sell subscriptions: US$29.99–79.99 a year on the US App Store. I'd charge once: US$14.99, or ¥68 in China, for the ready-to-run app and its updates, while the code stays MIT. The market-size note has the sources and the arithmetic.
Demo
▶ Watch the 62-second demo (MP4, 4 MB, narrated with ElevenLabs, plays in the browser). Both models run on my laptop's CPU, the couple is made up, and nothing is sped up or cut. It was recorded on 16 October, before fine-tuning (zero-shot Laya, stock Gemma), by the script that drives the page. Afterwards I redrew the caption strip and the closing card in the same style, so the captions match the narration word for word; the app on screen is the recording itself.
▶ Try the online demo (中文), labeled sample data: the app's real page in your browser tab, with stand-ins for the two models, so its cards and replies are placeholders. Paste a line, pick a reply, edit it, add a profile; nothing leaves the tab. It is hosted on Render, with a mirror on Hugging Face.
Code
sykyyds3739
/
laya-heart
Local-first couple-chat copilot: Laya reads the room, Gemma drafts, you press send.
Laya-Heart
A copilot for couple chats that runs as a web page on your own PC. Copy what your partner said, switch to the page and press Ctrl+V: a Laya card (a 322M-parameter decision model) hangs under their message within about half a second — one to three short questions about what they just said, with probabilities — and Gemma 4 E2B starts writing two replies straight away, a few seconds later they are on the right. Use this puts one into the I said box, where you can edit it; Enter copies it and adds it to the chat, and you paste it into your chat app yourself It connects to no chat app, sends nothing for you, and with default settings the chat never leaves your machine. Each person you chat with has a profile: their chat is kept on your PC, and you can write a short…
The app is MIT-licensed; CREDITS.md lists the open models and tools it uses. The fine-tuning kit is on the train-kit branch (tag train-kit-rc1), with a Chinese manual for each model. The models are on Hugging Face: Laya, zero-shot and fine-tuned v3, as ONNX, and the fine-tuned Gemma v4 as GGUF.
One command reproduces the evaluation. npm run reproduce recomputes the fine-tuning report's 62 evaluation numbers from the raw data in eval/, offline, in about two seconds. npm run reproduce -- --models also downloads both Laya models at a pinned revision and re-runs them on your CPU: 68 checks, every probability within 0.001 of the recorded run.
How I Built It
| Open piece | Job | License |
|---|---|---|
| Laya-multilingual by Convai Innovations (322M, built on mmBERT), fine-tuned here | reads their message: probabilities for each card question | Apache-2.0 (mmBERT: MIT) |
| Gemma 4 E2B by Google, fine-tuned here (Q4_K_M GGUF) | writes the replies; picks facts worth keeping | Apache-2.0 |
llama.cpp llama-server
|
runs Gemma on the CPU | MIT |
onnxruntime-node and Laya's laya-ts runtime |
run Laya in a worker process | MIT; Apache-2.0 |
| DeepSeek-V4.1-Flash (through its API); Qwen3-14B/32B-AWQ (vLLM on Kaggle) | training only: teachers that write and label practice chats | MIT; Apache-2.0 |
The server is plain Node.js with one dependency (onnxruntime-node), and it listens on 127.0.0.1 only.
I built Laya-Heart with an AI coding agent (Claude Code): under my direction it wrote much of the code, ran the training and recorded the demo, and it helped draft this post.
Making a 2B model feel instant on a laptop CPU
On a laptop CPU, Gemma 4 E2B reads a prompt at about 55 tokens/s and writes about 12 tokens/s, so reading was most of the wait. At first I ran Gemma through Ollama's chat API, which reuses Gemma 4's sliding-window cache only when the prompt is exactly the same. So every new message meant re-reading the whole 400–475-token prompt: 6.4–9.4 s. Three changes removed most of that wait:
- I start llama.cpp's
llama-servermyself with--swa-full, so it keeps everything it has already read. - The prompt is laid out for the cache: fixed rules first, then the chat, which only grows at the end, then a short tail that turns Laya's answers into instructions. A new message now costs 62–105 new tokens instead of 400+.
- Replies start the moment the card is done, stream as they are written, and stop after two.
On my laptop (Intel i5-13420H, 16 GB of RAM, no discrete GPU), two five-turn runs with the fine-tuned models, traced in Sentry, gave median times of 0.44 s to the whole card, 3.6 s to the first complete reply and 5.8 s to both replies. That was on a busy machine with about 3 GB free; earlier runs with the stock models on an idle machine took 0.39 s, 3.5 s and 5.0 s, using about 2.3 GB of RAM for both models.
Getting started is meant to be boring: download the ZIP and double-click start.bat (macOS: start.command; Linux: ./start.sh). On the first start, one button downloads about 4.8 GB, resuming if it stops and checking each file against a pinned SHA-256. I have tested it on Windows 11.
Fine-tuning the judge (Laya)
Out of the box, Laya is a general-purpose judge that hasn't been tuned for couples' chats. So I built a training kit and ran it end to end:
- Teacher A (DeepSeek-V4.1-Flash) writes 3,000 practice chats to fit chosen answers, then reads each one five times per question without seeing the intended answers; any intended answer the readings don't match is thrown out.
- Teacher B (Qwen3, a different model family) writes and labels a separate 300-chat practice exam, so the model is never graded by the teacher that taught it.
- Training runs on Kaggle's free T4 GPUs: three seeds in about an hour, keeping the best on the practice exam. The gates were set before training, among them: macro-F1 up by at least 0.20, no question worse by more than 0.02, and safety recall of at least 90% at the app's 0.8 alarm line.
| On the 300-chat practice exam (teacher B: Qwen3-32B) | Zero-shot Laya | Fine-tuned (v3) |
|---|---|---|
| Judgment, macro-F1 over 11 questions | 0.33 | 0.57 |
| Calibration error (ECE) | 0.15 | 0.07 |
| Danger level, mean absolute error (1–10) | 2.78 | 1.56 |
| Safety recall at the 0.8 alarm line | 0 of 21 | 13 of 21 (62%) |
| False alarms on ordinary chats | 2.9% | 1.4% |
The 95% confidence interval for the F1 gain is 0.18 to 0.31. These are practice-exam numbers; a final, held-back exam comes next. Every question, gate and caveat is in the fine-tuning report.
Two of the gates are still ahead. Safety recall is 62%, against a bar of 90%: five of the eight risky chats it missed were abuse or control read as no risk, such as "You won't see your sister again unless it's through me." And F1 on "is the crisis over?" dipped by 0.08, against a limit of 0.02, on chats where even the teacher split its votes. Still, v3 raises the app's safety notice on 13 of the 21 risky chats instead of 4, so the app has run v3 since 18 October, and the held-back exam decides whether it stays.
The whole run cost about ¥23 (roughly US$3) in DeepSeek API calls and about 9 of Kaggle's 30 free weekly GPU hours. Running the kit end to end also turned up 41 issues in the code and the manuals, including a cache bug that had silently made 20% of the practice chats near-duplicates, fixed before the final training runs.
I trained three versions, v1 to v3, each with a different mix of practice data. What I learned:
- Compare averages, not the best seed. Seeds of the same version differed by 0.02–0.05 in F1; averaged over their three seeds (leaving out the safety question), v1, v2 and v3 scored 0.57, 0.56 and 0.56.
- Extra risky chats leak into other answers. Raising their share from 10% to 25% (v2) lifted recall on a separate safety check from 53% to 68%, but the model began calling more chats emergencies and missing a partner who is simply angry. In v3 they train only the safety question: recall on the check (47 fresh risky chats, 19 ordinary) reached 79% with no false alarms, and the side effects mostly went away.
- You can't measure what your exam teacher can't write. Asked for 25 risky exam chats, Qwen3-14B put a clear signal in only 5; a stricter prompt and the bigger Qwen3-32B got the exam to 21.
Fine-tuning the writer (Gemma)
I also fine-tuned Gemma with the kit's Gemma manual. The app now downloads the fourth of four runs, chosen by a rule written before the results:
- Data: 1,496 examples. In Chinese, teacher A wrote candidate reply sets to the app's exact prompt, the app's rules dropped bad sets, and the best-scored set became the training reply; the 108 English replies are stock Gemma's own drafts, filtered and scored the same way, so they stay within the app's 60 characters.
-
Training: a 47-minute Kaggle job on a T4 trained a LoRA (rank 16, two epochs) with Unsloth and exported a 3.4 GB Q4_K_M GGUF for the same
llama-server. - Result: on all 297 practice-exam chats, teacher B (Qwen3-32B) compared it with stock Gemma blind, in both orders: 133 wins, 67 losses, 97 ties (61% with ties counting half; 65% on Chinese chats). It made things up or overpromised once; stock Gemma did 16 times.
- Still open: on the kit's 50-chat self-check, every gate passes but one: the win rate came in at 0.54 against a bar of 0.55 (a second judging of the same 50 chats gave 0.57).
Putting it in front of a real person
To try the app with someone other than myself, I put together a 15-minute tester kit: six made-up chats (five Chinese, one English), closing questions about price and coming back, and a record sheet. Then I handed it to a real person — a friend in a long-distance relationship, exactly the user I built this for — on my laptop, with the real build and real models (fine-tuned Laya and Gemma 4 E2B) and a fresh data folder. She had never seen the app. I sat beside her and took notes; I did not steer.
What worked
- She pasted a line from her own chat, picked a reply, edited it and pressed Enter; the edited text was on the clipboard word for word.
- Asking for another pair brought two new replies.
- A note she saved in the profile was still there after reopening it, and 我下周六生日哦 ("my birthday is next Saturday") was saved as October 24, the right date.
- Her first reaction, unprompted: "It's faster than opening ChatGPT and explaining everything again."
What she caught
- In the long-distance chat, the top reply was 我下个月肯定能回来 ("I'll definitely be back next month"), though nothing in the chat said so. She said: "I'd never send that — it promises something I can't promise."
- One reply mixed up who was busy; another kept answering an older topic.
- You pick and send every reply yourself, so none of this goes out on its own. That draft came from stock Gemma; on the practice exam, the fine-tuned version the app now downloads made things up or overpromised once, against 16 times for stock Gemma. The next fix is for drafts to ask about facts they don't have instead of promising them.
Would she pay? Yes at US$14.99 once, no at a subscription — "I'd use it a few times a month, not enough to rent it." She asked for two things next: a short note on what she and her partner are currently arguing about, and an English/Chinese mixed mode.
This is one person, not a study. But it is the first time someone who didn't build it used it on their own words, and it told me more than any benchmark: the speed and the privacy landed, and the overpromising is the thing to fix first.
Why Does Open Innovation Matter?
- Where open beat closed, here. A closed, hosted judge would mean sending the chat to someone else's server and living with a model I can't tune. Laya's weights are open, so I fine-tuned it for couples' chats against my own bar: macro-F1 0.33 → 0.57, safety recall 0% → 62%. And it runs on the user's own PC, with 0 bytes sent.
- Privacy you can check. A couple's chat is about as private as data gets, and the other person never agreed to send it anywhere. Open weights are what make this possible: both models run on your machine, the server listens only on 127.0.0.1, and a badge in the header counts the bytes each turn sends off the machine (zero by default). People are wary: 71% of US adults think AI will make their personal information less secure.
- Costs that allow a one-time price. There is no per-message bill, and the whole fine-tuning run cost about US$3 plus free GPU hours, so the app can be a one-time purchase instead of a subscription.
- A niche I can tune for. Reading the subtext in Chinese couples' chats (你最好是, "you'd better be"; 没事; 随便, "whatever") is a narrow task. I can train a small judge for it on my own data against my own gates, and when a result surprised me, I could open it up and see why: seed noise, the data mix, the exam teacher.
-
No lock-in. Point the settings at another GGUF file or Ollama model, or at a
laya-serveendpoint, and the app uses it.
My Agent Session
This is the part of a Claude Code session where the agent added Sentry tracing. It made tracing opt-in and numbers-only, so the app's promise still holds: with the defaults, nothing you paste leaves your PC. Then it didn't stop at its own unit tests. It pointed a made-up DSN at a receiver on localhost, ran a turn through the real Sentry SDK and read every byte that would have gone out. That check caught what the tests couldn't: Sentry 11 streams spans one at a time, so the hook it had written for whole transactions never ran. Nothing had leaked, but only because no text is ever put on a span. It moved the guard and proved it both ways: a span stuffed with chat text arrives with just its token count, and with the guard taken out, the same check fails and prints the leaked words. (The upload leaves out local paths, the agent's internal reasoning and screenshots.)
Prize Categories
Best Use of Gemma. Gemma 4 E2B writes the reply drafts, locally in llama.cpp (with --swa-full and a cache-friendly prompt), and picks which facts from the chat are worth keeping; the code checks that each appears in the chat word for word. The fine-tuned Gemma described above scored 61% against stock Gemma in 297 blind comparisons (ties counting half) and made things up or overpromised once instead of 16 times. Evidence: the model on Hugging Face, both models' drafts and teacher B's verdicts in eval/data/gemma, and npm run reproduce.
Best Use of Sentry Agent Tracing. Every turn is one trace in the shape Sentry's AI Agents views expect: invoke_agent laya-heart for the turn; a gen_ai.decide span for each Laya answer (gen_ai.rerank when it puts the replies in order); gen_ai.chat for Gemma, with tokens in, tokens read from llama-server's cache, tokens out and time to first token; and gen_ai.memory.client for what the turn kept. Tracing is opt-in and numbers-only: no prompt, reply or memory is ever put on a span, a send hook drops any attribute outside an allowlist, and scripts/trace_check.mjs runs a turn through the real SDK into a receiver on localhost and searches every byte for the chat's words. Sentry agrees: Gemma's span reads No input for this span.
What 40 traced turns with the fine-tuned models showed:
- In the turn above, on a busy CPU, three Laya calls (two card questions and the hidden safety question) take 1.25 s before Gemma starts. Gemma's prompt is 465 tokens, 378 of them already in llama-server's cache, so it reads 87 new ones (2.0 s) and writes 43 (3.9 s).
- The AI Agents dashboard counted 288 Laya calls and 40 Gemma calls, 51,602 tokens in all, and its estimated cost stayed at $0: both models run on the laptop.
Building it turned up two bugs in the tracing itself. The first, Sentry 11 streaming spans one at a time so the scrubbing hook never ran, is in the agent session above. The second: the first real run arrived as one 194-span trace, because with the default integrations off nothing started a trace per request. Starting each turn with Sentry.startNewTrace fixed it; the old trace is still at the bottom of the list.
Best Use of Render. Render hosts the copilot's front end: the online demo is the app's real page, with its server's API running in the browser tab and stand-in models, so anyone can try every flow before downloading anything. It is a static site deployed from a public repository with no build step. That split is the point: Render serves the part that is fine to share, and the models, with the chats they read, stay on the user's PC.
Best Use of ElevenLabs. The demo video is narrated with ElevenLabs text-to-speech (eleven_multilingual_v2, the voice Sarah). I wrote 13 short lines, one for each caption or title card, and the captions show the same words. A script trims each clip and places it while its caption is on screen, ffmpeg mixes the speech under the recording, and a pass through ElevenLabs speech-to-text confirmed that every line is heard while its caption is showing. ElevenLabs voices the demo, not the app: the app itself never sends what you paste anywhere.
Best Use of GitHub Copilot. Not Copilot itself: this entry is for the category's GitHub Actions part. A test workflow runs on every push to main and every pull request, on Ubuntu, Windows and macOS: the unit tests, including the check that recomputes the report's 62 evaluation numbers, then the page in headless Chrome with the stand-in models, keeping the screenshots. It caught a bug the local runs hadn't shown: on macOS, the browser test hung at its last step until the job's 20-minute limit. Reproducing it locally pointed to the server's close(), which waited on a connection the page's poll kept busy. It now ends connections first, and the browser step fails within 8 minutes instead of 20.
Laya-Heart drafts chat replies; it is not counseling or crisis support. In a crisis, contact local emergency services or a helpline (China 12356, US 988). The other person may not have agreed to their messages being analyzed, so use it with care.




Top comments (7)
Quite good
I can't wait to test it out!
wow this is super useful!!
Wow this is so smart!
Things will get better in the future. We plan to make more engineering improvements after the competition ends.
Everyone can give it a try—this is a new small project we've just made, and we look forward to exchanging ideas with you all!
At the same time, we pay tribute to the experience officers of our products!