DEV Community

Mochamad Faishal Amir
Mochamad Faishal Amir

Posted on

Tangkwa Journal: A Soft AI Diary I Built for My Friend in ENT Training

Hacktoberfest Weekend Challenge: Build for a Friend Submission 🤝

This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend

Today screen

Log meal / drink

Gemma reply

What I Built

I built Tangkwa Journal for my friend Tangkwa.

She’s in a demanding ENT specialist training program. Her days are long, her schedule is messy, and when she’s rushed or stressed she often reaches for something sweet — a cup of sweet matcha, a quick comfort drink — without really tracking how often it happens. She doesn’t need another calorie spreadsheet. She needs something that feels like a friend.

So I made a cute, mobile-first PWA where she can:

  1. Write a short diary entry (optional mood chips: tired, okay, stressed, happy, overwhelmed)
  2. Log a meal or drink in plain language — “a cup of sweet matcha”, “a slice of pizza”
  3. Get a warm reply from an open-weight companion that acts as a psychologist-style friend and a gentle diet mentor

The brain is gemma2:2b via Ollama. The phone is a thin PWA. Inference stays on the server side on purpose — her notes stay on an open-weight stack I control, not a closed chatbot SaaS in the middle.

Accounts are per-person (username + password, httpOnly session cookie) so each journal stays private. Important: the app is explicit that this is a supportive friend, not a clinician. There’s an in-app disclaimer. Crisis-looking language takes a fixed human-help path and skips model generation.

Demo

Live demo: https://tangkwa-journal.byfaishal.cv/

Judges can Create an account on the live site, or sign in with the seed account:

  • username: tangkwa
  • password: tangkwa-journal

Local / judge repro:

git clone https://github.com/mochfamir/tangkwa-journal
cd tangkwa-journal
pnpm install
ollama pull gemma2:2b
cp .env.example .env
pnpm dev:api   # http://127.0.0.1:8787
pnpm dev:web   # http://127.0.0.1:5173
Enter fullscreen mode Exit fullscreen mode

Health check (must see the open-weight model):

curl -s http://127.0.0.1:8787/api/health
# {"ok":true,"model":"gemma2:2b","ollama":"up","modelPresent":true}
Enter fullscreen mode Exit fullscreen mode

Companion smoke test (register → cookie → create meal entry):

curl -s -c /tmp/tj.jar -X POST http://127.0.0.1:8787/api/auth/register \
  -H 'content-type: application/json' \
  -d '{"username":"judge-demo","password":"judge-demo-pass","display_name":"Judge"}'

curl -s -b /tmp/tj.jar -X POST http://127.0.0.1:8787/api/entries \
  -H 'content-type: application/json' \
  -d '{"type":"meal","body":"a cup of sweet matcha","sweetness":"sweet"}'
Enter fullscreen mode Exit fullscreen mode

Or skip register and log in as the seed user (tangkwa / tangkwa-journal) against /api/auth/login with the same cookie jar.

Code

https://github.com/mochfamir/tangkwa-journal

Built during the challenge window (commits from 2026-10-03, after the Oct 2 UTC start).

How I Built It

I shipped in deliberate layers the same Saturday afternoon (WIB), because the challenge is short and Tangkwa’s problem is real.

1. Brain first — open-weight companion loop

Before pastels, I wanted proof that Gemma could talk back.

  • Small Hono API (apps/api) on port 8787
  • SQLite (better-sqlite3) for diary + meal entries, scoped per user
  • Versioned system prompt in prompts/companion.md — friend first, coach second, ~100-word cap, no shame language, no fake calorie precision
  • Ollama POST /api/chat with gemma2:2b and a tight num_predict
  • Only the last 3 one-line summaries as context (keeps a 2B model coherent; avoids dumping her whole journal into the prompt)
  • Fail closed: if Ollama is down → HTTP 503, nothing half-written is persisted

Warm replies on my machine landed in about 1–4 seconds. That was the moment it stopped being a scaffold and became something that could answer a tired ENT trainee who just logged a sweet matcha.

Sample meal reply from build-time testing:

Matcha's a great pick, Tangkwa! Sometimes a little sweetness helps bridge the day's fatigue. How's that energy after rounds?

2. Then the shell she’d actually open

UI only — no API contract changes, so the open-weight path stayed honest.

  • Vite + React + TypeScript PWA
  • Soft pastel system (blush / cream / mint) with Fraunces + Nunito
  • Screens: login/register, Today (“Hi Tangkwa”), Write diary, Log drink/meal, History, Detail with AI reply, About/disclaimer
  • “Gemma is thinking…” loading state
  • PWA shell via vite-plugin-pwa — offline shell is fine; /api/* stays network-only so a dead model never becomes a fake journal reply in the UI

Architecture in one line:

Phone PWA  →  Hono API (+ session auth)  →  Ollama (gemma2:2b)
                 ↓
              SQLite journal (per user)
Enter fullscreen mode Exit fullscreen mode

3. Why 2b, not a bigger Gemma?

I already had a larger Gemma locally. I still chose gemma2:2b so a weekend deploy on modest hardware stays realistic, latency stays friendly on a phone wait screen, and the “live for a friend” path doesn’t depend on a GPU fairy tale. Quality is managed with a strict prompt and short outputs — not by hoping a bigger model forgives a vague product.

Privacy-minded journal builders keep saying the same thing: personal notes need a hard boundary on where inference runs and what gets remembered. That pushed me toward self-hosted open weights instead of a closed chat API as the product’s brain.

Why Does Open Innovation Matter?

This isn’t a todo list. It’s mood + eating notes from someone I care about, written on exhausted hospital days.

I didn’t want that intimacy routed through a closed commercial chatbot by default.

Open-weight Gemma + open-source Ollama means:

  • The model is a setting, not the product’s landlord — swap weights later without rewriting around a vendor
  • Inference can stay on infrastructure I run (laptop now; VPS/Compose for her phone later)
  • Judges (and Tangkwa) can see the companion voice in a file in the repo, not buried in a black-box prompt dashboard
  • A small open model is enough for short, kind check-ins — which is what she actually has time for

Closed APIs would have been faster to prototype. They would have been the wrong default for this friend’s data.

Prize Categories

  • Best Use of Gemma — generation path is only gemma2:2b via Ollama; no closed-LLM fallback in the companion loop. The PWA is a thin client; Gemma is the product’s brain.

Built for

Tangkwa — and anyone who’s too busy for calorie apps but still deserves a soft place to check in.

Top comments (0)