DEV Community

Cover image for I just saved 25 hours a month…
Sum Lau
Sum Lau

Posted on AI-assisted

I just saved 25 hours a month…

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

I just saved 25 hours a month — and a $20/mo ChatGPT subscription — by pointing a 3.4 GB open-source model at my laptop instead of a paid API.

If you run a small shop on Etsy or eBay, you already know the real bottleneck isn't the product — it's the listing. Titles that rank, descriptions that sell, and the right 10–13 search tags. Do that for 100 listings a month and it eats 25 hours. That's a full-time job's worth of copywriting hiding inside a side hustle.

What I Built

Listing Copilot takes a one-line product keyword — handmade ceramic mug, minimalist, gift — and returns a ready-to-paste Etsy/eBay listing: a keyword-loaded title under 140 chars, a benefit-driven 2–4 paragraph description, and 10–13 lowercase search tags. All of it is generated on my machine by Google's open-source Gemma 3 (4B) running through Ollama.

There is no API call to OpenAI or Anthropic, no per-token billing, and no inference leaving the machine. A single line in the UI says it: 100% on-device · Gemma 3 · $0 API.

Optional, and all graceful-degrade: competitor pricing via SerpApi, and a save backend via MongoDB Atlas — when the keys aren't there, the app quietly falls back (pricing panel hidden, saves to local JSON), so the core demo never breaks.

Demo

Open the app, type keywords, hit Generate, and the listing types itself out word by word in the terminal-style UI:

Listing Copilot UI: a generated Etsy listing for a ceramic mug, with the fixed badge reading 100% on-device, Gemma 3, $0 API in the bottom right corner

The model is a 3.4 GB pull. After warm-up a generation takes about 6–7 seconds; a cold start is 15–20 seconds. Here is /health on my own machine. model_ready: true for gemma3:4b, and the last two fields are the ones I actually care about as evidence: "storage": "mongodb" and "serpapi_enabled": true. Those two flags flip to local-json and false the moment the keys are missing, so they cannot be faked by editing a README:

Terminal output of curl health on localhost port 8000, pretty-printed JSON showing model_ready true, storage mongodb and serpapi_enabled true

Code

Full stack is deliberately boring and open: Python 3.13 + FastAPI for the backend, Ollama's /api/chat endpoint with format:"json" and temperature 0.7 for generation, and a single no-build HTML file on the front end — no framework, no node_modules, no deploy pipeline. You clone it, pip install -r requirements.txt, ollama pull gemma3:4b, and run uvicorn app.main:app.

Generation is a single structured call:

payload = {
    "model": "gemma3:4b",
    "messages": [system, user],   # system forces valid JSON output
    "stream": False,
    "format": "json",
    "options": {"temperature": 0.7, "num_predict": 700},
}
Enter fullscreen mode Exit fullscreen mode

The prompt instructs the model to return only {"title", "description", "tags"} with hard rules (title ≤ 140 chars with the most-searched keywords; tags 10–13, each ≤ 20 chars, lowercase, no #). A tolerant parser strips markdown fences and recovers the JSON even if the model adds chatter.

One rule looks pedantic until you've sold on Etsy: no # in tags. Etsy tags are plain search keywords capped at 20 characters each, and a # spends one of those characters without adding anything Etsy will match on. eBay doesn't even have a tags field (its search runs on the title and item specifics), so a # is pure dead weight in a listing's most valuable real estate.

The prompt hard-codes no '#', and a normalization pass strips any stray # the model might still emit. Tags render as #chips in the UI — that's how people read them — but the data underneath is exactly what a seller pastes into Etsy.

Repo (MIT-licensed): SS-LAU/sidehustle-listing-copilot

How I Built It

The core loop is under 100 lines of Python. I wrapped Ollama in a client that turns every failure mode — service down, model not pulled, timeout — into a friendly OllamaError instead of a 500, and the front end catches that and shows a hint plus a Demo button that loads a bundled offline sample so the UI never white-screens. That last detail matters more than it sounds: a side-hustle tool has to work on the seller's flaky wifi, not just on a dev machine.

On the open-source side, I made swapping the model a one-line env var (OLLAMA_MODEL). Today that's gemma3:4b; tomorrow it's gemma3:1b on a weaker laptop or a bigger model on a GPU box. The prompt, tone presets, and tag rules are plain text you can edit for your own brand — that's the whole point.

Why Does Open Innovation Matter?

Because for a side hustle, "open" isn't a value statement — it's the business model.

  1. Zero marginal cost → near-100% gross margin. A hosted API bill scales with every listing you generate; a flat $20/mo subscription just caps how much you can do by hand. Gemma running locally makes the 10,000th listing cost exactly $0 more than the first. My cost curve is flat; a proprietary API's is not.
  2. No prompt ever reaches a hosted LLM. A seller's product keywords and copy are their trade secrets. Local inference means no "we may use your prompts for training" fine print, which lowers the trust barrier with the exact audience — small, privacy-wary sellers. The two optional integrations are the only outbound calls, and both stay off until you add keys.
  3. Self-host + edit the prompt = a moat. Because it's open, a user can re-tune the tone, inject their own brand voice, and point the whole thing at any model. You can't lock someone into a moat built on a closed API; you can build a moat on tooling around open models that a competitor has to rebuild, not just resubscribe to.

On the numbers: 100 listings × 15 min each = 25 hours a month back. At a $2/mo self-hosted price point, 1,000 sellers is roughly $2,000 MRR on a cost structure where the AI itself is free to run. That flat cost curve is the whole thesis — and it's only possible because the model is open.

My Agent Session

I built this with an AI coding agent driving the loop: it scaffolded FastAPI, wrote the Ollama client and JSON parser, and — crucially — wrote the offline fallback so the demo survives a model outage. The agent also ran the app and caught that a cold start (~15s) looked like a hang to a first-time user, which is why the warm-up + offline-Demo path exists.

Prize Categories

  • Best Use of Gemma — the core engine. Every listing in this project is generated by gemma3:4b on-device.
  • Best Use of SerpApi — the competitor-pricing layer (auto-hides when no key is set).
  • Best Use of Render — the app deploys from a checked-in render.yaml Blueprint and is live at sidehustle-listing-copilot.onrender.com (free tier, so the first request may take up to a minute to wake). Render serves the FastAPI front end, a /health endpoint that always returns 200 so the health check never flaps, and the bundled offline sample; a scheduled GitHub Action is configured to ping /health every 5 minutes to keep the instance warm. There's no GPU or Ollama in the cloud, so hosted Generate deliberately fails fast and honestly rather than pretending.
  • Best Use of MongoDB Atlas — save backend via MONGODB_URI (falls back to local demo/listings.json when unset).

Claims are cheap, so before submitting I run a six-assertion integration check against the live server — scripts/verify_integrations.py. It fails loudly if SerpApi stops returning real prices or if saves stop landing in Atlas, which is why I trust the two partner categories above:

Terminal output of scripts/verify_integrations.py showing six PASS lines, with real SerpApi competitor prices and listings written to MongoDB Atlas

Top comments (1)

Collapse
 
sum_lau_b6f2b27a5f2b30806 profile image
Sum Lau •

Etsy and eBay sellers: how long does one listing take you today? I'm curious whether 15 minutes is realistic for you.