The openai-compat-asr backend (#877) shipped with settings routes but no
discoverable activation path: the config panel hid in Settings → Models,
its hint text claimed "there's no in-app engine picker for ASR yet"
(stale — the matrix has one), and there was no way to check a server
actually answers before pointing a dub/dictation run at it.
Configure → test → activate now live on one screen, Settings → Engines:
- The ASR family tab mounts the config panel (URL / model / optional
API key) below the engine matrix; saving refetches the matrix via a
new reloadToken prop so the engine row flips unavailable → available
and its "Use" button appears without a manual refresh.
- New "Test connection" button + loopback-gated
POST /api/settings/asr-openai-compat/test: saves first (same
stale-config contract as /llm-providers/{id}/test), then probes
GET {base_url}/models — no audio leaves the machine. The structured
verdict maps to localized, actionable messages: latency + whether the
configured model is listed on success; classified auth_failed /
http_error / timeout / unreachable / ok_no_models failures. detail is
core.scrub-ed; the key is never logged or echoed.
- Engine reads persisted config fresh per transcribe (regression test) —
config changes need no backend restart. Never default-active: ASR
auto-detect only picks local engines.
- i18n for every new string (en.json); no hardcoded CJK; identical
behavior on macOS/Windows/Linux (pure HTTP + React).
- Docs-sync: docs/engines/openai-compatible-asr.md rewritten around the
one-screen flow with LM Studio / llama.cpp / Groq / OpenAI examples
and the privacy note; README engine table cell updated.
Verified end-to-end against a fake OpenAI-compatible server: UI drive
(configure → test → row flip → Use) plus a real transcription through
the backend's /v1/audio/transcriptions immediately after a config
change, no restart.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>