A voice-memo → illustrated dream-journal pipeline orchestrated by n8n.
You drop a .wav voice memo at a webhook. The stack transcribes it with Whisper (self-hosted, faster-whisper), extracts dream themes, symbols, mood, and a text-to-image prompt as structured JSON via Ollama running Mistral 7B Instruct, and renders surreal cover art via a Stable Diffusion API. The result is a dated markdown entry plus an embedded image, written to ./journal/. A second cron-triggered workflow produces a weekly illustrated digest.
The whole stack runs under Docker + docker-compose.
POST audio ./journal/
│ ▲
▼ │
┌────────┐ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
│ n8n │───▶│ STT │───▶│ Ollama │───▶│ Stable │────────┘
│ webhook│ │ FastAPI │ │ Mistral │ │ Diffusion │
│ + cron │ │ +faster- │ │ 7B JSON │ │ (A1111 / │
│ │ │ whisper │ │ │ │ ComfyUI) │
└────────┘ └──────────┘ └───────────┘ └──────────────┘
:5678 :8001 :11434 :7860 (host)
See docs/architecture.md for node-by-node detail.
Prereqs: Docker, an Ollama running on localhost:11434 with mistral:7b-instruct pulled (or use the bundled profile, see below), and a Stable Diffusion API endpoint (default expects A1111 on host:7860).
git clone https://github.com/samkeller-dev/dream-journal.git
cd dream-journal
cp .env.example .env
# Edit .env if your SD or Ollama lives somewhere non-default.
# Generate a real key: openssl rand -hex 32 → N8N_ENCRYPTION_KEY
docker compose up -d --buildWait until dj-stt is healthy and dj-n8n is up:
curl http://localhost:8001/health # STT
curl http://localhost:5678/healthz # n8n
curl http://localhost:11434/api/tags # Ollama (host)Visit http://localhost:5678, create the owner account, then import each JSON in workflows/ via the UI (+ → Import from File) or the REST API:
# After owner setup, log in via the UI once to seed your cookie, then:
curl -X POST http://localhost:5678/rest/workflows \
-H 'Content-Type: application/json' \
-b cookies.txt \
--data @workflows/01_dream_capture.jsonActivate workflow 01 Dream Capture (toggle in the UI) so its production webhook registers.
curl -X POST -F "audio=@samples/sample_memo.wav" \
http://localhost:5678/webhook/dream-capture
# → {"ok":true,"date":"2026-04-28","entry_path":"/journal/2026-04-28.md","image_path":"/journal/2026-04-28.png"}A markdown entry and PNG land in ./journal/. See samples/expected_entry.md for a reference of what the entry looks like.
By default it fires from the cron-style Schedule Trigger every Sunday at 09:00 local. To run it on demand, open the workflow in the n8n UI and click Execute Workflow — the bundled Manual Trigger node will fire it. The digest writes to ./journal/digests/{week_starting}.md plus a cover image.
If there are no entries in the past 7 days, the workflow short-circuits via the Has Entries? IF node and exits cleanly without calling the LLM.
- Webhook receives multipart
audioupload. - HTTP Request posts the binary to the in-network STT service (
faster-whisper). - Set assembles the system prompt and the user message.
- HTTP Request posts to Ollama with
format: "json"to extract{date, transcript, themes, symbols, mood, summary, image_prompt}. - Code parses + validates the JSON shape.
- HTTP Request posts to Stable Diffusion (
/sdapi/v1/txt2img). - Code decodes the base64 PNG, builds the markdown body, and emits one item with two binary properties.
- Two Read/Write Files from Disk writes — image and entry — both into
/journal/. - Respond to Webhook returns the resulting paths.
- Schedule Trigger (cron
0 9 * * 0) or Manual Trigger. - Code globs
/journal/*.md, parses YAML frontmatter, filters to entries dated within the past 7 days. Computesweek_startingas Monday of this week. - IF short-circuits if zero entries.
- Set assembles the digest prompt.
- HTTP Request → Ollama for digest JSON
{dominant_themes, recurring_symbols, emotional_arc, digest_text, cover_image_prompt}. - Code parses + overrides
week_startingwith the locally computed Monday. - HTTP Request → Stable Diffusion for the cover.
- Code decodes, builds digest markdown.
- Two Read/Write Files from Disk into
/journal/digests/.
See .env.example for the canonical list.
| Var | Default | Purpose |
|---|---|---|
N8N_ENCRYPTION_KEY |
(required) | n8n credential vault key. openssl rand -hex 32. |
OLLAMA_BASE_URL |
http://host.docker.internal:11434 |
Where n8n reaches Ollama. |
OLLAMA_MODEL |
mistral:7b-instruct |
Model name passed to /api/chat. |
WHISPER_MODEL |
base.en |
faster-whisper model size. Try small.en for quality. |
WHISPER_DEVICE |
cpu |
cpu or cuda. |
WHISPER_COMPUTE_TYPE |
int8 |
int8, float16, float32. |
SD_BACKEND |
a1111 |
a1111 (wired) or comfyui (documented). |
SD_BASE_URL |
http://host.docker.internal:7860 |
Where n8n reaches Stable Diffusion. |
SD_STEPS/SD_WIDTH/SD_HEIGHT/SD_SAMPLER |
28 / 768 / 768 / Euler a |
Passed to /sdapi/v1/txt2img. |
The default workflow targets A1111 (POST /sdapi/v1/txt2img, sync, returns base64 inline). Swapping to ComfyUI is documented in docs/architecture.md — the short version: ComfyUI is async (POST /prompt returns a prompt_id, then poll /history/{id}), so the single Generate Image node has to be replaced with a small mini-flow. A minimal txt2img graph template lives at prompts/comfyui_txt2img.json.
By default the stack expects Ollama running on the host (most users have one). To bundle it:
docker compose --profile bundled-ollama up -d
# then in .env:
OLLAMA_BASE_URL=http://ollama:11434The ollama-init sidecar will pull ${OLLAMA_MODEL} on first start.
dream-journal/
├── README.md
├── docker-compose.yml
├── .env.example
├── .gitignore
├── workflows/
│ ├── 01_dream_capture.json # n8n export
│ └── 02_weekly_digest.json
├── stt/ # FastAPI + faster-whisper
│ ├── Dockerfile
│ ├── requirements.txt
│ └── server.py
├── prompts/ # all prompt templates + ComfyUI graph
├── samples/ # sample audio + expected_entry.md
├── docs/
│ ├── architecture.md
│ └── screenshots/
└── journal/ # gitignored output dir
└── digests/
tests/run.sh # full suite (Python + Node)
tests/run.sh py # FastAPI/STT + workflow-schema only
tests/run.sh node # Code-node logic onlyThe runner spins up throwaway python:3.11-slim and node:20-alpine containers — no host pytest or Node needed. The suite covers:
- STT server (
tests/python/test_stt_server.py) — FastAPI surface with a stubbedfaster_whisper. - Workflow JSON schema (
tests/python/test_workflow_schema.py) — every node has a UUID + required fields, every connection target exists, no name collisions, every$env.Xreference is documented in.env.example, and Code nodes are ASCII-safe (guards against the em-dash sandbox bug). - Code-node logic (
tests/node/*.test.mjs) — extractsjsCodestraight out of the workflow JSON and runs it against a faked$input/$()/$envcontext. CoversParse Dream,Collect Entries, andPrepare Outputs.
- No frontend — n8n's canvas is the UI.
- No auth on the webhook. Local-dev only.
- ComfyUI path is documented but not wired into the workflow JSON.
- No retries on transient SD/Ollama failures.
- The Code nodes use
require('fs')to glob/journal/, which requiresNODE_FUNCTION_ALLOW_BUILTIN=fs,path(set by the compose file).
n8n workflow orchestration · Whisper STT (faster-whisper) · Ollama + Mistral 7B Instruct for JSON-schema extraction · Stable Diffusion image generation · Docker / docker-compose · Webhook + cron triggers · FastAPI for the STT microservice