The MAUI client transcribes speech on the device with Whisper.net — see On-device voice. That's perfect for a phone (offline, private), but it means the Swiss-German model is downloaded and run per client, and a browser can't do it at all. This page describes the inversion: run the same fine-tuned Swiss-German Whisper model once, as a container, and expose transcription to every client — the Blazor portal, React Native, and MAUI — from one place the portal configures.

  Blazor chat mic ┐                         POST /api/speech/transcribe
  React Native    ┼── record WAV ──────────▶ (portal endpoint — "expose from everywhere")
  MAUI            ┘                                   │
                                                      ▼  ISpeechTranscriber  (mesh, HTTP IIoPool)
                                          WhisperContainerTranscriber
                                                      │  POST /inference (multipart)
                                    configure ───────▶ whisper.cpp CONTAINER + ggml-swiss-german-turbo-q5_0
                              (SpeechConfiguration: Endpoint, Language, Enabled — set in the portal)

The pieces

Piece Where Status
The containerwhisper.cpp server + the Swiss-German model baked in (see Voice model distribution) deploy/whisper/ (Dockerfile + compose + helm + README) built; not runtime-verified in CI (no Docker in the sandbox)
SpeechConfiguration — endpoint, language, enabled; the portal-settable config src/MeshWeaver.Speech/SpeechConfiguration.cs done
ISpeechTranscriber / WhisperContainerTranscriber — the centralized client; POST /inference on the HTTP IIoPool, cold IObservable src/MeshWeaver.Speech/ done + unit tests against the real /inference contract
Portal endpointPOST /api/speech/transcribe the clients call memex/Memex.Portal.Shared/Api/SpeechEndpoints.cs (MapSpeechApi, wired in MemexConfiguration) done
Mic UI — a record button in ThreadChatView (browser MediaRecorder) → transcript into the composer MeshWeaver.Blazor.Portal/Chat done
React / React Native — record → same endpoint clients/react (ops.transcribe), MeshWeaver.Plugins/app/react-native/src/speech/ done
MAUI memex/Memex.Client/Voice/ still the on-device Whisper.net path — see On-device voice

Why a container (vs. on-device or a cloud STT)

Config — the async + mutation rules

Security

Audio is posted to the portal endpoint under the caller's session; the portal forwards it to the (typically cluster-internal) Whisper container. The container endpoint is not exposed to clients directly — they only ever see /api/speech/transcribe, so the model host stays behind the portal's auth. Specifically, the endpoint:

Status

Shipped: the container definition (deploy/whisper), MeshWeaver.Speech (config + transcriber) with unit tests driving WhisperContainerTranscriber against an in-process server mimicking whisper.cpp's /inference (transcribe, language forward + per-call override, unconfigured error, server-error propagation), the portal POST /api/speech/transcribe endpoint, the ThreadChatView mic button, and the React / React Native record paths.

Not done: MAUI still transcribes on-device rather than through this endpoint, and there has been no end-to-end run against a live container (the one thing that needs Docker + the model).

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.