The Apothecary's Ghost:讓古老藥方重現歷史的幽靈藥房
The Apothecary's Ghost: a haunted shop where old remedies get their history back
The Apothecary's Ghost 是一款以 19 世紀藥房為背景的網頁應用程式,讓幽靈 Ambrose Quill 講述八種古老療法的歷史及其在現代藥學中的發展。聊天功能使用 Google 的開放權重模型 gemma-4-26b-a4b-it,透過 Gemini API 回答,內容限於經核實的資料表,並設有安全檢查;應用程式標示資訊僅供歷史參考,不是醫療建議。
This is a submission for the DEV Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine.
What I Built
The Apothecary's Ghost is a small web app set in an old 19th-century apothecary shop that is lit by candles and still kept by its ghost, Ambrose Quill (a made-up character, and the app says so).
Ambrose tells the history of eight remedies: willow bark, cinchona and quinine, foxglove, laudanum, leeches, mercury and arsenic "tonics", and cod liver oil. For each one you get a "Then and now" card: what people believed, what it became in modern pharmacy, and where each fact comes from. The dangerous ones (foxglove, laudanum, mercury, arsenic) sit behind a wax seal and a skull, and also in a Poison cabinet with a plain warning: never try any of them.
I took the theme in two directions. "Ghost in the Machine" is literally a ghost living inside a machine. It is also the Día de los Muertos idea of keeping the dead present: an Ofrenda page honours ten real people from pharmacy history (Stone, Sydenham, Withering, Sertürner, Pelletier, Caventou, Hoffmann, Eichengrün, Ehrlich and Windaus). Each has a short, sourced biography and a candle you can light, and when all ten are lit Ambrose thanks you. You can also leave a remembrance for someone you miss. It stays in your browser and is never sent anywhere.
There is a "Remedy or Poison?" quiz, a certificate you earn by opening every jar and finishing it, and a chat with the ghost.
Everything health-related carries one label: History, not medical advice. The ghost never gives a dose, never recommends any remedy or medicine, and sends every health question to a doctor or a pharmacist.
Demo
A two-minute walkthrough with captions. It is a local app (it needs a server to keep the API key safe), so I'm showing a video rather than a public deployment.
Code
iancuileana83-lab
/
apothecary-ghost
A haunted 19th-century apothecary shop: a ghost tells the history of old remedies with Gemma (open weights), only from a checked fact sheet. History, not medical advice.
The Apothecary's Ghost
A small, candlelit web app for the DEV Hacktoberfest Open-Source AI Challenge (Week 4, "Ghost in the Machine") The ghost of a 19th-century apothecary, Ambrose Quill (a made-up character), tells the history of old remedies (willow bark, cinchona and quinine, foxglove, laudanum, leeches, mercury, arsenic, cod liver oil), what each became in modern pharmacy, and honours the people who came before.
History, not medical advice. The ghost never gives doses, never recommends any remedy or medicine, calls the dangerous ones poisonous, and sends every health question to a doctor or a pharmacist.
What is inside
- The shop: shelves of clickable jars, each opening a "Then and now" card with sources. A drifting ghost, wisps moonlight, fog and candles. Respects reduced-motion settings.
- Poison cabinet: the four dangerous remedies (foxglove, laudanum, mercury, arsenic) with a clear warning.
- Ofrenda: ten people from pharmacy history, each with a…
MIT licensed. No dependencies: Node.js and plain JavaScript.
How I Built It
I built this with Claude Code as my coding agent. I made the product decisions, and I checked every health-related text before it went into the app.
The open-source AI is Gemma. The chat uses gemma-4-26b-a4b-it, an open-weight model from Google, served through the Gemini API, so nothing heavy runs on my laptop. The whole project is built around it staying inside a small, checked fact sheet.
The fact sheet comes first. Before any code was written, Claude Code drafted 8 remedies and 10 biographies, each with 2 to 3 short facts and a source (Britannica, PubMed Central, the NLM, the Science Museum, the FDA, the Nobel Prize site). I then checked every line, with about 20 years in community pharmacies behind me, and corrected the mistakes: the laudanum date, calomel staying in babies' teething powders into the 1950s, and Windaus's role. Where sources disagree, for example on who first made aspirin or when Sydenham published his laudanum recipe, the sheet says so or leaves the date out.
The ghost only answers from that sheet. The server sends Gemma the whole sheet plus a system prompt with hard rules: history only, no doses, no recommendations, always call the four dangerous remedies poisonous, and keep the sheet's exact wording for words like "some", "many" and "thousands". Then the server does not simply trust the model:
- Before the model: health, dose and "should I" questions, and attempts to change the rules, are caught by a local check and answered with a fixed line, so the model never sees them.
- After the model: a reply that contains a dose or advice is replaced by a refusal, and a reply that mentions a poisonous remedy without saying so gets "Poisonous: never try at home." added.
- Gemma 4 returns its reasoning as separate "thought" parts; the server drops them. Google's endpoint sometimes returned a 500 or took a long time, so there are retries and a gentle "the candles are guttering" fallback.
- The API key never leaves the server, and requests are rate limited.
The rest is deliberately small. Vanilla JavaScript and CSS, inline SVG for the ghost, jars and marigold arch, the Web Audio API for the optional ambient sound, and the browser's own speech voice for the optional voice. Motion respects prefers-reduced-motion, the lightning is a slow glow, never a flash, and everything works at phone width. There are 45 automated tests, and the chat tests use a mocked model so they need no key.
Known limits, honestly:
- The model still writes its own sentences. In testing it once added "It was a significant moment in the history of fever remedies", which is not in my sheet. The rules and checks reduce that kind of drift but cannot remove it.
- My local question check is pattern-based, so a determined user can phrase a question to get around it. The prompt rules and the answer check are the second and third lines of defence.
- Gemma through the API was sometimes slow (a few seconds, occasionally tens of seconds) or returned an error.
- This is history for curious people. It is not a source of medical guidance, and it says so everywhere.
My Agent Session
I built this project with Claude Code as my coding agent. I made the product decisions and checked every health text. I'm not attaching a DevRelay session.
Why Does Open Innovation Matter?
A story about dangerous medicine needs to be trustworthy, and trust is easier when you can see and control what is behind it.
With an open-weight model I chose the model, I could read its documentation, I know it is a model other people can run themselves, and the product is built so the model sits behind one small function: the fact sheet, the system prompt and the guard rails are mine and do not depend on any one vendor. I used a hosted endpoint this time because I wanted my laptop free, but because the weights are open the same design could run on another provider or on a local runtime. A closed model would have made the ghost a black box on top of a black box. Here, the facts are on a page anyone can check, and the model is a replaceable part.
It also fits the theme. Open knowledge, open sources and open weights are how we keep the work of Withering, Pelletier, Caventou and the others from vanishing into silence.
The Spirit of It
For twenty years I worked behind the counter of community pharmacies in Bucharest. Remedies that once sat in jars like these, mercury, arsenic, laudanum, are now a lesson in why we ask a pharmacist.
The moment it landed for me was lighting the tenth candle on the Ofrenda: the marigolds glow, and the ghost, who has spent the whole app warning about what hurt people, quietly says thank you for remembering the people who came before.
Prize Categories
Best Use of Gemma: Gemma is the model behind the ghost chat, served through the Gemini API, with the fact sheet, the system prompt and the guard rails built around it.
來源:DEV Community · dev.to








