Reproduced on the 68-min meeting: the timestamped transcript filled
the whole 32k window (prompt_eval 32451), gemma4 hit the length limit
and returned an EMPTY answer (done_reason=length) — which the server
wrote as an empty summary.md and marked "done". Fixes:
- num_ctx 32768 -> 65536 and transcript cap 90k -> 100k chars (env
OLLAMA_NUM_CTX / TRANSCRIPT_MAX_CHARS); ~33k tokens now fit with
plenty of room for the answer
- _ollama_chat treats an empty response as an ERROR (with done_reason
and prompt_eval in the message) instead of producing a done-with-empty
summary
- truncated transcripts get a note appended to the summary
- startup recovery: a container restart resets stale "pending" statuses
so the UI can never stick on "wird erstellt" from dead threads
- phone: the summary/agenda poll survives transient fetch errors
(previously one network hiccup stopped the poll forever)
- whisper-server stack: second container (port 8086) running the
English-trained small.en-tdrz model with -tdrz; image patched
(speaker-turn.patch) to expose speaker_turn_next per segment in
verbose_json like the cli example does
- core/whisper Diarization: merges the tdrz pass's TURN TIMES onto the
quality transcript as alternating 'Sprecher 1/2:' labels, splitting
segments when a turn falls inside them; no turns detected = no
labels (never mislabels); 6 unit tests
- RemoteWhisperEngine gains a diarize flag (sends tinydiarize=true,
parses speaker_turn_next); WhisperEngine.Segment carries the flag
- RecorderService: optional second pass on the diarize server after the
final pass; failures keep the unlabeled transcript
- Settings: Diarize server URL (persisted; empty disables)
- validated infrastructure locally: patched image builds, tdrz model
downloads from akashmjn/tinydiarize-whisper.cpp, speaker_turn_next
present in responses; synthetic espeak audio does not trigger the
model's turn tokens — real two-person speech needed for the
end-to-end check
- meetrec-server: Ollama integration (chat API, gemma4:12b, num_ctx
32768); German structured summary (topic/points/decisions/to-dos)
written to summary.md; agenda coverage returns strict JSON (covered,
time, evidence) parsed defensively; both run automatically in a
background thread after upload plus manual trigger endpoints
(POST /summary, POST /agenda) with status tracking in the index
- phone: agenda input on the Record tab (one item per line, persisted)
is uploaded with the recording; Library detail shows the summary and
a per-item agenda checklist with timestamps and evidence quotes,
with polling while the server generates and manual re-trigger buttons
- validated end-to-end against the live Ollama server: crafted German
test meeting produced a correct structured summary and perfect agenda
discrimination (covered items with correct timestamps + quotes,
undiscussed item correctly false)
- server/meetrec-server: FastAPI storage API (upload bundle with wav/
txt/srt/json + metadata incl. agenda, list, fetch, download, delete);
file-based index.json, no database; Docker Compose on port 8090,
Tailscale-only bind like whisper-server; Ollama env prepared for M3c
(gemma4:12b, German)
- phone: StorageClient (stdlib multipart upload); RecorderService uploads
the bundle in the background after the final pass and publishes
UploadState (Uploading/Done/Error) to the UI
- app: Library URL setting (persisted, default http://100.103.83.12:8090,
empty disables upload); status line reports upload progress
- storage API validated locally end-to-end: upload, list, metadata,
download, path-traversal rejected, delete
The first server deployment downloaded only 1101 bytes (a redirect/error
page) and the entrypoint promoted it to ggml-large-v3.bin, after which
every restart skipped the download and the server ran without a valid
model. Now both fresh downloads and existing files are validated
(minimum 50 MB + the ggml magic bytes); invalid files are logged,
deleted, and re-downloaded, and a failed download dumps the first 400
bytes of what was actually received for diagnosis before exiting.
Validated locally: a poisoned model file is detected, removed, and a
valid one re-downloaded; inference served correctly afterwards.
- server/whisper-server: compose stack built from the pinned whisper.cpp
v1.9.3 release, same as the Android JNI layer
- Vulkan GPU backend (AMD Radeon AI PRO R9700 / RADV) with transparent
CPU fallback and NO_GPU override; GGML models auto-download on first
start (MODEL env, default large-v3)
- API bound to the Tailscale interface only (100.103.83.12:8080) since
whisper-server has no authentication; render-group GID passthrough for
/dev/dri
- validated locally: image builds, entrypoint downloads tiny, POST
/inference returns verbose_json with language + segments; GPU-less
fallback confirmed