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 connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.