From 70909c323e5855e185dc060323684f2a175e46cf Mon Sep 17 00:00:00 2001 From: kaizawa97 <41991049+kaizawa97@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:56:38 +0900 Subject: [PATCH] feat(stt): add faster-whisper provider Three related changes, ported from the abandoned feat/meetnote branch and finished where that port stopped short. A `faster` provider. Unlike `whisper` and `mlx` it runs in-process rather than shelling out, and it decodes audio through PyAV's bundled FFmpeg instead of the system binary -- so it needs neither the binary-discovery path nor a separately installed ffmpeg. FFmpeg is not absent: it arrives inside PyAV's wheel. What goes away is the install step and the search for a binary, and that is the reason to have it: on a machine where installing the CLI toolchain is the hard part, this is a pip install of prebuilt wheels. Inference is quantised to int8 and offloaded to the subprocess executor, since it is CPU-bound and the segment iterator does the work as it is consumed. Scope, since upstream has moved: `apple` now covers macOS 26+ at roughly fifty times whisper's speed with nothing to install, so this provider earns its place on Linux, Windows, and older macOS -- the platforms with no `apple` path, which are also where getting ffmpeg onto the machine is most often the obstacle. A hallucination filter for the Whisper family. Whisper models fed silence emit two recognisable artefacts: one phrase repeated many times, and caption boilerplate memorised from training-set video subtitles. Both matter more here than in a general transcriber because this text reaches agents -- a phrase repeated forty times becomes forty note lines, and a memorised caption credit becomes a meeting note. A transcript that filters down to nothing returns None rather than boilerplate. The phrase half only removes caption SELF-ATTRIBUTION ("Subtitles by Amara.org", "Transcribed by"), never a spoken sign-off. Sign-offs and subscribe CTAs ("Thank you for watching", "Hit the bell") were dropped from the list during review: each is a sentence someone recording a demo or dictating a video script genuinely says, and because the match is whole-sentence and an emptied transcript becomes None, filtering one could delete the only words a recording held. An un-repeated hallucinated sign-off therefore survives into the transcript -- the safe direction of that trade, and the repetition collapse still catches the far more common form, where the model emits the sign-off for the rest of the window. AWS Transcribe is excluded from both halves: it uses a different decoder and does not produce these, so filtering it could only ever delete real speech. The full model enum. `stt.model` accepted exactly one value, `turbo`, which is a strange thing for a field named "model size". It now takes tiny/base/small/medium/large-v3/turbo -- the small end is the difference between usable and unusable on a machine short of RAM, and large-v3 is the accuracy ceiling. `turbo` stays the default. An unrecognised value warns and falls back rather than raising, matching the provider validator: a typo in one config field must not stop the Gateway booting. Three things the port source left undone, fixed here: - `_STT_MODEL_SIZES` is the dashboard's PUT allowlist, so expanding the loader's enum without expanding it would have had the API silently reject every new model. A test now pins the two sets equal. - The settings UI had no `faster` entry at all: the dropdown would have read a bare "faster", and the model picker was gated on `whisper` alone, so choosing `faster` showed no model control while the backend went on reading `stt.model`. Both fixed, with the install button, its blurb and the progress-step label. - `cli_doctor` reported a missing ffmpeg as an ISSUE for a provider that never looks for one, and `_stt_prereq_commands` offered the whole brew/Xcode/python toolchain as prerequisites for a plain pip install. Neither applies to `faster`. The user-facing copy says "no separate ffmpeg install", not "no ffmpeg needed", in all eleven catalogues. The shorter claim was wrong in a way that matters: it tells the user nothing of FFmpeg lands on their machine, when in fact PyAV's wheel carries it. The test that pins this string now asserts the accurate claim rather than merely that it mentions ffmpeg, so the imprecise wording cannot come back. faster-whisper is deliberately NOT added to setup.cfg extras. It is installed on demand from Settings, and cli_doctor names the Windows-ARM case where CTranslate2 publishes no wheel and no install can succeed. Two existing tests pinned the exact provider list while testing mlx gating; their expectations are updated, and a note says they track _VALID_STT_PROVIDERS on purpose. The new UI test mirrors that list too, and had gone stale against upstream's `apple` -- the omission the mirror exists to catch, so it is fixed and commented. Verified: 1,301 pytest passed across the stt/transcribe selection; the faster-whisper UI and i18n parity suites pass (79 tests); isort, flake8 and mypy clean. Transcription accuracy itself is unverified -- faster-whisper is not installed here, so the library is patched in every test. Co-authored-by: Bolin Chen Rebased onto current main and folded in the two review follow-ups filed against this PR, so they land with the provider rather than after it. Six files conflicted against a base that had moved 766 commits, and two of those conflicts were semantic rather than textual: main added the parakeet provider, so _VALID_STT_PROVIDERS, the config baseline and two provider-list mirrors in the tests all needed both additions, not either one. The i18n catalogs and the settings page merged cleanly. Issue #3782 -- surface the Windows-on-ARM wheel gap. CTranslate2 publishes no win-arm64 wheel and no sdist, so pip fails while RESOLVING and names ctranslate2, a package the user never asked for. That reads like a transient registry problem and invites retrying something that can never work, so pi_stt_install now refuses up front and names the two providers that do work here. The check is server-side rather than inside the generated script, and that is a correctness requirement, not a preference: the script is launched through ash -c, which does not exist on a stock native-Windows gateway, so the run dies with FileNotFoundError before executing a line. A guard inside the script would be unreachable on exactly the platform it is for. The same verdict is served from the config GET as aster_unsupported, mirroring ranscribe_unsupported, so the Settings card shows the notice and the alternatives BEFORE the press instead of turning every press into an identical 400. platform_compat.is_windows_on_arm() deliberately reports the PROCESS architecture. Windows on ARM runs x86-64 processes under emulation, and such an interpreter installs the win_amd64 wheel and works -- a host-architecture probe would refuse a package that succeeds. Issue #3780 -- honor stt.timeout_secs. Its first half (memoizing the constructed WhisperModel per model+device) was already implemented here, so only the unbounded future remained. The inference future is now bounded like every other provider's work, and it moved off subprocess_executor onto a dedicated stt_executor. That pool split is the substance: a started run_in_executor future cannot be cancelled, so a wedged model load -- or a first-run multi-GB weight download inside the library constructor -- holds its worker until the process exits. On the PTY-teardown pool it would consume one of the eight workers whose whole purpose is absorbing a teardown storm, making the recovery path starvable by the thing it recovers from. Two workers, because each in-flight call keeps a fully quantised model resident, so the count is a memory ceiling and not only a CPU one. The timeout releases the CALLER, never the thread, and both the log line and the docstring say so rather than implying a cancel. One judgement call worth naming: cli_doctor reports the win-arm case as a hard issue even on Windows, departing from the stt_fatal downgrade its neighbours use. That downgrade exists because whisper and ffmpeg are absent from a stock Windows box yet trivially installable, so failing a first-run doctor over them is noise. This is the opposite case -- aster is never the default, so reaching it means the user explicitly chose a provider that cannot be made to work -- and a run that exits 0 would contradict the alternatives it just printed. The ordinary not-installed arm still follows the convention, because the install button does fix that one. Verified on Windows: 828 passed / 52 skipped across the stt, transcribe, dashboard-core, cli, platform-compat, executors and config-baseline suites, plus 13 frontend tests for the notice and its catalogs. flake8, isort, black (diff-scoped gate) and mypy --platform linux all clean -- mypy went from 7 errors to 6, the fixed one being a dangling reference this rebase would otherwise have shipped. One inherited failure, test_executors.py::test_cancelling_the_caller_does_not_kill_a_job_already_running, reproduces identically on unmodified main. Transcription accuracy stays unverified: faster-whisper is not installed here, so the library is patched in every test. Co-authored-by: chenmingwei23 Co-authored-by: Kiro Crew --- config-baseline.json | 10 +- .../modules/learn-cron-dashboard.md | 2 +- src/kiro_crew/cli_doctor.py | 47 +- src/kiro_crew/config/loader.py | 48 +- src/kiro_crew/dashboard/handlers/core.py | 200 ++++- src/kiro_crew/executors.py | 54 +- src/kiro_crew/platform_compat.py | 24 + src/kiro_crew/transcribe.py | 338 ++++++++ .../01-faster-not-installed-dark.png | Bin 0 -> 109719 bytes .../02-faster-installing-dark.png | Bin 0 -> 109717 bytes .../03-faster-ready-light.png | Bin 0 -> 47353 bytes test/test_cli.py | 69 ++ test/test_dashboard_handlers_core_coverage.py | 146 +++- test/test_platform_compat.py | 73 ++ test/test_stt_stream.py | 31 +- test/test_transcribe_faster.py | 768 ++++++++++++++++++ website/scripts/capture-stt-faster.mjs | 184 +++++ website/src/i18n/locales/bn.json | 5 + website/src/i18n/locales/de.json | 5 + website/src/i18n/locales/en-XA.json | 7 +- website/src/i18n/locales/en.manual.json | 7 +- website/src/i18n/locales/es.json | 5 + website/src/i18n/locales/fr.json | 5 + website/src/i18n/locales/hi.json | 5 + website/src/i18n/locales/it.json | 5 + website/src/i18n/locales/ja.json | 5 + website/src/i18n/locales/ko.json | 5 + website/src/i18n/locales/pt.json | 5 + website/src/i18n/locales/ru.json | 5 + website/src/i18n/locales/zh-CN.json | 5 + website/src/pages/settings/SttSettings.tsx | 50 +- website/src/test/SttSettingsFaster.test.ts | 146 ++++ 32 files changed, 2215 insertions(+), 44 deletions(-) create mode 100644 temp-screenshots/stt-faster-whisper/01-faster-not-installed-dark.png create mode 100644 temp-screenshots/stt-faster-whisper/02-faster-installing-dark.png create mode 100644 temp-screenshots/stt-faster-whisper/03-faster-ready-light.png create mode 100644 test/test_transcribe_faster.py create mode 100644 website/scripts/capture-stt-faster.mjs create mode 100644 website/src/test/SttSettingsFaster.test.ts diff --git a/config-baseline.json b/config-baseline.json index c280a78365c..510c1e24cd6 100644 --- a/config-baseline.json +++ b/config-baseline.json @@ -2540,7 +2540,8 @@ "mlx", "apple", "parakeet", - "transcribe" + "transcribe", + "faster" ], "defaultValue": "whisper" }, @@ -2567,9 +2568,14 @@ "sensitive": false, "tags": [], "label": "Model", - "help": "Whisper model size.", + "help": "Whisper model size (whisper and faster providers).", "hasChildren": false, "enumValues": [ + "tiny", + "base", + "small", + "medium", + "large-v3", "turbo" ], "defaultValue": "turbo" diff --git a/docs/system-specs/modules/learn-cron-dashboard.md b/docs/system-specs/modules/learn-cron-dashboard.md index 0870889f637..b878492aa88 100644 --- a/docs/system-specs/modules/learn-cron-dashboard.md +++ b/docs/system-specs/modules/learn-cron-dashboard.md @@ -1265,7 +1265,7 @@ React 18 + TypeScript + Vite 5 + Redux Toolkit + React Router v7 + Tailwind CSS - **Lessons tab**: add lesson form + lesson table with Delete actions - **Skills tab (CRUD)**: + New button with create form (name + SKILL.md editor), installed skill list with click-to-view, ✏ Edit button with inline textarea editor + Save, ✕ Delete with confirmation, name sanitized to lowercase + hyphens. AIM Skills section shows skills from `~/.aim/` grouped by package with Uninstall button per package. Skills are fully AIM-managed — no bundled skills; `AIPowerUserCapabilities` installed by default via setup/update. - **MCP Servers tab**: Controls `~/.kiro/settings/mcp.json` (global config that kiro-cli ACP loads at runtime). Server-level enable/disable sets `disabled: true/false` in global config and syncs `@server` to kirocrew.json `tools`/`allowedTools`. Per-tool enable/disable sets `disabledTools` array in global config. Probe All discovers tools per server, preserves enabled/disabledTools state across probes. Enable All / Disable All bulk buttons. Tool chips: green = enabled (clickable to disable), strikethrough = disabled (clickable to enable). Apply & Restart at top bar resets all active sessions. Live server badges (🔌 color-coded by status). - - **Slack tab**: STT (Speech-to-Text) settings card — toggle enabled/disabled, provider selector (`whisper` / `mlx` / `transcribe`), model selector (turbo ~1.6 GB), status badge (ready/not installed), provider-aware install button (`brew install openai-whisper` for `whisper`, `pipx install mlx-whisper` for `mlx`; hidden for `transcribe`, which has no local runtime to install — its availability is "`boto3` + `amazon-transcribe` importable by the gateway process", so the prereq block instead surfaces ` -m pip install 'kirocrew[voice]'` (PowerShell `&`-form on Windows; suppressed once the extra is importable) plus a platform ffmpeg command when ffmpeg is missing and a restart-the-gateway hint, and `POST /api/stt/install` refuses with 400 `code: stt_no_local_install` for it rather than installing a Whisper runtime that cannot change Transcribe's status. When no install channel can make the extra importable — frozen build, the desktop app's code-signed bundled interpreter, pip-less interpreter, or a PEP 668 externally-managed python — the response carries `transcribe_unsupported: true` and the Settings page shows an unsupported notice instead of commands; `bundled_interpreter: true` discriminates the desktop-app cause, whose notice names the actual remedy (install Kiro Crew with pip and run the gateway from that environment) because "run the gateway from a different Python environment" is not actionable inside an app bundle. The response also carries `ffmpeg_missing`, served independently of `available`, because Transcribe's availability check treats ffmpeg as optional (it only affects the non-streaming `.webm` remux path) — the Settings page uses it to show the ffmpeg install command even while the status badge reads ready). The `mlx` provider (Apple Silicon Metal GPU) uses the `mlx_model` config key (default `mlx-community/whisper-large-v3-turbo`). Endpoints: `GET/PUT /api/config/stt`, `POST /api/stt/install`. **Homebrew resolution never relies on `PATH`**: a GUI-launched gateway (desktop app / launchd) inherits `/usr/bin:/bin:/usr/sbin:/sbin`, which contains no Homebrew prefix, so `shutil.which("brew")` reported Homebrew MISSING on machines that have it — the prereq list told a Homebrew user to install Homebrew and the `mlx` install script aborted at its first `command -v brew` with `ERROR: Homebrew required`. The prereq probe goes through `transcribe.find_brew()` (PATH, then the fixed prefixes `/opt/homebrew/bin`, `/usr/local/bin`, Linuxbrew system + per-user), and every install script is prefixed with `_stt_install_path_prelude()` — it prepends `transcribe.BREW_PATH_DIRS` (the same prefixes plus `~/.local/bin`, where pipx puts `mlx_whisper`) and then `eval "$(brew shellenv)"`. The prelude is required because the script runs as `bash -c`, which is neither a login nor an interactive shell, so the user's `~/.zprofile` `brew shellenv` line never executes. + - **Slack tab**: STT (Speech-to-Text) settings card — toggle enabled/disabled, provider selector (`whisper` / `mlx` / `apple` / `parakeet` / `transcribe` / `faster`), model selector (turbo ~1.6 GB; for `whisper`/`faster` the picker offers the six Whisper sizes `tiny`/`base`/`small`/`medium`/`large-v3`/`turbo`), status badge (ready/not installed), provider-aware install button (`brew install openai-whisper` for `whisper`, `pipx install mlx-whisper` for `mlx`; hidden for `transcribe`, which has no local runtime to install — its availability is "`boto3` + `amazon-transcribe` importable by the gateway process", so the prereq block instead surfaces ` -m pip install 'kirocrew[voice]'` (PowerShell `&`-form on Windows; suppressed once the extra is importable) plus a platform ffmpeg command when ffmpeg is missing and a restart-the-gateway hint, and `POST /api/stt/install` refuses with 400 `code: stt_no_local_install` for it rather than installing a Whisper runtime that cannot change Transcribe's status. When no install channel can make the extra importable — frozen build, the desktop app's code-signed bundled interpreter, pip-less interpreter, or a PEP 668 externally-managed python — the response carries `transcribe_unsupported: true` and the Settings page shows an unsupported notice instead of commands; `bundled_interpreter: true` discriminates the desktop-app cause, whose notice names the actual remedy (install Kiro Crew with pip and run the gateway from that environment) because "run the gateway from a different Python environment" is not actionable inside an app bundle. The response also carries `ffmpeg_missing`, served independently of `available`, because Transcribe's availability check treats ffmpeg as optional (it only affects the non-streaming `.webm` remux path) — the Settings page uses it to show the ffmpeg install command even while the status badge reads ready). The `mlx` provider (Apple Silicon Metal GPU) uses the `mlx_model` config key (default `mlx-community/whisper-large-v3-turbo`). The `faster` provider (faster-whisper / CTranslate2) installs **in-process** via `POST /api/stt/install` — `sys.executable -m pip install faster-whisper`, deliberately the gateway's own interpreter because the import has to succeed in THIS process — and needs no system ffmpeg, since it decodes through PyAV's bundled FFmpeg. Two refusals are specific to it: `GET /api/config/stt` serves `faster_unsupported: true` on native Windows-on-ARM (CTranslate2 publishes no `win_arm64` wheel and no sdist, so pip fails at resolution), and the install endpoint returns 400 `code: stt_no_install_channel` when the gateway python has no usable pip channel. Availability is LOCATED, never imported, on the request path (`importlib.util.find_spec`), because linking CTranslate2's native extension on the event loop would stall every gateway task. Endpoints: `GET/PUT /api/config/stt`, `POST /api/stt/install`. **Homebrew resolution never relies on `PATH`**: a GUI-launched gateway (desktop app / launchd) inherits `/usr/bin:/bin:/usr/sbin:/sbin`, which contains no Homebrew prefix, so `shutil.which("brew")` reported Homebrew MISSING on machines that have it — the prereq list told a Homebrew user to install Homebrew and the `mlx` install script aborted at its first `command -v brew` with `ERROR: Homebrew required`. The prereq probe goes through `transcribe.find_brew()` (PATH, then the fixed prefixes `/opt/homebrew/bin`, `/usr/local/bin`, Linuxbrew system + per-user), and every install script is prefixed with `_stt_install_path_prelude()` — it prepends `transcribe.BREW_PATH_DIRS` (the same prefixes plus `~/.local/bin`, where pipx puts `mlx_whisper`) and then `eval "$(brew shellenv)"`. The prelude is required because the script runs as `bash -c`, which is neither a login nor an interactive shell, so the user's `~/.zprofile` `brew shellenv` line never executes. - **Agent Config tab**: JSON editor with Save + warning about `kirocrew setup --agent-only` - **System** (`/system`) — live metrics (1s refresh): CPU %, memory used/total, network RX/TX stat cards; host info with correct Apple Silicon arch detection, load averages; memory, process, network, storage detail cards; uptime ticking every 1s via `useUptime` hook (client-side from `start_time`) - **Agent Capabilities** (`/capabilities`; `/agents` redirects here) — merged Agents + Capabilities destination, bottom-pinned in the nav. `SidePanelLayout` tabs in order: **Agents** (agent → workspace → memory store bindings, `KiroCrewAgentsPage` embedded), **Agent Templates** (installed agent configurations/packages, `AgentsPage` embedded), **Integrations (MCP)**, **Skills**, **Hooks**, **Prompts**. The templates tab keeps the side-by-side layout: installed agents list (left) with detail panel (right, height from `LAYOUT.AGENT_LIST_HEIGHT`). Installed agents card shows each agent with name, `SourceBadge` source pill (neutral `package`/`kirocrew`/`project`/`local` sources — the former `aim` source was generalized to `package`), model, description, skill count, MCP server count. Click to view full agent config in detail panel: system prompt, tools, auto-approved tools, MCP servers with `--aim` token colors (hover tooltip showing tool list), expandable denied commands list (`
` with all patterns). **Agent-package install / uninstall now routes through the capability seam** (`installPlugin`/`uninstallPlugin` in `providers/adapters/acp.ts` dispatch `type === 'agent'` to `/api/capability/agents/{install,uninstall}`). This replaced the earlier "intentionally NOT offered" stance: the pure-AIM `/api/aim/agents/*` + `/api/aim/update` routes were removed, but leaving `GET /api/capability/agents` as list-only meant the seam could SHOW installed agent packages and never manage them — an asymmetry with skills/MCP that forced an edition to shadow the core or mount its own routes. Bulk "Update All" remains unoffered (no `update_*` op on the seam). Skills/MCP install/uninstall still route through the capability seam (see Capability Integration); the manager returns human-friendly errors for invalid packages. The MCP registry browser is hidden entirely when no external capability manager is configured (registry → 503); when present it offers click-to-expand descriptions with all detail lines, clickable URLs (DOMPurify-sanitized), a direct Install button, and tier badges at `text-[11px]` minimum. Striped subagent table with `EmptyState` when empty, kiro credit usage card, context window usage bars per session (agent name in `--aim` color). diff --git a/src/kiro_crew/cli_doctor.py b/src/kiro_crew/cli_doctor.py index d425d8c0f88..6719cb1395a 100644 --- a/src/kiro_crew/cli_doctor.py +++ b/src/kiro_crew/cli_doctor.py @@ -93,7 +93,12 @@ from kiro_crew.service import linux as service_linux from kiro_crew.session_pid_sig import signing_health from kiro_crew.subprocess_utf8 import UTF8_TEXT -from kiro_crew.transcribe import _find_parakeet_mlx, _find_whisper, ensure_ffmpeg_in_path +from kiro_crew.transcribe import ( + _faster_whisper_model, + _find_parakeet_mlx, + _find_whisper, + ensure_ffmpeg_in_path, +) from kiro_crew.validation import _AGENT_NAME_RE logger = logging.getLogger(__name__) @@ -2500,7 +2505,11 @@ def _doctor(platform_boot_error: "Exception | None" = None, bundle: bool = False print("\nSpeech-to-Text") stt_active = cfg.stt.enabled needs_whisper = stt_active and cfg.stt.provider == "whisper" - needs_ffmpeg = stt_active # both providers use ffmpeg + # Every provider but ``faster`` shells out to something that needs the system + # ffmpeg; faster-whisper decodes in-process through PyAV's bundled copy, so + # reporting a missing ffmpeg as an ISSUE there would send the user to install a + # binary their configuration never calls. + needs_ffmpeg = stt_active and cfg.stt.provider != "faster" if not stt_active: print(" status: ⏹ disabled (enable from dashboard → Overview → Slack)") @@ -2589,6 +2598,40 @@ def _doctor(platform_boot_error: "Exception | None" = None, bundle: bool = False if stt_fatal: issues.append("parakeet-mlx") + # faster-whisper (CTranslate2) is installed on demand, not as a declared extra, + # so an unavailable library is the expected first-run state rather than a broken + # install. Windows on ARM is called out separately because no CTranslate2 wheel + # exists there at all — the install button cannot fix it, and telling the user to + # retry would waste their time instead of naming a provider that does work. + if stt_active and cfg.stt.provider == "faster": + if _faster_whisper_model() is not None: + print(" faster: ✅ faster-whisper importable") + elif platform_compat.is_windows_on_arm(): + # Deliberately NOT routed through ``stt_mark``/``stt_fatal``. That + # Windows downgrade exists because whisper and ffmpeg are absent from a + # stock Windows box yet trivially installable, so failing a first-run + # doctor over them is noise. This is the opposite case: ``faster`` is + # never the default, so reaching here means the user explicitly selected + # a provider that CANNOT be made to work on this machine. That is a real + # configuration fault, and the whole point of naming the alternatives is + # that the run should not exit 0 as if nothing were wrong. + print(" faster: ❌ not available (Windows on ARM — no CTranslate2 wheel)") + print( + " Alternatives: set stt.provider to 'whisper' " + "(local) or 'transcribe' (AWS)" + ) + issues.append("faster-whisper: unavailable on Windows ARM") + else: + # The ordinary not-yet-installed state, which the install button DOES + # fix — so this one follows the platform convention like whisper above. + print(f" faster: {stt_mark} not installed") + print( + " Install from dashboard → Settings → " + "Speech-to-Text, or: pip install faster-whisper" + ) + if stt_fatal: + issues.append("faster-whisper") + # ── Slack (optional) ── print("\nSlack Integration") if _has_slack: diff --git a/src/kiro_crew/config/loader.py b/src/kiro_crew/config/loader.py index 543bcb27a5f..2e46a7f038a 100644 --- a/src/kiro_crew/config/loader.py +++ b/src/kiro_crew/config/loader.py @@ -4364,7 +4364,19 @@ def from_dict(cls, data: dict) -> ChannelConfig: ) -_VALID_STT_PROVIDERS = ("whisper", "mlx", "apple", "parakeet", "transcribe") +_VALID_STT_PROVIDERS = ("whisper", "mlx", "apple", "parakeet", "transcribe", "faster") + +#: Whisper model sizes accepted for ``stt.model``. +#: +#: Shared by the ``whisper`` and ``faster`` providers, which both name models this +#: way. (``mlx`` uses ``stt.mlx_model`` and ``parakeet`` uses +#: ``stt.parakeet_model``, HuggingFace repo ids, instead.) +#: +#: ``turbo`` stays the default: it is the only entry the dashboard offered before, +#: and it is the best accuracy-per-second of the set. The smaller sizes exist +#: because they are the difference between usable and unusable on a machine +#: without much RAM, and ``large-v3`` because it is the accuracy ceiling. +_VALID_STT_MODELS = ("tiny", "base", "small", "medium", "large-v3", "turbo") _VALID_CHANNEL_PREFIXES = ("C", "D", "G") @@ -4376,6 +4388,32 @@ def _validated_stt_provider(value: str) -> str: return "whisper" +def _validated_stt_model(value: object) -> str: + """Return *value* as the STT model name, warning when it is off the menu. + + Unknown STRINGS pass through with a warning rather than being coerced: the + old loader accepted any string, and openai-whisper legitimately takes names + outside the dashboard's size menu (``tiny.en``/``base.en``/``small.en``/ + ``medium.en``/``large-v2``), so coercing a hand-edited config to ``turbo`` + would silently remove a real capability. Providers degrade safely on a bad + name anyway: the whisper CLI errors per-recording, and faster-whisper + resolves an unknown name to a download error, both logged, neither fatal. + Only a NON-STRING (numbers, null, nested json from a mangled edit) falls + back to ``turbo``, since it cannot be passed to any provider at all. + """ + if isinstance(value, str) and value: + if value not in _VALID_STT_MODELS: + logger.warning( + "STT model '%s' is not in the dashboard menu %s; passing it through" + " — the provider will reject it per-recording if it is invalid", + value, + list(_VALID_STT_MODELS), + ) + return value + logger.warning("Non-string STT model %r, falling back to turbo", value) + return "turbo" + + _VALID_COMPLETION_KEEP = ("head", "tail", "both") @@ -4678,7 +4716,11 @@ class SttConfig: ) model: str = field( default="turbo", - metadata=_meta("Model", "Whisper model size.", enum=["turbo"]), + metadata=_meta( + "Model", + "Whisper model size (whisper and faster providers).", + enum=list(_VALID_STT_MODELS), + ), ) mlx_model: str = field( default="mlx-community/whisper-large-v3-turbo", @@ -7764,7 +7806,7 @@ def _load_resolved(cls) -> KiroCrewConfig: whisper_path=stt_data.get("whisper_path", ""), # Default "turbo" — faster and recommended for most users # (809M vs 74M, but much better latency). - model=stt_data.get("model", "turbo"), + model=_validated_stt_model(stt_data.get("model", "turbo")), mlx_model=stt_data.get("mlx_model", "mlx-community/whisper-large-v3-turbo"), parakeet_model=stt_data.get("parakeet_model", "mlx-community/parakeet-tdt-0.6b-v3"), device=stt_data.get("device", "cpu"), diff --git a/src/kiro_crew/dashboard/handlers/core.py b/src/kiro_crew/dashboard/handlers/core.py index 216618dfca0..e8e564c5f40 100644 --- a/src/kiro_crew/dashboard/handlers/core.py +++ b/src/kiro_crew/dashboard/handlers/core.py @@ -46,7 +46,13 @@ from kiro_crew.executors import discovery_executor from kiro_crew.metrics import provider as _metrics_provider from kiro_crew.security_posture import build_posture_snapshot_async, posture_counts_async -from kiro_crew.transcribe import BREW_PATH_DIRS, ensure_ffmpeg_in_path, find_brew, is_available +from kiro_crew.transcribe import ( + BREW_PATH_DIRS, + _faster_whisper_model, + ensure_ffmpeg_in_path, + find_brew, + is_available, +) logger = logging.getLogger(__name__) @@ -476,7 +482,19 @@ async def pwa_file(request: web.Request) -> web.StreamResponse: # ── STT (Speech-to-Text) ── +#: Whisper model sizes offered in the STT picker and accepted on PUT. +#: +#: Maps model -> approximate on-disk download size, which is the number that +#: actually decides the choice on a laptop. Keys MUST stay in step with +#: ``_VALID_STT_MODELS`` in the config loader: this dict is the PUT allowlist, so a +#: model the loader accepts but this omits would be silently rejected by the API. +#: ``test_stt_model_sizes_cover_valid_models`` pins that. _STT_MODEL_SIZES: dict[str, str] = { + "tiny": "~75 MB", + "base": "~145 MB", + "small": "~484 MB", + "medium": "~1.5 GB", + "large-v3": "~3.1 GB", "turbo": "~1.6 GB", } @@ -638,16 +656,20 @@ async def api_stt_config(request: web.Request) -> web.Response: cfg = KiroCrewConfig.load() provider = cfg.stt.provider - available = is_available(cfg.stt) # _stt_prereq_commands probes for a system python/brew via subprocess; run it # off the event loop so a slow/again-spawned interpreter check can't stall the # gateway (observed as "event-loop heartbeat: lag" on Windows where the probe # is heavier). The GET is read-only, so threading it is safe. The ffmpeg and # install-channel probes ride in the same thread: ensure_ffmpeg_in_path, # find_spec and the PEP 668 marker check all touch the filesystem, which - # does not belong on the loop either. - - def _prereqs_and_probes() -> tuple[list[str], bool, bool, bool]: + # does not belong on the loop either. is_available() rides here too for the + # same reason and not as an afterthought: EVERY provider branch of it reaches + # the filesystem — a real `import amazon_transcribe` plus shutil.which for + # `transcribe`, find_spec for `faster`, and a stats-only lookup for `mlx`, + # `parakeet` and `apple` — so leaving it on the loop would put the heaviest + # probe of the set outside the thread that exists to hold the lighter ones. + + def _prereqs_and_probes() -> tuple[list[str], bool, bool, bool, bool]: cmds = _stt_prereq_commands(provider) ensure_ffmpeg_in_path() no_ffmpeg = shutil.which("ffmpeg") is None @@ -656,11 +678,15 @@ def _prereqs_and_probes() -> tuple[list[str], bool, bool, bool]: # user guidance (no Python environment of the user's own to fix), so # the UI needs to distinguish it from the pip-less/PEP 668 causes. bundled = platform_compat.is_bundled_interpreter() - return cmds, no_ffmpeg, unsupported, bundled - - prereqs, ffmpeg_missing, transcribe_unsupported, bundled_app = await asyncio.to_thread( - _prereqs_and_probes - ) + return cmds, no_ffmpeg, unsupported, bundled, is_available(cfg.stt) + + ( + prereqs, + ffmpeg_missing, + transcribe_unsupported, + bundled_app, + available, + ) = await asyncio.to_thread(_prereqs_and_probes) return web.json_response( { "enabled": cfg.stt.enabled, @@ -697,6 +723,15 @@ def _prereqs_and_probes() -> tuple[list[str], bool, bool, bool]: # threaded probe above: find_spec and the marker check touch the # filesystem. "transcribe_unsupported": transcribe_unsupported, + # True when this platform has no CTranslate2 wheel at all, so the + # faster-whisper install can only ever fail. Mirrors + # `transcribe_unsupported` so the Settings card can show the notice and + # the alternatives BEFORE the user presses Install, rather than turning + # every press into an identical 400. Deliberately NOT in the threaded + # probe above: `is_windows_on_arm()` short-circuits on a module constant + # off Windows and otherwise reads a cached `platform.uname()` — no + # filesystem, no subprocess, so it is loop-safe as the probes are not. + "faster_unsupported": platform_compat.is_windows_on_arm(), "bundled_interpreter": bundled_app, # ffmpeg is required to remux the browser's .webm for the # non-streaming path, but is_available() only logs a warning when @@ -791,7 +826,15 @@ def _stt_prereq_commands(provider: str = "whisper") -> list[str]: The ``mlx`` and ``parakeet`` providers have their own lightweight prerequisite (``pipx install mlx-whisper`` / ``pipx install parakeet-mlx``) and only need ffmpeg beyond that — they do not require the system-python/whisper toolchain. + The ``faster`` provider needs nothing manual at all. """ + if provider == "faster": + # Nothing manual: faster-whisper is a pip install of prebuilt wheels, and it + # decodes audio through PyAV's bundled FFmpeg — so neither the system ffmpeg + # nor the brew/Xcode toolchain the CLI providers need applies here. Returned + # before ensure_ffmpeg_in_path() so this path does no filesystem probing for + # a binary it will not use. + return [] if provider == "transcribe": # AWS Transcribe's availability is "boto3 + amazon-transcribe importable # by THIS gateway process" (see kiro_crew.transcribe.is_available); the @@ -970,6 +1013,13 @@ async def api_stt_install(request: web.Request) -> web.Response: {"error": f"Install already in progress: {_stt_install_status['step']}"}, status=409 ) + # RESERVE the slot before anything can yield: the busy check above and the + # probes below would otherwise race — an `await` between check and set lets + # two concurrent requests both pass the 409 gate and launch pip twice + # against the same environment. Every rejection path below must roll this + # back to idle. + _stt_install_status = {"step": "starting", "detail": "", "error": ""} + # Native install via shell script, tailored to the configured provider. # Transcribe has no local runtime to install (its requirement is the # ``voice`` extra importable by this process, surfaced as a prerequisite @@ -977,6 +1027,7 @@ async def api_stt_install(request: web.Request) -> web.Response: # change Transcribe's availability. provider = KiroCrewConfig.load().stt.provider if provider == "transcribe": + _stt_install_status = {"step": "idle", "detail": "", "error": ""} _sel().log_api_access( caller=caller, operation="stt.install", @@ -994,7 +1045,67 @@ async def api_stt_install(request: web.Request) -> web.Response: status=400, ) - _stt_install_status = {"step": "starting", "detail": "", "error": ""} + # Windows on ARM has NO CTranslate2 wheel — faster-whisper's inference backend — + # and no sdist either, so pip fails while RESOLVING rather than while building. + # That distinction is why this check exists: the failure surfaces as a resolver + # error naming ``ctranslate2``, a package the user never asked for, which reads + # like a transient registry problem and invites retrying forever. Nothing about + # the machine can change the outcome, so refuse up front and name the two + # providers that do work here — the same alternatives ``cli_doctor`` prints, + # which a dashboard user never sees. + # + # Checked SERVER-SIDE rather than inside the generated shell script, and that is + # not a style preference: ``api_stt_install`` launches the script through + # ``bash -c``, so on a stock native-Windows gateway the run dies with + # ``FileNotFoundError`` ("bash not found") before any line of it executes. An + # in-script guard would be unreachable on precisely the platform it is for. + # + # ``is_windows_on_arm()`` keys off the PROCESS architecture, so an x86-64 + # interpreter under emulation is correctly left alone: it installs the + # ``win_amd64`` wheel and works. + if provider == "faster" and platform_compat.is_windows_on_arm(): + _stt_install_status = {"step": "idle", "detail": "", "error": ""} + _sel().log_api_access( + caller=caller, + operation="stt.install", + outcome="denied", + error="no ctranslate2 wheel for provider=faster on windows-arm64", + ) + return web.json_response( + { + "code": "stt_unsupported_platform", + "error": ( + "faster-whisper is not available on Windows on ARM" + " (no CTranslate2 wheel exists for this platform)." + " Alternatives: set stt.provider to 'whisper' (local)" + " or 'transcribe' (AWS)." + ), + }, + status=400, + ) + + # ``faster`` is imported in-process, so its install must land in the + # gateway's own interpreter (see _build_stt_install_script). Where no pip + # channel into that interpreter exists — frozen build, bundled desktop + # interpreter, pip-less python — the script below cannot succeed, and + # running it anyway recreates the press-and-nothing-changes failure. + if provider == "faster" and not await asyncio.to_thread(_pip_install_channel_available): + _stt_install_status = {"step": "idle", "detail": "", "error": ""} + _sel().log_api_access( + caller=caller, + operation="stt.install", + outcome="denied", + error="no pip install channel for provider=faster", + ) + return web.json_response( + { + "code": "stt_no_install_channel", + "error": "This gateway's Python can't install extra packages, so" + " faster-whisper can't be enabled here. Run the gateway from a" + " Python environment where pip can install faster-whisper.", + }, + status=400, + ) _sel().log_api_access( caller=caller, @@ -1036,6 +1147,8 @@ async def api_stt_install(request: web.Request) -> web.Response: # The detail line carries the accurate "parakeet-mlx" text, so a # dedicated step (and its 14-locale i18n key) is not warranted. _stt_install_status = {"step": "installing_mlx", "detail": line, "error": ""} + elif "Installing faster-whisper" in line: + _stt_install_status = {"step": "installing_faster", "detail": line, "error": ""} elif "No suitable python3" in line: _stt_install_status = {"step": "installing_python", "detail": line, "error": ""} elif "Using:" in line: @@ -1059,6 +1172,12 @@ async def api_stt_install(request: web.Request) -> web.Response: return web.json_response({"ok": False, "error": output[-500:]}, status=500) _stt_install_status = {"step": "done", "detail": "Whisper ready", "error": ""} + if provider == "faster": + # Warm the import cache OFF the event loop so the next is_available() + # (a cached read, loop-safe) reports ready without a gateway restart. + # Importing here would load CTranslate2's native extension on the + # loop, which is exactly what the cached-read design avoids. + await asyncio.to_thread(_faster_whisper_model) _sel().log_api_access( caller=caller, operation="stt.install", @@ -1068,10 +1187,14 @@ async def api_stt_install(request: web.Request) -> web.Response: return web.json_response( { "ok": True, - "ffmpeg": ( - shutil.which("ffmpeg") is not None - or os.path.isfile(os.path.expanduser("~/ffmpeg/ffmpeg")) - ), + # `ffmpeg: false` makes the Settings page show an + # "installed but ffmpeg missing" error toast. The faster + # provider decodes through PyAV's bundled FFmpeg and never + # uses the system binary, so a missing system ffmpeg is not an + # error for it — always report True to keep the toast away. + "ffmpeg": provider == "faster" + or shutil.which("ffmpeg") is not None + or os.path.isfile(os.path.expanduser("~/ffmpeg/ffmpeg")), } ) except asyncio.TimeoutError: @@ -1131,19 +1254,56 @@ def _stt_install_path_prelude() -> str: def _build_stt_install_script(provider: str = "whisper") -> str: """Shell script that installs the runtime for the selected STT provider. + - ``faster``: installs faster-whisper via pip (CTranslate2, no system ffmpeg) + into the GATEWAY'S OWN interpreter — unlike the CLI providers below, the + library is imported in-process by ``kiro_crew.transcribe``, so a system + python's user-site would be invisible here and the install would report + "Done" while transcription stayed unavailable. - ``mlx``: installs mlx-whisper via pipx (Apple Silicon only) plus ffmpeg. - ``parakeet``: installs parakeet-mlx via pipx (Apple Silicon only) plus ffmpeg. - ``whisper`` (default): installs openai-whisper + ffmpeg via brew or pip. - The pip fallback deliberately targets a SYSTEM python with ``--user`` (never - the gateway's own venv, which is replaced on every upgrade). ``--user`` lands - in ``~/.local/bin``, which :func:`kiro_crew.transcribe._find_whisper` probes - via its ``_WHISPER_SEARCH_PATHS`` (and via ``shutil.which`` when that dir is - on PATH). It also constrains the resolve so pip can never drop into a source + The ``whisper`` pip fallback deliberately targets a SYSTEM python with + ``--user`` (never the gateway's own venv, which is replaced on every + upgrade): the CLI binary lands in ``~/.local/bin``, which + :func:`kiro_crew.transcribe._find_whisper` probes via its + ``_WHISPER_SEARCH_PATHS`` (and via ``shutil.which`` when that dir is on + PATH). It also constrains the resolve so pip can never drop into a source build — see the ``BINARY_ONLY`` comment in the script for why an incompatible wheel otherwise reports itself as a compiler error. """ prelude = _stt_install_path_prelude() + if provider == "faster": + # No $PY probe and no --user: the import happens in THIS process, so the + # one interpreter whose environment matters is sys.executable. --user is + # doubly wrong for it — inside a venv pip refuses the flag outright + # ("Can not perform a '--user' install ..."), and outside one it lands in + # a user-site this gateway may not even scan. api_stt_install gates this + # provider on _pip_install_channel_available(), so the command below is + # only reached where a pip install into sys.executable can succeed. + gateway_py = shlex.quote(sys.executable) + return prelude + f""" +# faster-whisper (CTranslate2 backend) — no system ffmpeg required, because audio +# is decoded in-process through PyAV's bundled FFmpeg. +# CTranslate2 publishes wheels for Linux x86-64/AArch64, macOS x86-64/ARM64 and +# Windows x86-64. Windows on ARM has NO wheel, so this script is unreachable +# there: api_stt_install refuses the request before building it (see the +# is_windows_on_arm gate) rather than letting pip fail with a resolver error that +# names ctranslate2 and reads as a transient problem worth retrying. +PY={gateway_py} +echo "Using: $PY ($($PY --version))" +echo "Installing faster-whisper..." +"$PY" -m pip install -q faster-whisper || {{ echo "ERROR: pip install faster-whisper failed"; exit 1; }} +# The import is the real check, so it must be able to FAIL this script: pip can +# report success while the package is unusable, and a CTranslate2 wheel whose +# native extension will not load is the common case. +if ! FW_PATH=$("$PY" -c "import faster_whisper; print(faster_whisper.__file__)" 2>&1); then + echo "ERROR: faster-whisper installed but is not importable:" + echo "$FW_PATH" + exit 1 +fi +echo "Done. faster_whisper=$FW_PATH" +""" if provider in ("mlx", "parakeet"): pipx_pkg = "parakeet-mlx" if provider == "parakeet" else "mlx-whisper" verify_bin = "parakeet-mlx" if provider == "parakeet" else "mlx_whisper" diff --git a/src/kiro_crew/executors.py b/src/kiro_crew/executors.py index 0f59b7161d2..791f07e6cb1 100644 --- a/src/kiro_crew/executors.py +++ b/src/kiro_crew/executors.py @@ -46,6 +46,16 @@ work gets its OWN small pool so a burst of screenshots queues among ITSELF and can never occupy the :func:`maintenance_executor` workers the orphan sweeps need to recover from a wedge. +* :func:`stt_executor` -- in-process speech-to-text inference + (:func:`kiro_crew.transcribe._run_faster_whisper_sync`). Minutes of CPU per + call on a long recording, and the first call for a model size may block on a + multi-GB weight download inside the library's constructor. It cannot share + :func:`subprocess_executor`: a ``run_in_executor`` future cannot be + cancelled, so a wedged model load would hold one of the eight PTY-teardown + workers indefinitely -- and those exist precisely so a teardown storm has + somewhere to go. A caller that gives up on a timeout does NOT free the + thread, which is the whole reason this work needs a pool it can only starve + for itself. Long-term direction: this blocking work should move into a dedicated supervised process (the VS Code extension-host model), so a wedge there cannot @@ -72,6 +82,7 @@ "discovery_executor", "embed_executor", "image_executor", + "stt_executor", "governance_executor", "cron_gate_executor", "CronGateTimeout", @@ -211,6 +222,18 @@ # maintenance sweeps or head-of-line blocking any other pool's work. _MAX_IMAGE_WORKERS = 2 +# In-process STT inference is the longest-running work in this module: minutes of +# CPU on a meeting-length recording, and the first call for a model size can block +# on a multi-GB weight download inside the library's constructor. TWO workers, +# deliberately small for a reason the other pools do not share -- each in-flight +# call holds a fully quantised model in RAM (up to ~GBs for large-v3), so the +# worker count is a MEMORY ceiling, not just a CPU one. Two lets a queued +# recording start while one finishes; more would let concurrent dictations OOM a +# small host. Sizing it here rather than reusing the 8-worker subprocess pool is +# the point: a wedged model load cannot be cancelled, so it must only ever be able +# to starve other STT work. +_MAX_STT_WORKERS = 2 + _lock = threading.Lock() _pool: ThreadPoolExecutor | None = None _subprocess_pool: ThreadPoolExecutor | None = None @@ -218,6 +241,7 @@ _discovery_pool: ThreadPoolExecutor | None = None _embed_pool: ThreadPoolExecutor | None = None _image_pool: ThreadPoolExecutor | None = None +_stt_pool: ThreadPoolExecutor | None = None _governance_pool: ThreadPoolExecutor | None = None _cron_gate_pool: ThreadPoolExecutor | None = None @@ -321,6 +345,31 @@ def image_executor() -> ThreadPoolExecutor: return _image_pool +def stt_executor() -> ThreadPoolExecutor: + """Return the process-wide STT inference pool, creating it on first use. + + Threads are named ``mc-stt``. Separate from :func:`subprocess_executor` for the + reason a started ``run_in_executor`` future cannot be cancelled: a wedged model + load (or a first-run weight download inside the library constructor) holds its + worker until the process exits, and on the PTY-teardown pool that would consume + one of the eight workers whose whole purpose is absorbing a teardown storm. + Here it can only starve other STT work, which is the containment we want. + + Callers bound their own wait (``stt.timeout_secs``); that releases the CALLER, + never the thread — see :func:`kiro_crew.transcribe._transcribe_faster`. + """ + global _stt_pool + if _stt_pool is None: + with _lock: + if _stt_pool is None: + _stt_pool = ThreadPoolExecutor( + max_workers=_MAX_STT_WORKERS, + thread_name_prefix="mc-stt", + ) + atexit.register(shutdown_maintenance_executor) + return _stt_pool + + def embed_executor() -> ThreadPoolExecutor: """Return the process-wide Ollama embed/probe pool, creating it on first use. @@ -679,7 +728,7 @@ async def run_in_embed_pool(func: Callable[..., _T], /, *args: Any, **kwargs: An def shutdown_maintenance_executor() -> None: """Shut down all maintenance pools if they were created. Idempotent.""" global _pool, _subprocess_pool, _cron_pool, _discovery_pool, _embed_pool - global _governance_pool, _image_pool, _cron_gate_pool + global _governance_pool, _image_pool, _cron_gate_pool, _stt_pool with _lock: pool, _pool = _pool, None subprocess_pool, _subprocess_pool = _subprocess_pool, None @@ -689,6 +738,7 @@ def shutdown_maintenance_executor() -> None: governance_pool, _governance_pool = _governance_pool, None image_pool, _image_pool = _image_pool, None cron_gate_pool, _cron_gate_pool = _cron_gate_pool, None + stt_pool, _stt_pool = _stt_pool, None if pool is not None: pool.shutdown(wait=False, cancel_futures=True) if subprocess_pool is not None: @@ -705,3 +755,5 @@ def shutdown_maintenance_executor() -> None: image_pool.shutdown(wait=False, cancel_futures=True) if cron_gate_pool is not None: cron_gate_pool.shutdown(wait=False, cancel_futures=True) + if stt_pool is not None: + stt_pool.shutdown(wait=False, cancel_futures=True) diff --git a/src/kiro_crew/platform_compat.py b/src/kiro_crew/platform_compat.py index f957bf7a0ae..6e855e58e31 100644 --- a/src/kiro_crew/platform_compat.py +++ b/src/kiro_crew/platform_compat.py @@ -17,6 +17,7 @@ import logging import ntpath import os +import platform import shutil import signal import stat @@ -107,6 +108,29 @@ def reexec_python_module(module: str, args: Sequence[str]) -> None: RENAME_NOREPLACE_AVAILABLE: bool = _RENAME_NOREPLACE_FN is not None +#: ARM machine strings as ``platform.machine()`` spells them on Windows. +#: ``ARM64`` is what a native arm64 interpreter reports; ``AARCH64`` is accepted +#: because that spelling reaches Windows through cross-built and MSYS/Cygwin +#: Pythons. Compared case-folded, so the casing here is documentation only. +_WINDOWS_ARM_MACHINES: frozenset[str] = frozenset({"arm64", "aarch64"}) + + +def is_windows_on_arm() -> bool: + """True when this interpreter is a NATIVE ARM64 process on Windows. + + Deliberately a property of the running PROCESS, not of the host CPU, because + every caller cares about which wheel tags pip will accept here. Windows on ARM + runs x86-64 processes under emulation, and in one of those ``platform.machine()`` + reports ``AMD64`` — correctly, since such an interpreter installs ``win_amd64`` + wheels and works fine. A host-architecture probe would report ARM for that same + process and wrongly refuse a package that installs. + + Keyed off :data:`IS_WINDOWS` rather than ``platform.system()`` so there is one + canonical Windows predicate in this module instead of two that can drift. + """ + return IS_WINDOWS and platform.machine().casefold() in _WINDOWS_ARM_MACHINES + + # Portable signal constants — signal.SIGKILL is undefined on Windows. SIGKILL: int = getattr(signal, "SIGKILL", 9) diff --git a/src/kiro_crew/transcribe.py b/src/kiro_crew/transcribe.py index fb3dadcbc29..4aa1ce2fd66 100644 --- a/src/kiro_crew/transcribe.py +++ b/src/kiro_crew/transcribe.py @@ -7,16 +7,20 @@ from __future__ import annotations import asyncio +import importlib +import importlib.util import logging import os import re import shutil import sys import tempfile +import threading from pathlib import Path from typing import Any from kiro_crew import aws_consent, dep_sync, platform_compat +from kiro_crew.executors import stt_executor from kiro_crew.sandbox import _PYTHON_ENV_PREFIXES # Transcribe-path deps are an OPTIONAL 'aws' extra (amazon-transcribe + boto3). @@ -34,9 +38,210 @@ CredentialResolver = object # type: ignore[assignment,misc] Credentials = None # type: ignore[assignment,misc] +# faster-whisper is an optional runtime installed on demand via /api/stt/install, +# NOT a declared extra. The module MUST stay importable when it is absent so the +# Gateway starts without the library and ``cli_doctor`` can report the gap. +# +# Deliberately NOT imported here. ``transcribe`` is reached from the gateway boot +# path (``dashboard.handlers.core`` imports it at module scope), and importing +# faster_whisper links CTranslate2's native extension — hundreds of ms of disk and +# dynamic linking paid by every launch of an install that has the library, before +# the dashboard socket accepts requests. The cache below is filled off-loop on +# first use instead; :func:`is_available` locates the library without executing it. +_FasterWhisperModel: Any = None + + +def _faster_whisper_model() -> Any: + """Return the faster-whisper ``WhisperModel`` class, or ``None`` if absent. + + Imports on first use and caches the class in the module global. Nothing + imports the library at module load, so this is the only place the native + extension is ever linked — and because the cache starts empty, a gateway that + booted before the on-demand install from Settings picks the library up without + a restart. While it stays absent the call costs one failed import each time. + + NEVER call on the event loop: importing faster_whisper loads CTranslate2's + native extension synchronously (hundreds of ms of disk and dynamic linking), + which would stall every gateway task. Call sites are the STT executor thread + (:func:`_run_faster_whisper_sync`), the install handler's ``asyncio.to_thread`` + warm-up, and ``cli_doctor`` (its own process, no loop). :func:`is_available` + runs on the loop and so must never call this — it locates the library with + ``importlib.util.find_spec``, which stats the import path without executing it. + """ + global _FasterWhisperModel + if _FasterWhisperModel is None: + try: + _FasterWhisperModel = importlib.import_module("faster_whisper").WhisperModel + except ImportError: + return None + return _FasterWhisperModel + + logger = logging.getLogger(__name__) +# --------------------------------------------------------------------------- +# Hallucination filter — suppress Whisper-family transcription artefacts. +# +# Whisper models fed silence or low-energy audio produce two recognisable +# artefacts, and both are worse than an empty transcript in this app: the text +# goes to agents, so a hallucinated sign-off becomes a meeting note, and a phrase +# repeated forty times becomes forty note lines. +# +# 1. One phrase repeating ("Thank you. Thank you. Thank you. …") +# 2. Boilerplate unrelated to the audio — subtitle credits, sign-offs, stock +# phrases memorised from the training set's video captions. +# +# Applied to every Whisper-family provider. The repetition collapse below is +# pure text logic and language-independent; the boilerplate list is English-only +# and matches nothing in a zh-CN or de-DE transcript, so a non-English recording +# gets the repetition half of this filter and none of the phrase half. +# AWS Transcribe uses a different decoder and does not produce these, +# so it is deliberately excluded rather than filtered "just in case" — running +# the filter there could only ever delete genuine speech. +# --------------------------------------------------------------------------- + +# Boilerplate phrases Whisper hallucinates on silence. Compared case-insensitively. +# LIST DISCIPLINE: an entry must be a CAPTION ARTEFACT — text that exists because +# a transcript was produced, not because anyone spoke. "Implausible as dictated +# speech" was the earlier bar and it was not strict enough: it admitted sign-offs +# and subscribe CTAs ("thank you for watching", "don't forget to subscribe", +# "hit the bell", "see you in the next video"), which anyone recording a demo or +# dictating a video script says out loud. Because the match is whole-sentence and +# a transcript filtered down to nothing returns None, such an entry can delete the +# only sentence a recording had — the speaker's own words, unrecoverable. Those +# entries are gone, along with the ordinary-speech phrases dropped before them +# ("goodbye", "copyright", "thanks for listening", "thanks for joining", "see you +# next time", "all rights reserved"). +# +# What remains is attribution text a caption track carries about itself. Nobody +# utters "Subtitles by the Amara.org community" into a voice memo, so no reading of +# these deletes speech. +# +# Residual, accepted deliberately: a single un-repeated hallucinated sign-off now +# survives into the transcript. That is the safe direction of the trade — one stray +# line a reader can see and ignore, versus silently destroying real speech — and the +# repetition collapse below still removes the far more common form of this artefact, +# where the model emits the same sign-off for the rest of the decode window. +_WHISPER_BOILERPLATE: tuple[str, ...] = ( + "subtitles by", + "subtitles by amara.org", + "subtitles by the amara.org community", + "subtitles created by", + "subtitled by", + "translated by", + "transcribed by", + "captioned by", + "amara.org", + "www.mooji.org", +) + +# How many consecutive identical sentences count as a repetition artefact rather +# than emphasis. Humans genuinely say a sentence two, three, even five times +# ("No. No. No.", a counted beat, an insistent refusal), so a low threshold +# rewrites real speech. The Whisper failure mode this targets repeats a phrase +# for the remainder of the decode window — typically dozens of times — so six +# is still far below the artefact and comfortably above plausible emphasis. +_REPEAT_THRESHOLD = 6 + +# Boilerplate matches the WHOLE sentence only, never a substring and never a +# word-count neighbourhood. Anything looser deletes real speech: a bare +# substring rule drops "Thanks for joining today's standup, let's start", and +# even a one-word slack drops "Thanks for joining, everyone." — a normal +# meeting opener. This filter runs on every Whisper-family transcript, so a +# false positive is silent loss of genuine speech; a false negative is one +# stray boilerplate line, which the repetition collapse usually removes anyway. +# Known multi-word artefact shapes ("Subtitles by Amara.org") are covered by +# listing the full phrase in _WHISPER_BOILERPLATE, not by loosening the match. + + +def _is_boilerplate_line(line: str) -> bool: + """Return True if *line* is exactly (case/punctuation aside) known boilerplate.""" + stripped = line.strip().rstrip(".!?,;:").strip().lower() + if not stripped: + return False + return stripped in _WHISPER_BOILERPLATE + + +def _collapse_repeated_phrases(text: str) -> str: + """Collapse runs of >= :data:`_REPEAT_THRESHOLD` identical sentences to one. + + Splits on sentence boundaries, keeping each sentence's trailing punctuation. + Only CONSECUTIVE runs collapse: the same sentence recurring later in a + meeting is ordinary speech, not an artefact. + """ + sentences = re.split(r"(?<=[.!?])\s+", text) + if len(sentences) <= 1: + return text + output: list[str] = [] + i = 0 + while i < len(sentences): + current_norm = sentences[i].strip().lower() + j = i + 1 + while j < len(sentences) and sentences[j].strip().lower() == current_norm: + j += 1 + if j - i >= _REPEAT_THRESHOLD: + output.append(sentences[i]) + else: + output.extend(sentences[i:j]) + i = j + return " ".join(output) + + +def filter_hallucinations(text: str) -> str: + """Remove Whisper hallucination artefacts from a transcript. + + May return ``""`` when the whole transcript was hallucinated — which is the + honest answer for a recording of silence, and is why callers treat an empty + result as "no transcript" rather than passing it on. + + Every removal is logged, because this is the one step in the pipeline that + can delete words the speaker actually said, and a silent deletion is + indistinguishable from the model never having heard them. What each log line + carries is deliberate: matched boilerplate is named verbatim, since it comes + from the fixed :data:`_WHISPER_BOILERPLATE` vocabulary and so reveals nothing + about the recording, whereas a collapsed repetition is reported only as a + COUNT — that text is ordinary speech and belongs in the transcript, not in + the log. Discarding the transcript outright is a warning rather than an info + line, because the caller then reports "no transcript" and the recording is + gone with no other trace. + """ + if not text: + return text + before = len(re.split(r"(?<=[.!?])\s+", text)) + text = _collapse_repeated_phrases(text) + sentences = re.split(r"(?<=[.!?])\s+", text) + collapsed = before - len(sentences) + + kept: list[str] = [] + dropped: list[str] = [] + for sentence in sentences: + (dropped if _is_boilerplate_line(sentence) else kept).append(sentence) + + if collapsed or dropped: + logger.info( + "stt hallucination filter: collapsed %d repeated sentence(s), " + "dropped %d boilerplate line(s)%s", + collapsed, + len(dropped), + (": " + "; ".join(sorted(set(dropped)))) if dropped else "", + ) + + result = " ".join(kept).strip() + if not result: + logger.warning( + "stt hallucination filter: discarded the entire transcript as " + "hallucinated (%d sentence(s) in, none kept); the caller will " + "report no transcript for this recording", + len(sentences), + ) + return result + + +#: Providers whose output passes through :func:`filter_hallucinations`. +_WHISPER_FAMILY_PROVIDERS = frozenset(("whisper", "mlx", "faster")) + + def _ffmpeg_candidate_dirs() -> list[str]: """Build the ordered directory list to probe for an ffmpeg install. @@ -339,6 +544,27 @@ def is_available(stt_config=None) -> bool: # type: ignore[no-untyped-def] if not shutil.which("ffmpeg"): logger.warning("ffmpeg not found; .webm transcription will be unavailable") return True + if provider == "faster": + # No ffmpeg probe: faster-whisper decodes audio itself through PyAV's + # bundled FFmpeg, so the system binary the CLI providers need is irrelevant. + # LOCATE, NEVER IMPORT — this function runs on the event loop (config GET, + # Slack voice), and importing faster_whisper links CTranslate2's native + # extension synchronously, which would stall every gateway task. find_spec + # only walks and stats the import path, so the answer stays correct + # immediately after a plain restart of an already-installed gateway without + # putting that load on the boot path. Once a transcription has warmed the + # cache, the class answers directly and even the stat is skipped. + if _FasterWhisperModel is not None: + return True + try: + return importlib.util.find_spec("faster_whisper") is not None + except (ImportError, ValueError): + # A partially-removed install can leave the name in sys.modules with + # no spec (ValueError) or a finder that raises (ImportError). Either + # way the library is not usable, and this runs on the loop serving + # /api/config/stt — reporting unavailable is correct and keeps the + # endpoint from 500ing on a broken environment. + return False if provider == "mlx": ensure_ffmpeg_in_path() return _find_mlx_whisper() is not None @@ -396,6 +622,9 @@ async def transcribe_audio(audio_path: str, stt_config=None) -> str | None: # t provider = stt_config.provider if provider == "transcribe": result = await _transcribe_aws(audio_path, stt_config) + elif provider == "faster": + # No ensure_ffmpeg_in_path: faster-whisper decodes in-process via PyAV. + result = await _transcribe_faster(audio_path, stt_config) elif provider == "mlx": await asyncio.to_thread(ensure_ffmpeg_in_path) result = await _transcribe_mlx(audio_path, stt_config) @@ -409,6 +638,13 @@ async def transcribe_audio(audio_path: str, stt_config=None) -> str | None: # t result = await _transcribe_native(audio_path, stt_config) if result: + # Before redaction, and before the caller sees anything: a transcript that + # is entirely hallucinated must come back as None, not as boilerplate for + # an agent to write into the notes. + if provider in _WHISPER_FAMILY_PROVIDERS: + result = await asyncio.to_thread(filter_hallucinations, result) + if not result: + return None result = await asyncio.to_thread(_redact_transcript, result) return result @@ -884,6 +1120,108 @@ async def _run_whisper_cli( _MLX_MODEL_RE = re.compile(r"^[A-Za-z0-9_-]+/[A-Za-z0-9._-]+$") +#: The one constructed WhisperModel, stored with its ``(model, device)`` key. +#: Loading a model re-reads and re-quantizes the weights (tens of MB for +#: ``tiny`` up to ~GBs for ``large-v3``), so constructing one per recording adds +#: multi-second latency — and CONCURRENT recordings would each hold a full +#: copy, compounding to RAM exhaustion on the 8-thread executor pool. The cache +#: is deliberately SINGLE-SLOT: the gateway serves one configured model at a +#: time, so switching sizes evicts the previous instance instead of keeping +#: every size ever selected resident (which would itself OOM a small host). +#: The lock serializes construction only; ``WhisperModel.transcribe`` is safe +#: to call from multiple threads on one instance. +_FW_MODEL_CACHE: dict[tuple[str, str], Any] = {} +_FW_MODEL_LOCK = threading.Lock() + + +def _cached_fw_model(model_cls: Any, model: str, device: str) -> Any: + """Return the shared WhisperModel for ``(model, device)``, single-slot.""" + key = (model, device) + with _FW_MODEL_LOCK: + fw_model = _FW_MODEL_CACHE.get(key) + if fw_model is None: + # Evict any other-size instance BEFORE constructing the new one, so + # peak residency during a switch is one model plus the one being + # built, never an unbounded accumulation of every size selected. + _FW_MODEL_CACHE.clear() + fw_model = model_cls(model, device=device, compute_type="int8") + _FW_MODEL_CACHE[key] = fw_model + return fw_model + + +def _run_faster_whisper_sync(audio_path: str, model: str, device: str) -> str | None: + """Run faster-whisper inference synchronously. NEVER call on the event loop. + + ``compute_type="int8"`` is what makes CPU inference practical — the models are + quantised on load, trading a little accuracy for the several-fold speedup that + keeps a meeting-length recording from taking longer than the meeting. + """ + model_cls = _faster_whisper_model() + if model_cls is None: + logger.error("faster-whisper not available — install: pip install faster-whisper") + return None + try: + fw_model = _cached_fw_model(model_cls, model, device) + segments, _info = fw_model.transcribe(audio_path, beam_size=5) + # `segments` is a GENERATOR: inference happens as it is consumed, which is + # precisely why this whole function belongs off the loop. + parts = [text for segment in segments if (text := segment.text.strip())] + return " ".join(parts).strip() or None + except Exception: + # Same contract as every other provider here: log and return None rather + # than raise, so one bad recording cannot take a caller down. + logger.exception("faster-whisper transcription failed") + return None + + +async def _transcribe_faster(audio_path: str, stt_config) -> str | None: # type: ignore[no-untyped-def] + """Transcribe with faster-whisper (CTranslate2), in-process. + + Unlike the ``whisper`` and ``mlx`` providers there is no subprocess and no + system ffmpeg: faster-whisper links CTranslate2 and decodes audio through + PyAV's bundled FFmpeg. That removes the whole binary-discovery problem, and is + why this provider is worth having on machines where installing the CLI + toolchain is the hard part. + + The model name needs no regex guard of the kind ``_MLX_MODEL_RE`` provides: + nothing is passed to a shell here, and faster-whisper resolves an unknown name + to a download or an error rather than executing it. + """ + # No availability guard HERE: this coroutine runs on the event loop, and the + # lazy import retry loads a native library. _run_faster_whisper_sync performs + # the same check (with the retry) inside the executor thread and returns + # None with a log line when the library is absent. + loop = asyncio.get_running_loop() + # stt_executor(), not subprocess_executor() and not asyncio.to_thread: + # inference is CPU-bound and minutes long, and the first call for a model size + # can block inside the library's constructor downloading weights. Its own pool + # means that cost can only ever queue behind OTHER STT work — see + # kiro_crew.executors.stt_executor for why sharing the PTY-teardown pool was + # the wrong bulkhead. + fut = loop.run_in_executor( + stt_executor(), + _run_faster_whisper_sync, + audio_path, + stt_config.model, + stt_config.device, + ) + try: + return await asyncio.wait_for(fut, timeout=stt_config.timeout_secs) + except asyncio.TimeoutError: + # The timeout releases the CALLER, not the thread. A running + # run_in_executor future cannot be interrupted, so the inference (or the + # weight download it is stuck in) continues to completion and its worker + # stays occupied until then; what this bound buys is that the dictation + # request itself fails fast instead of hanging forever. Matching the CLI + # providers, which also log and return None on timeout rather than raise. + logger.error( + "faster-whisper transcription timed out after %ds " + "(worker still running; it cannot be cancelled)", + stt_config.timeout_secs, + ) + return None + + async def _transcribe_mlx(audio_path: str, stt_config) -> str | None: # type: ignore[no-untyped-def] """Transcribe using the mlx_whisper CLI (Apple Silicon, Metal GPU). diff --git a/temp-screenshots/stt-faster-whisper/01-faster-not-installed-dark.png b/temp-screenshots/stt-faster-whisper/01-faster-not-installed-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..55a48e35aa6e97bb0c649ace328dd2e8df0a3941 GIT binary patch literal 109719 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 literal 0 HcmV?d00001 diff --git a/temp-screenshots/stt-faster-whisper/02-faster-installing-dark.png b/temp-screenshots/stt-faster-whisper/02-faster-installing-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1c241e39b2c19a3cf6cf4cde9389134fa8aa6858 GIT binary patch literal 109717 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 wX5HTiBbmt@h+#RLNGv!Qgz+pI1Zv8ycUM6aWAK literal 0 HcmV?d00001 diff --git a/test/test_cli.py b/test/test_cli.py index 765860b9677..5a1e02d6ea3 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -258,6 +258,75 @@ def _which(binary, **_kw): assert f" whisper: {expected_mark} not found" in out assert f" ffmpeg: {expected_mark} not found" in out + @pytest.mark.parametrize( + "win_arm, expect_fatal_glyph, expect_alternatives", + [(True, True, True), (False, False, False)], + ids=["windows-arm-unfixable", "windows-x64-installable"], + ) + def test_doctor_faster_windows_arm_is_fatal_even_on_windows( + self, tmp_path, capsys, monkeypatch, win_arm, expect_fatal_glyph, expect_alternatives + ): + """The `faster` win-arm arm deliberately does NOT follow ``stt_mark``. + + That Windows downgrade exists because whisper and ffmpeg are absent from a + stock Windows box yet trivially installable, so failing a first-run doctor + over them is noise. This is the opposite case: `faster` is never the default, + so reaching it means the user explicitly selected a provider that CANNOT be + made to work here, and the run must not exit 0 as if nothing were wrong. The + ordinary not-installed arm (win_arm=False) still follows the convention, + because the install button does fix that one. + """ + import kiro_crew.cli_doctor as _doc + from kiro_crew.config.loader import KiroCrewConfig + + agent_file = tmp_path / "kirocrew.json" + _healthy_agent_file(agent_file) + mock_run = MagicMock(returncode=0, stdout="kiro-cli 1.0.0", stderr="") + # Windows in BOTH arms: the point is what differs while IS_WINDOWS is true. + monkeypatch.setattr(_doc.platform_compat, "IS_WINDOWS", True) + monkeypatch.setattr(_doc.platform_compat, "is_windows_on_arm", lambda: win_arm) + + def _cfg_with_faster() -> KiroCrewConfig: + cfg = KiroCrewConfig() + cfg.stt.enabled = True + cfg.stt.provider = "faster" + return cfg + + monkeypatch.setattr(KiroCrewConfig, "load", classmethod(lambda cls: _cfg_with_faster())) + monkeypatch.setattr(_doc, "_find_whisper", lambda path=None: None) + monkeypatch.setattr(_doc, "ensure_ffmpeg_in_path", lambda: None) + # The library is absent, which is what routes into the two arms. + monkeypatch.setattr(_doc, "_faster_whisper_model", lambda: None) + + with ( + patch("kiro_crew.cli_doctor.shutil.which", side_effect=lambda b, **_k: f"/bin/{b}"), + patch("kiro_crew.cli_doctor.KIRO_AGENTS_DIR", tmp_path), + patch("kiro_crew.cli_doctor.subprocess.run", return_value=mock_run), + patch("urllib.request.urlopen", side_effect=urllib.error.URLError("no gateway")), + patch("kiro_crew.cli_doctor.is_local_only", return_value=True), + patch("kiro_crew.cli_doctor.config_dir", return_value=tmp_path), + patch("kiro_crew.cli_doctor.probe_server", side_effect=_noop_probe_server), + ): + exited = False + try: + _doctor() + except SystemExit as e: + exited = bool(e.code) + out = capsys.readouterr().out + if expect_fatal_glyph: + assert " faster: ❌ not available (Windows on ARM" in out + # An unfixable configuration must fail the run even on Windows. + assert exited is True + else: + assert " faster: ⚠️ not installed" in out + if expect_alternatives: + # Naming the working providers IS the deliverable — a bare refusal + # leaves the user with no next step. + assert "stt.provider to 'whisper'" in out + assert "'transcribe'" in out + else: + assert "stt.provider to 'whisper'" not in out + def test_doctor_reports_platform_boot_error_without_crashing(self, tmp_path, capsys): """A PlatformCompositionError from boot must be REPORTED by the doctor, not crash it — the doctor is the tool that diagnoses a broken setup, so diff --git a/test/test_dashboard_handlers_core_coverage.py b/test/test_dashboard_handlers_core_coverage.py index f05a63f41cc..dd01cbd3ec9 100644 --- a/test/test_dashboard_handlers_core_coverage.py +++ b/test/test_dashboard_handlers_core_coverage.py @@ -754,7 +754,12 @@ async def test_get_advertises_capabilities(self, seeded_config) -> None: # Streaming capability is served from the backend's own set so the # Settings UI gates on a CAPABILITY rather than a provider name. assert body["streaming_providers"] == ["transcribe", "apple"] - assert body["models"] == {"turbo": "~1.6 GB"} + # Tracks _STT_MODEL_SIZES (the PUT allowlist) rather than pinning one + # literal: the faster-whisper work widened the enum from `turbo` alone + # to the full Whisper size ladder, and a test pinned to yesterday's + # ladder fails on every legitimate widening. + assert body["models"] == core_mod._STT_MODEL_SIZES + assert "turbo" in body["models"] assert body["language_codes"][0] == "en-US" assert body["available"] is False assert body["prereqs"] == [] @@ -770,6 +775,21 @@ async def test_get_advertises_capabilities(self, seeded_config) -> None: # remux gap even when the provider reads ready. assert isinstance(body["ffmpeg_missing"], bool) + @pytest.mark.asyncio + async def test_get_serves_faster_unsupported_for_pre_click_gating(self, monkeypatch): + """Mirrors `transcribe_unsupported`. Without this the Settings card can only + learn the platform is unsupported by pressing Install and reading a 400 — the + same dead end on every press, which is the failure this flag removes.""" + monkeypatch.setattr(core_mod.platform_compat, "is_windows_on_arm", lambda: True) + resp = await core_mod.api_stt_config(_req()) + assert json.loads(resp.body)["faster_unsupported"] is True + + @pytest.mark.asyncio + async def test_get_reports_faster_supported_elsewhere(self, monkeypatch): + monkeypatch.setattr(core_mod.platform_compat, "is_windows_on_arm", lambda: False) + resp = await core_mod.api_stt_config(_req()) + assert json.loads(resp.body)["faster_unsupported"] is False + # ── STT install endpoint ──────────────────────────────────────────────── @@ -863,6 +883,130 @@ async def _spawn(*_a, **_k): assert core_mod._stt_install_status["step"] == "error" assert fake_sel.log_api_access.call_args.kwargs["outcome"] == "failed" + @pytest.mark.asyncio + async def test_faster_is_refused_on_windows_on_arm(self, monkeypatch, fake_sel, stt_status): + """No CTranslate2 wheel exists for win-arm64 and there is no sdist either, so + pip fails while RESOLVING — naming a package the user never asked for. Refuse + up front with the alternatives instead of letting every press repeat it.""" + core_mod._stt_install_status = {"step": "idle", "detail": "", "error": ""} + path = config_path() + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps({"stt": {"provider": "faster"}}) + "\n", + encoding="utf-8", + newline="\n", + ) + monkeypatch.setattr(core_mod.platform_compat, "is_windows_on_arm", lambda: True) + resp = await core_mod.api_stt_install(_req()) + assert resp.status == 400 + body = json.loads(resp.body) + assert body["code"] == "stt_unsupported_platform" + # The alternatives are the whole point — a bare refusal leaves the user stuck. + assert "whisper" in body["error"] and "transcribe" in body["error"] + # Must not strand the status machine in `starting`, or the 409 gate deadlocks. + assert core_mod._stt_install_status["step"] == "idle" + assert fake_sel.log_api_access.call_args.kwargs["outcome"] == "denied" + + @pytest.mark.asyncio + async def test_windows_arm_refusal_never_spawns_the_shell( + self, monkeypatch, fake_sel, stt_status + ): + """The refusal has to be SERVER-SIDE, not a guard inside the generated script. + + ``api_stt_install`` launches the script through ``bash -c``, which does not + exist on a stock native-Windows gateway — the run would die with + FileNotFoundError before executing a line. An in-script guard would therefore + be unreachable on precisely the platform it exists for, so assert no spawn is + even attempted. + """ + core_mod._stt_install_status = {"step": "idle", "detail": "", "error": ""} + path = config_path() + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps({"stt": {"provider": "faster"}}) + "\n", + encoding="utf-8", + newline="\n", + ) + monkeypatch.setattr(core_mod.platform_compat, "is_windows_on_arm", lambda: True) + spawned = [] + + async def _spawn(*a, **_k): + spawned.append(a) + return _proc([b"Done.\n"]) + + monkeypatch.setattr(asyncio, "create_subprocess_exec", _spawn) + resp = await core_mod.api_stt_install(_req()) + assert resp.status == 400 + assert spawned == [] + + @pytest.mark.asyncio + async def test_platform_refusal_precedes_the_pip_channel_check( + self, monkeypatch, fake_sel, stt_status + ): + """Ordering is load-bearing: on win-arm a healthy pip channel would pass the + sibling gate and let the install proceed to a guaranteed failure. The + platform verdict must win, and its message is also the more actionable one.""" + core_mod._stt_install_status = {"step": "idle", "detail": "", "error": ""} + path = config_path() + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps({"stt": {"provider": "faster"}}) + "\n", + encoding="utf-8", + newline="\n", + ) + monkeypatch.setattr(core_mod.platform_compat, "is_windows_on_arm", lambda: True) + # A working channel: without correct ordering this would fall through. + monkeypatch.setattr(core_mod, "_pip_install_channel_available", lambda: True) + resp = await core_mod.api_stt_install(_req()) + assert json.loads(resp.body)["code"] == "stt_unsupported_platform" + + @pytest.mark.asyncio + async def test_other_providers_are_unaffected_on_windows_on_arm( + self, monkeypatch, fake_sel, stt_status + ): + """The gate is scoped to `faster`. `whisper` has no CTranslate2 dependency, so + refusing it on win-arm would break a provider that works.""" + core_mod._stt_install_status = {"step": "idle", "detail": "", "error": ""} + path = config_path() + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps({"stt": {"provider": "whisper"}}) + "\n", + encoding="utf-8", + newline="\n", + ) + monkeypatch.setattr(core_mod.platform_compat, "is_windows_on_arm", lambda: True) + + async def _spawn(*_a, **_k): + return _proc([b"Done.\n"]) + + monkeypatch.setattr(asyncio, "create_subprocess_exec", _spawn) + resp = await core_mod.api_stt_install(_req()) + assert resp.status == 200 + + @pytest.mark.asyncio + async def test_faster_install_proceeds_on_a_supported_platform( + self, monkeypatch, fake_sel, stt_status + ): + core_mod._stt_install_status = {"step": "idle", "detail": "", "error": ""} + path = config_path() + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + json.dumps({"stt": {"provider": "faster"}}) + "\n", + encoding="utf-8", + newline="\n", + ) + monkeypatch.setattr(core_mod.platform_compat, "is_windows_on_arm", lambda: False) + monkeypatch.setattr(core_mod, "_pip_install_channel_available", lambda: True) + monkeypatch.setattr(core_mod, "_faster_whisper_model", lambda: object()) + + async def _spawn(*_a, **_k): + return _proc([b"Installing faster-whisper\n", b"Done.\n"]) + + monkeypatch.setattr(asyncio, "create_subprocess_exec", _spawn) + resp = await core_mod.api_stt_install(_req()) + assert resp.status == 200 + assert core_mod._stt_install_status["step"] == "done" + @pytest.mark.asyncio async def test_install_timeout_kills_the_child(self, monkeypatch, fake_sel, stt_status) -> None: core_mod._stt_install_status = {"step": "idle", "detail": "", "error": ""} diff --git a/test/test_platform_compat.py b/test/test_platform_compat.py index 09e1362a11e..19eff85a0c6 100644 --- a/test/test_platform_compat.py +++ b/test/test_platform_compat.py @@ -140,6 +140,79 @@ def test_reexec_successor_survives_hostile_parent_encoding(self, tmp_path): assert "👻 restarted".encode() in result.stdout +class TestWindowsOnArm: + """``is_windows_on_arm`` answers "will pip accept a win_amd64 wheel here?". + + Callers use it to refuse a package that publishes no win-arm64 wheel, so the + predicate has to be a property of the running PROCESS rather than of the host + CPU — the two disagree under Windows' x86-64 emulation, and only the process + answer matches what pip does. + """ + + def test_true_for_a_native_arm64_interpreter(self, monkeypatch): + monkeypatch.setattr(pc, "IS_WINDOWS", True) + monkeypatch.setattr(pc.platform, "machine", lambda: "ARM64") + assert pc.is_windows_on_arm() is True + + def test_accepts_the_aarch64_spelling(self, monkeypatch): + # Reaches Windows through cross-built and MSYS/Cygwin interpreters. + monkeypatch.setattr(pc, "IS_WINDOWS", True) + monkeypatch.setattr(pc.platform, "machine", lambda: "aarch64") + assert pc.is_windows_on_arm() is True + + def test_case_is_irrelevant(self, monkeypatch): + monkeypatch.setattr(pc, "IS_WINDOWS", True) + monkeypatch.setattr(pc.platform, "machine", lambda: "aRm64") + assert pc.is_windows_on_arm() is True + + def test_false_for_an_emulated_x86_64_interpreter(self, monkeypatch): + """The case a host-architecture probe would get WRONG. + + Windows on ARM runs x86-64 processes under emulation, and such an + interpreter reports AMD64 and installs win_amd64 wheels perfectly well. + Reporting it as ARM would refuse a package that works. + """ + monkeypatch.setattr(pc, "IS_WINDOWS", True) + monkeypatch.setattr(pc.platform, "machine", lambda: "AMD64") + assert pc.is_windows_on_arm() is False + + def test_false_on_apple_silicon(self, monkeypatch): + # arm64 alone must not trip it: macOS and Linux both publish arm64 wheels + # for the packages this gate exists to refuse on Windows. + monkeypatch.setattr(pc, "IS_WINDOWS", False) + monkeypatch.setattr(pc.platform, "machine", lambda: "arm64") + assert pc.is_windows_on_arm() is False + + def test_does_not_consult_machine_off_windows(self, monkeypatch): + """Short-circuits on the platform constant. + + Keeps the predicate loop-safe for the dashboard's STT config GET, which + calls it inline rather than from the threaded probe block. + """ + monkeypatch.setattr(pc, "IS_WINDOWS", False) + calls = [] + + def _machine(): + calls.append(1) + return "arm64" + + monkeypatch.setattr(pc.platform, "machine", _machine) + assert pc.is_windows_on_arm() is False + assert calls == [] + + def test_uses_the_modules_own_windows_predicate(self, monkeypatch): + """Keyed off IS_WINDOWS, not a second platform.system() call. + + Two Windows predicates in one module can drift; this pins that there is + one. Flipping only IS_WINDOWS must flip the answer. + """ + monkeypatch.setattr(pc.platform, "machine", lambda: "arm64") + monkeypatch.setattr(pc, "IS_WINDOWS", True) + assert pc.is_windows_on_arm() is True + monkeypatch.setattr(pc, "IS_WINDOWS", False) + assert pc.is_windows_on_arm() is False + + class TestFileLock: def test_exclusive_lock_round_trips(self, tmp_path): # The lock must acquire + release cleanly and run the body, on whatever diff --git a/test/test_stt_stream.py b/test/test_stt_stream.py index 939029c4c72..10eed131652 100644 --- a/test/test_stt_stream.py +++ b/test/test_stt_stream.py @@ -1106,19 +1106,31 @@ def fake_run(*_a, **_kw): monkeypatch.setattr("subprocess.run", fake_run) assert core._is_apple_silicon() is False + # Both lists below are exact on purpose: they pin ORDER as well as membership, + # since the dashboard renders them in this sequence. They must track + # ``_VALID_STT_PROVIDERS`` in the config loader — adding a provider there + # deliberately fails these until the expectation is updated too. def test_providers_include_mlx_on_apple_silicon(self, monkeypatch): from kiro_crew import apple_speech from kiro_crew.dashboard.handlers import core monkeypatch.setattr(core, "_is_apple_silicon", lambda: True) # `apple` has its own gate (macOS 26 + Swift toolchain); pin it off here so - # this test measures only the Apple-Silicon gate. + # this test measures only the Apple-Silicon gate. `faster` has no gate — it + # is a pip extra, so it is always advertised and stays in the expectation. monkeypatch.setattr( apple_speech, "availability", lambda: apple_speech.Availability(False, "pinned off") ) # `parakeet` is gated the same way as `mlx` (Apple-Silicon-only), so both - # are present here. - assert core._stt_providers() == ["whisper", "mlx", "parakeet", "transcribe"] + # are present here. `faster` is a plain pip install with no platform gate + # in _stt_providers, so it is always advertised. + assert core._stt_providers() == [ + "whisper", + "mlx", + "parakeet", + "transcribe", + "faster", + ] def test_stt_providers_calls_is_apple_silicon_exactly_once(self, monkeypatch): """`parakeet` reuses the `mlx` gate's already-computed Apple-Silicon @@ -1153,7 +1165,7 @@ def test_providers_exclude_mlx_off_apple_silicon(self, monkeypatch): ) providers = core._stt_providers() assert "mlx" not in providers - assert providers == ["whisper", "transcribe"] + assert providers == ["whisper", "transcribe", "faster"] def test_providers_include_apple_when_supported(self, monkeypatch): """`apple` is advertised only where SpeechAnalyzer can actually run.""" @@ -1162,7 +1174,16 @@ def test_providers_include_apple_when_supported(self, monkeypatch): monkeypatch.setattr(core, "_is_apple_silicon", lambda: True) monkeypatch.setattr(apple_speech, "availability", lambda: apple_speech.Availability(True)) - assert core._stt_providers() == ["whisper", "mlx", "apple", "parakeet", "transcribe"] + # `faster` is ungated — a pip extra, not a platform capability — so it is + # present in every expectation here regardless of what is being gated. + assert core._stt_providers() == [ + "whisper", + "mlx", + "apple", + "parakeet", + "transcribe", + "faster", + ] def test_providers_exclude_apple_when_toolchain_missing(self, monkeypatch): """A host that could run the framework but has no Swift toolchain must not be diff --git a/test/test_transcribe_faster.py b/test/test_transcribe_faster.py new file mode 100644 index 00000000000..de58062febe --- /dev/null +++ b/test/test_transcribe_faster.py @@ -0,0 +1,768 @@ +"""The ``faster`` (faster-whisper) STT provider, and the hallucination filter. + +Two things under test, and they are separable: + +* The provider — dispatch, availability, and the fact that it needs neither a + subprocess nor the system ffmpeg the CLI providers depend on. +* The hallucination filter — pure text logic applied to every Whisper-family + provider. It matters because transcripts here go to agents: a hallucinated + sign-off becomes a meeting note, and a phrase repeated forty times becomes + forty note lines. + +``faster_whisper`` is not installed (it is an on-demand runtime, not a declared +extra), so the library itself is always patched. That is the same situation CI is +in, which is the point. +""" + +from __future__ import annotations + +import logging +import sys +from contextlib import contextmanager +from unittest.mock import MagicMock, patch + +import pytest + +from kiro_crew.config.loader import ( + _VALID_STT_MODELS, + _VALID_STT_PROVIDERS, + SttConfig, + _validated_stt_model, +) +from kiro_crew.dashboard.handlers.core import ( + _STT_MODEL_SIZES, + _build_stt_install_script, + _stt_prereq_commands, +) +from kiro_crew.transcribe import ( + _WHISPER_FAMILY_PROVIDERS, + _collapse_repeated_phrases, + _faster_whisper_model, + _is_boilerplate_line, + _run_faster_whisper_sync, + filter_hallucinations, + is_available, + transcribe_audio, +) + + +@contextmanager +def _library_absent(): + """Simulate faster-whisper being uninstalled. + + Patching the cached class alone is not enough since the lazy helper retries + the import — on a dev machine that happens to have the library, the retry + would succeed and the "absent" test would silently test presence. Poisoning + ``sys.modules`` makes the retry raise ImportError everywhere. + """ + with patch.dict(sys.modules, {"faster_whisper": None}): + with patch("kiro_crew.transcribe._FasterWhisperModel", None): + yield + + +@pytest.fixture(autouse=True) +def _clear_fw_model_cache(): + """Isolate the per-(model, device) instance cache between tests. + + The cache is a module global keyed on config values most tests share + (turbo/cpu), so without clearing, one test's MagicMock model leaks into the + next test's dispatch and every assertion after the first tests the cache, + not the code. + """ + from kiro_crew import transcribe + + transcribe._FW_MODEL_CACHE.clear() + yield + transcribe._FW_MODEL_CACHE.clear() + + +def _fake_model(text_segments: list[str]) -> MagicMock: + """A stand-in for ``faster_whisper.WhisperModel`` yielding *text_segments*.""" + model = MagicMock() + model.transcribe.return_value = ( + iter([MagicMock(text=t) for t in text_segments]), + MagicMock(), + ) + return model + + +# --------------------------------------------------------------------------- +# Registration +# --------------------------------------------------------------------------- + + +class TestProviderRegistration: + def test_faster_is_a_valid_provider(self): + assert "faster" in _VALID_STT_PROVIDERS + + def test_faster_is_in_the_whisper_family(self): + # Which is what subjects it to the hallucination filter. + assert "faster" in _WHISPER_FAMILY_PROVIDERS + + def test_transcribe_is_not_in_the_whisper_family(self): + # AWS Transcribe uses a different decoder and does not produce these + # artefacts, so filtering it could only ever delete real speech. + assert "transcribe" not in _WHISPER_FAMILY_PROVIDERS + + +# --------------------------------------------------------------------------- +# Model enum +# --------------------------------------------------------------------------- + + +class TestModelEnum: + def test_turbo_remains_the_default(self): + assert SttConfig().model == "turbo" + + def test_every_size_is_accepted(self): + for model in _VALID_STT_MODELS: + assert _validated_stt_model(model) == model + + @pytest.mark.parametrize("offmenu", ["tiny.en", "base.en", "small.en", "medium.en", "large-v2"]) + def test_offmenu_string_models_pass_through_with_a_warning(self, offmenu): + # openai-whisper legitimately accepts names outside the dashboard's size + # menu; a hand-edited config holding one must NOT be silently coerced to + # turbo — that would remove a real capability the old loader allowed. + assert _validated_stt_model(offmenu) == offmenu + + def test_unknown_string_passes_through_rather_than_coercing(self): + # Providers degrade safely per-recording on a bad name (logged, non-fatal), + # so the loader's job is to warn, not to rewrite the user's config. + assert _validated_stt_model("large-v9") == "large-v9" + + @pytest.mark.parametrize("bad", ["", None, 42, ["small"]]) + def test_non_string_or_empty_model_falls_back_instead_of_raising(self, bad): + # A mangled config field must not stop the Gateway from starting, and a + # non-string cannot be handed to any provider at all. + assert _validated_stt_model(bad) == "turbo" + + def test_dashboard_offers_a_size_for_every_valid_model(self): + # `_STT_MODEL_SIZES` is the dashboard's PUT allowlist, so a model the config + # loader accepts but this dict omits would be silently rejected by the API. + assert set(_STT_MODEL_SIZES) == set(_VALID_STT_MODELS) + + def test_every_size_is_human_readable(self): + for model, size in _STT_MODEL_SIZES.items(): + assert size.startswith("~"), model + assert size.endswith(("MB", "GB")), model + + +# --------------------------------------------------------------------------- +# Availability +# --------------------------------------------------------------------------- + + +class TestIsAvailable: + def test_available_when_the_library_imports(self): + cfg = SttConfig(enabled=True, provider="faster") + with patch("kiro_crew.transcribe._FasterWhisperModel", MagicMock()): + assert is_available(cfg) is True + + def test_unavailable_when_the_library_is_missing(self): + cfg = SttConfig(enabled=True, provider="faster") + with _library_absent(): + assert is_available(cfg) is False + + def test_does_not_probe_for_ffmpeg(self): + # faster-whisper decodes in-process through PyAV's bundled FFmpeg, so the + # system binary is irrelevant. Probing for it would make availability depend + # on something this provider never calls. + cfg = SttConfig(enabled=True, provider="faster") + with patch("kiro_crew.transcribe._FasterWhisperModel", MagicMock()): + with patch("kiro_crew.transcribe.ensure_ffmpeg_in_path") as ensure: + assert is_available(cfg) is True + ensure.assert_not_called() + + def test_disabled_beats_available(self): + cfg = SttConfig(enabled=False, provider="faster") + with patch("kiro_crew.transcribe._FasterWhisperModel", MagicMock()): + assert is_available(cfg) is False + + def test_available_from_disk_before_anything_has_imported_it(self): + # The regression this guards: a plain restart of an already-installed + # gateway begins with an empty cache. While availability was a cached read, + # Settings reported faster-whisper missing until something happened to run a + # transcription. Locating the library on the import path answers correctly + # from the first request. + cfg = SttConfig(enabled=True, provider="faster") + with patch("kiro_crew.transcribe._FasterWhisperModel", None): + with patch("importlib.util.find_spec", return_value=MagicMock()) as find: + assert is_available(cfg) is True + find.assert_called_once_with("faster_whisper") + + def test_never_imports_the_library(self): + # This function runs on the event loop (config GET, Slack voice) and + # importing faster_whisper links CTranslate2's native extension + # synchronously, stalling every gateway task. The import must therefore be + # unreachable from here whatever the cache holds — asserting "answers + # correctly" is not enough, since the wrong implementation also answers + # correctly and merely blocks the loop while doing it. + cfg = SttConfig(enabled=True, provider="faster") + + def _fail_on_import(*_args, **_kwargs): + pytest.fail("is_available imported faster_whisper on the event loop") + + with patch("kiro_crew.transcribe._FasterWhisperModel", None): + with patch("importlib.util.find_spec", return_value=MagicMock()): + with patch("importlib.import_module", _fail_on_import): + assert is_available(cfg) is True + + def test_a_broken_install_reports_unavailable_rather_than_raising(self): + # A half-removed install can leave the name in sys.modules with no spec, so + # find_spec raises instead of answering. This runs on the loop serving + # /api/config/stt, where an exception is a 500 rather than a verdict. + cfg = SttConfig(enabled=True, provider="faster") + with patch("kiro_crew.transcribe._FasterWhisperModel", None): + with patch("importlib.util.find_spec", side_effect=ValueError("no spec")): + assert is_available(cfg) is False + + +class TestLazyImportRetry: + def test_helper_retries_the_import_after_an_on_demand_install(self): + # The Settings install lands the library in this interpreter AFTER module + # load cached None. Without a retry, the button reports "Done" while + # availability stays False until a gateway restart. + sentinel = MagicMock() + fake_module = MagicMock(WhisperModel=sentinel) + with patch("kiro_crew.transcribe._FasterWhisperModel", None): + with patch.dict(sys.modules, {"faster_whisper": fake_module}): + assert _faster_whisper_model() is sentinel + + def test_helper_returns_none_while_the_library_is_absent(self): + with _library_absent(): + assert _faster_whisper_model() is None + + def test_helper_prefers_the_cached_class(self): + cached = MagicMock() + with patch("kiro_crew.transcribe._FasterWhisperModel", cached): + assert _faster_whisper_model() is cached + + +class TestModelMemoization: + def test_same_model_and_device_constructs_once(self): + # Constructing a WhisperModel re-loads and re-quantizes the weights; + # concurrent recordings each holding a copy compounds to RAM exhaustion. + model_cls = MagicMock(return_value=_fake_model(["one"])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") + _run_faster_whisper_sync("/tmp/b.wav", "turbo", "cpu") + assert model_cls.call_count == 1 + + def test_distinct_keys_get_distinct_instances(self): + model_cls = MagicMock(side_effect=lambda *a, **k: _fake_model(["x"])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") + _run_faster_whisper_sync("/tmp/a.wav", "small", "cpu") + assert model_cls.call_count == 2 + + def test_switching_models_evicts_the_previous_instance(self): + # SINGLE-SLOT on purpose: keeping every size ever selected resident + # would accumulate multi-GB native models and OOM a small gateway host. + from kiro_crew import transcribe + + model_cls = MagicMock(side_effect=lambda *a, **k: _fake_model(["x"])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") + _run_faster_whisper_sync("/tmp/a.wav", "large-v3", "cpu") + assert list(transcribe._FW_MODEL_CACHE) == [("large-v3", "cpu")] + # Switching BACK constructs again — correctness over reload cost. + _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") + assert list(transcribe._FW_MODEL_CACHE) == [("turbo", "cpu")] + assert model_cls.call_count == 3 + + def test_a_failed_construction_is_not_cached(self): + # One bad load (e.g. interrupted download) must not poison every later + # recording with a cached broken instance or a cached None. + model_cls = MagicMock(side_effect=RuntimeError("load failed")) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + assert _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") is None + ok_cls = MagicMock(return_value=_fake_model(["recovered"])) + with patch("kiro_crew.transcribe._FasterWhisperModel", ok_cls): + assert _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") == "recovered" + + +# --------------------------------------------------------------------------- +# Inference +# --------------------------------------------------------------------------- + + +class TestRunFasterWhisperSync: + def test_joins_segment_text(self): + model_cls = MagicMock(return_value=_fake_model([" Hello ", "world. ", " "])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + assert _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") == "Hello world." + + def test_quantises_to_int8_on_the_configured_device(self): + # int8 is what makes CPU inference fast enough to be usable on a + # meeting-length recording. + model_cls = MagicMock(return_value=_fake_model(["hi"])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + _run_faster_whisper_sync("/tmp/a.wav", "small", "cuda") + model_cls.assert_called_once_with("small", device="cuda", compute_type="int8") + + def test_empty_output_is_none_not_empty_string(self): + model_cls = MagicMock(return_value=_fake_model([" ", ""])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + assert _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") is None + + def test_returns_none_when_the_library_is_missing(self): + with _library_absent(): + assert _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") is None + + def test_an_inference_failure_is_logged_not_raised(self): + # Same contract as every other provider: one bad recording must not take a + # caller down. + model_cls = MagicMock(side_effect=RuntimeError("model load failed")) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + assert _run_faster_whisper_sync("/tmp/a.wav", "turbo", "cpu") is None + + +class TestDispatch: + @pytest.mark.asyncio + async def test_faster_provider_is_dispatched(self, tmp_path): + audio = tmp_path / "a.wav" + audio.write_bytes(b"RIFF") + cfg = SttConfig(enabled=True, provider="faster", model="small", device="cpu") + model_cls = MagicMock(return_value=_fake_model(["Real speech here."])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + result = await transcribe_audio(str(audio), cfg) + assert result == "Real speech here." + + @pytest.mark.asyncio + async def test_does_not_shell_out_or_need_ffmpeg(self, tmp_path): + # The reason this provider is worth having: no binary discovery at all. + audio = tmp_path / "a.wav" + audio.write_bytes(b"RIFF") + cfg = SttConfig(enabled=True, provider="faster") + model_cls = MagicMock(return_value=_fake_model(["ok"])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + with patch("kiro_crew.transcribe.ensure_ffmpeg_in_path") as ensure: + with patch("kiro_crew.transcribe._run_whisper_cli") as cli: + assert await transcribe_audio(str(audio), cfg) == "ok" + ensure.assert_not_called() + cli.assert_not_called() + + @pytest.mark.asyncio + async def test_missing_library_returns_none(self, tmp_path): + audio = tmp_path / "a.wav" + audio.write_bytes(b"RIFF") + cfg = SttConfig(enabled=True, provider="faster") + with _library_absent(): + assert await transcribe_audio(str(audio), cfg) is None + + @pytest.mark.asyncio + async def test_hallucinated_output_becomes_none(self, tmp_path): + # The whole point of the filter being inside transcribe_audio: a recording of + # silence must come back as "no transcript", not as boilerplate for an agent + # to write into the meeting notes. + audio = tmp_path / "a.wav" + audio.write_bytes(b"RIFF") + cfg = SttConfig(enabled=True, provider="faster") + model_cls = MagicMock(return_value=_fake_model(["Subtitles by Amara.org."])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + assert await transcribe_audio(str(audio), cfg) is None + + +# --------------------------------------------------------------------------- +# Hallucination filter +# --------------------------------------------------------------------------- + + +class TestBoilerplateDetection: + @pytest.mark.parametrize( + "line", + [ + "Subtitles by", + "subtitles by amara.org.", + " Subtitled by! ", + "Captioned by.", + "Subtitles by Amara.org", + ], + ) + def test_detects_boilerplate(self, line): + assert _is_boilerplate_line(line) is True + + @pytest.mark.parametrize( + "line", + [ + "", + " ", + "Let's ship the recording change on Friday.", + "The copyright review is blocked on legal.", + "I said goodbye to the old design.", + # A sentence CONTAINING a boilerplate phrase is not boilerplate: these + # are real speech on the normal path, and deleting them is silent + # content loss (the blocking finding this rule exists to prevent). + "Thanks for joining today's standup, let's start with Priya.", + "I really do thank you for watching over the rollout last week.", + "The transcript is available in the shared drive for everyone.", + "See you next time we meet in Boston, bring the roadmap.", + # Even ONE extra word must spare the sentence — "Thanks for joining, + # everyone." is a normal meeting opener, not an artefact. + "Thanks for joining, everyone.", + "Thanks for watching this, team.", + # Ordinary-speech phrases were REMOVED from the list entirely: a + # dictated farewell or rights notice is plausible real speech even + # as a complete utterance, so it must never be filtered. + "Goodbye.", + "goodbye", + "Copyright", + "All rights reserved.", + "Thanks for listening.", + "Thanks for joining.", + "See you next time!", + "The transcript is available.", + # Sign-offs and subscribe CTAs were REMOVED from the list: each is a + # sentence someone recording a demo or dictating a video script says + # out loud, and a whole-transcript match discarded the recording. + "Thank you for watching.", + "Thanks for watching!", + "Please subscribe.", + "Like and subscribe.", + "Please like and subscribe.", + "Don't forget to subscribe.", + "Hit the bell.", + "Click the subscribe button.", + "See you in the next video.", + ], + ) + def test_keeps_real_speech(self, line): + assert _is_boilerplate_line(line) is False + + def test_all_phrases_require_a_whole_line_match(self): + # Substring or word-count-proximity matching deletes real sentences that + # merely mention (or lightly extend) a phrase — the cases above. + assert _is_boilerplate_line("Transcribed by") is True + assert _is_boilerplate_line("We kept transcribed by in the caption doc") is False + + def test_every_listed_phrase_is_a_caption_artefact(self): + # LIST DISCIPLINE, tightened: an entry must be attribution text a caption + # track carries ABOUT ITSELF, not merely "video-flavoured". The looser + # caption-domain rule is what admitted "thank you for watching" and + # "hit the bell" — sentences a human genuinely records, which the + # whole-transcript path then deleted. Required direction: + import re as _re + + attribution_markers = _re.compile(r"subtitle|caption|transcri|translat|amara|mooji") + from kiro_crew.transcribe import _WHISPER_BOILERPLATE as phrases + + for phrase in phrases: + assert attribution_markers.search(phrase), ( + f"'{phrase}' is not caption self-attribution — it may be real" + " dictated speech, so it must not be on the filter list" + ) + + def test_no_listed_phrase_is_a_spoken_sign_off(self): + # Forbidden direction, and the half that actually holds the line: the + # required-marker test above passes for "subscribe to my subtitles too", + # so the vocabulary of speech a presenter utters is banned outright. This + # fails if a future edit re-adds any entry of the deleted class. + import re as _re + + speech_markers = _re.compile(r"watch|subscribe|bell|video|thank|see you|like and") + from kiro_crew.transcribe import _WHISPER_BOILERPLATE as phrases + + for phrase in phrases: + assert not speech_markers.search(phrase), ( + f"'{phrase}' reads as something a speaker says on a recording;" + " filtering it can delete the only words a transcript had" + ) + + def test_known_artefact_variants_are_listed_as_full_phrases(self): + # "Subtitles by Amara.org" is the canonical artefact shape; it matches by + # being IN the phrase list, not by loosening the match rule. + assert _is_boilerplate_line("Subtitles by Amara.org") is True + assert _is_boilerplate_line("Subtitles by the Amara.org community") is True + + +class TestCollapseRepeatedPhrases: + def test_collapses_a_long_run_to_one(self): + text = " ".join(["Thank you."] * 12) + assert _collapse_repeated_phrases(text) == "Thank you." + + def test_leaves_a_short_run_alone(self): + # Real emphasis reaches well past two — "No. No. No." is ordinary + # insistence, and even five repeats is plausible counted speech. Only + # dozens-long runs are the Whisper artefact. + for n in range(2, 6): + text = " ".join(["No."] * n) + assert _collapse_repeated_phrases(text) == text, n + + def test_only_consecutive_runs_collapse(self): + # The same sentence recurring later in a meeting is ordinary speech. + text = "Okay. Next item. Okay." + assert _collapse_repeated_phrases(text) == "Okay. Next item. Okay." + + def test_preserves_surrounding_speech(self): + run = " ".join(["Uh huh."] * 8) + text = f"We start now. {run} Then we ship." + assert _collapse_repeated_phrases(text) == "We start now. Uh huh. Then we ship." + + def test_single_sentence_is_untouched(self): + assert _collapse_repeated_phrases("Just one sentence") == "Just one sentence" + + +class TestFilterHallucinations: + def test_empty_input_is_returned_as_is(self): + assert filter_hallucinations("") == "" + + def test_real_speech_survives_intact(self): + text = "We agreed to ship on Friday. Priya owns the rollout." + assert filter_hallucinations(text) == text + + def test_a_fully_hallucinated_transcript_becomes_empty(self): + # Which the caller turns into None. An empty string is the honest answer for + # a recording of silence. + assert filter_hallucinations("Subtitles by Amara.org. Transcribed by.") == "" + + def test_strips_boilerplate_but_keeps_the_meeting(self): + text = "Priya owns the rollout. Subtitles by Amara.org. We ship Friday." + assert filter_hallucinations(text) == "Priya owns the rollout. We ship Friday." + + def test_a_dictated_sign_off_survives_whole(self): + """The GPT 5.6 blocking finding, pinned. + + Each of these is a complete sentence a human records — a demo outro, a + dictated video script — and each was previously deleted by an exact + whole-sentence match. When it was the entire transcript the filter + returned "", which ``transcribe_audio`` turns into ``None``: the only + words the recording held, gone, with a log line as the sole trace. + """ + for text in ( + "Thank you for watching.", + "Thanks for watching!", + "Please subscribe.", + "Don't forget to subscribe.", + "Hit the bell.", + "See you in the next video.", + ): + assert filter_hallucinations(text) == text, text + + def test_handles_both_artefacts_together(self): + run = " ".join(["Okay."] * 10) + text = f"{run} Ship it. Transcribed by." + assert filter_hallucinations(text) == "Okay. Ship it." + + +class TestFilterHallucinationsVisibility: + """The filter is the one step that can delete words the speaker said. + + A silent deletion is indistinguishable from the model never having heard the + words, so every removal has to leave a trace an operator can find after the + fact. These tests pin what the trace says, not merely that one exists. + """ + + def test_dropped_boilerplate_is_named_in_the_log(self, caplog): + with caplog.at_level(logging.INFO, logger="kiro_crew.transcribe"): + filter_hallucinations("Priya owns the rollout. Subtitles by Amara.org.") + assert "dropped 1 boilerplate line(s)" in caplog.text + assert "Subtitles by Amara.org." in caplog.text + + def test_collapsed_repetitions_are_counted_but_not_quoted(self, caplog): + # A repeated sentence is ordinary speech; its text belongs in the + # transcript, not in the log, so only the count is recorded. + with caplog.at_level(logging.INFO, logger="kiro_crew.transcribe"): + filter_hallucinations(" ".join(["Ship the thing."] * 8)) + assert "collapsed 7 repeated sentence(s)" in caplog.text + assert "Ship the thing" not in caplog.text + + def test_discarding_the_whole_transcript_warns(self, caplog): + # The caller turns "" into None and the recording is gone with no other + # trace, so this case is a warning rather than an info line. + with caplog.at_level(logging.INFO, logger="kiro_crew.transcribe"): + assert filter_hallucinations("Subtitles by Amara.org. Transcribed by.") == "" + assert "discarded the entire transcript" in caplog.text + assert any(r.levelno == logging.WARNING for r in caplog.records) + + def test_an_untouched_transcript_logs_nothing(self, caplog): + # Every recording passes through here. A line per transcription would bury + # the removals this logging exists to surface. + with caplog.at_level(logging.INFO, logger="kiro_crew.transcribe"): + filter_hallucinations("We agreed to ship on Friday.") + assert caplog.records == [] + + +# --------------------------------------------------------------------------- +# Install path +# --------------------------------------------------------------------------- + + +class TestInstallScript: + def test_installs_into_the_gateways_own_interpreter(self): + # The library is imported IN-PROCESS by kiro_crew.transcribe, so the one + # environment that matters is sys.executable's. A system python's + # user-site would be invisible here — and inside a venv pip refuses + # `--user` outright — so the script must target the gateway interpreter + # and must not pass `--user`. + script = _build_stt_install_script("faster") + assert "pip install -q faster-whisper" in script + assert "--user" not in script + assert sys.executable in script + + def test_does_not_probe_for_a_system_python(self): + # The $PY probe belongs to the CLI providers, whose binary any python can + # own. Probing here risks installing into an interpreter the gateway + # never imports from. + assert "for py in" not in _build_stt_install_script("faster") + + def test_does_not_install_ffmpeg(self): + # It is not needed, and installing it would make the button slower and more + # failure-prone for no benefit. + script = _build_stt_install_script("faster") + assert "brew install ffmpeg" not in script + assert "openai-whisper" not in script + + def test_documents_the_windows_arm_gap(self): + # CTranslate2 publishes no wheel there, so the install cannot succeed and the + # script should say why rather than fail opaquely. + assert "Windows on ARM" in _build_stt_install_script("faster") + + def test_includes_the_path_prelude(self): + # A brew-installed python3 is common on macOS, and the gateway's inherited + # PATH does not contain the Homebrew prefix. + assert "brew shellenv" in _build_stt_install_script("faster") + + def test_emits_the_progress_line_the_status_parser_matches(self): + # `_stt_install_status` keys the `installing_faster` step off this exact text. + assert "Installing faster-whisper" in _build_stt_install_script("faster") + + def test_requires_no_manual_prerequisites(self): + assert _stt_prereq_commands("faster") == [] + + def test_a_non_importable_install_fails_the_script(self): + # pip can report success while the package is unusable — a CTranslate2 + # wheel whose native extension will not load is the common case. The + # verification import therefore has to be able to fail the script: while + # its failure was swallowed into a "check install" note, the script exited + # 0 and the caller reported the provider ready. + script = _build_stt_install_script("faster") + assert "check install" not in script + assert "is not importable" in script + assert "exit 1" in script.split("Installing faster-whisper")[1] + + +# --------------------------------------------------------------------------- +# Inference bulkhead + timeout +# --------------------------------------------------------------------------- + + +class TestInferenceExecutor: + """Inference runs on its OWN pool, not the PTY-teardown one. + + A started ``run_in_executor`` future cannot be cancelled, so a wedged model load + (or a first-run multi-GB weight download inside the library's constructor) holds + its worker until the process exits. On ``subprocess_executor`` that would consume + one of the eight workers whose whole purpose is absorbing a teardown storm — the + recovery path would be starved by the thing it recovers from. + """ + + def test_transcribe_binds_the_stt_pool(self): + from kiro_crew import executors, transcribe + + assert transcribe.stt_executor is executors.stt_executor + + def test_stt_pool_is_distinct_from_the_teardown_pool(self): + from kiro_crew import executors + + assert executors.stt_executor() is not executors.subprocess_executor() + + def test_stt_pool_threads_are_identifiable_in_a_stack_dump(self): + from kiro_crew import executors + + assert executors.stt_executor()._thread_name_prefix == "mc-stt" + + def test_pool_is_bounded_because_each_worker_holds_a_model(self): + # The worker count is a MEMORY ceiling, not just a CPU one: every in-flight + # call keeps a fully quantised model resident (up to ~GBs for large-v3). + from kiro_crew import executors + + assert executors.stt_executor()._max_workers == 2 + + @pytest.mark.asyncio + async def test_inference_is_submitted_to_the_stt_pool(self, tmp_path): + from concurrent.futures import ThreadPoolExecutor + + audio = tmp_path / "a.wav" + audio.write_bytes(b"RIFF") + cfg = SttConfig(enabled=True, provider="faster") + pool = ThreadPoolExecutor(max_workers=1, thread_name_prefix="probe-stt") + try: + model_cls = MagicMock(return_value=_fake_model(["ok"])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + with patch("kiro_crew.transcribe.stt_executor", return_value=pool) as chosen: + assert await transcribe_audio(str(audio), cfg) == "ok" + chosen.assert_called_once() + finally: + pool.shutdown(wait=True) + + +class TestInferenceTimeout: + """``stt.timeout_secs`` bounds the faster path like it bounds the CLI providers. + + Before this, the future was unbounded: a wedged inference left the dictation + request hanging with no ceiling at all. + """ + + @pytest.mark.asyncio + async def test_a_wedged_inference_returns_none_instead_of_hanging(self, tmp_path): + import threading + + from kiro_crew import transcribe + + audio = tmp_path / "a.wav" + audio.write_bytes(b"RIFF") + cfg = SttConfig(enabled=True, provider="faster", timeout_secs=1) + release = threading.Event() + + def _wedged(*_a, **_k): + # Bounded so a failed assertion cannot leak a thread for the whole run; + # the test releases it explicitly below. + release.wait(timeout=30) + return "arrived too late" + + try: + with patch("kiro_crew.transcribe._run_faster_whisper_sync", _wedged): + assert await transcribe.transcribe_audio(str(audio), cfg) is None + finally: + release.set() + + @pytest.mark.asyncio + async def test_the_timeout_is_logged_as_releasing_the_caller_only(self, tmp_path, caplog): + """The log line must not imply the work was cancelled. + + ``asyncio.wait_for`` cannot interrupt a running thread, so the inference (or + the download it is stuck in) continues and its worker stays occupied. An + operator reading "timed out" would otherwise assume the slot was freed. + """ + import threading + + from kiro_crew import transcribe + + audio = tmp_path / "a.wav" + audio.write_bytes(b"RIFF") + cfg = SttConfig(enabled=True, provider="faster", timeout_secs=1) + release = threading.Event() + + def _wedged(*_a, **_k): + release.wait(timeout=30) + return None + + try: + with caplog.at_level("ERROR", logger="kiro_crew.transcribe"): + with patch("kiro_crew.transcribe._run_faster_whisper_sync", _wedged): + await transcribe.transcribe_audio(str(audio), cfg) + finally: + release.set() + assert "timed out" in caplog.text + assert "cannot be cancelled" in caplog.text + + @pytest.mark.asyncio + async def test_a_prompt_transcription_is_unaffected(self, tmp_path): + # The bound must not clip normal work: the same generous default every other + # provider uses applies here. + audio = tmp_path / "a.wav" + audio.write_bytes(b"RIFF") + cfg = SttConfig(enabled=True, provider="faster") + model_cls = MagicMock(return_value=_fake_model(["Real speech here."])) + with patch("kiro_crew.transcribe._FasterWhisperModel", model_cls): + assert await transcribe_audio(str(audio), cfg) == "Real speech here." diff --git a/website/scripts/capture-stt-faster.mjs b/website/scripts/capture-stt-faster.mjs new file mode 100644 index 00000000000..6d8a205e632 --- /dev/null +++ b/website/scripts/capture-stt-faster.mjs @@ -0,0 +1,184 @@ +/** + * Screenshot harness for the `faster` (faster-whisper) STT provider UI (#2192). + * + * Runs the REAL built SPA (website/dist) behind the in-process static server, + * answering every /api/** call from fixtures via Playwright route interception — + * no gateway, no token. The client code under test is unmodified, so + * Settings → Voice → Speech-to-Text renders exactly as in production. + * + * ## What the shots prove + * + * 1. `not-installed`: choosing the new `faster` provider shows its localized + * dropdown label, the MODEL picker (previously gated on `whisper` alone — + * the PR's WHISPER_MODEL_PROVIDERS fix), the "Install faster-whisper" + * button and its "no separate ffmpeg install" blurb. + * 2. `installing`: the progress bar and the `step_installing_faster` label the + * status parser drives off the script's "Installing faster-whisper..." line. + * 3. `ready`: the available state once the library imports. + * + * Each scene ASSERTS the strings programmatically before shooting, so a broken + * render fails the harness loudly instead of producing a misleading frame. + * + * Usage: node scripts/capture-stt-faster.mjs [outDir] + */ +import { chromium } from 'playwright' +import { mkdirSync } from 'node:fs' +import { serveDist } from './lib/serve-dist.mjs' +import { json, handleBootRoute } from './lib/boot-api.mjs' + +const OUT = process.argv[2] || '../temp-screenshots/stt-faster-whisper' +const PROJECT = '/home/user/workspace/KiroCrew' + +mkdirSync(OUT, { recursive: true }) + +/** Mutated per scene: what /api/config/stt returns. */ +const stt = { + enabled: true, + provider: 'faster', + model: 'turbo', + mlx_model: '', + available: false, + streaming: false, + endpointing: false, + dictation_panel: false, + transcribe_region: 'us-east-1', + transcribe_profile: '', + language_code: 'en-US', + models: { + tiny: '~75 MB', base: '~145 MB', small: '~484 MB', + medium: '~1.5 GB', 'large-v3': '~3.1 GB', turbo: '~1.6 GB', + }, + mlx_models: {}, + providers: ['whisper', 'mlx', 'transcribe', 'faster'], + streaming_providers: ['transcribe', 'apple'], + language_codes: ['en-US', 'zh-CN', 'de-DE'], + install_step: 'idle', + install_detail: '', + install_error: '', + prereqs: [], + transcribe_unsupported: false, + bundled_interpreter: false, + ffmpeg_missing: false, +} + +const scene = { theme: 'dark' } + +async function main() { + const { srv, base } = await serveDist() + const browser = await chromium.launch() + const context = await browser.newContext({ + viewport: { width: 1760, height: 1400 }, + // Settings rows are 12–13px type; a 1x shot renders soft on GitHub. + deviceScaleFactor: 2, + }) + const page = await context.newPage() + + await page.routeWebSocket(/\/api\/ws/, () => {}) + + await page.route('**/api/**', async route => { + const path = new URL(route.request().url()).pathname + // Scene-specific routes first; everything else is the shared boot fixture. + if (path === '/api/config/stt') return json(route, stt) + if (path === '/api/config/kirocrew') { + return json(route, { + agent: { model: 'claude-opus-4.8', reasoning_effort: 'high' }, + session: { autocompact_pct: 90 }, + dashboard: { user_role: '', user_technical_level: '' }, + }) + } + if (path === '/api/chat/slots') return json(route, []) + return handleBootRoute(route, path, { project: PROJECT, theme: scene.theme }) + }) + + page.on('pageerror', err => console.log('PAGEERROR:', String(err).slice(0, 300))) + page.on('console', msg => { if (msg.type() === 'error') console.log('CONSOLE:', msg.text().slice(0, 300)) }) + + async function load(theme = 'dark') { + scene.theme = theme + await page.addInitScript(s => { + localStorage.clear() + localStorage.setItem('mc-theme', s.theme) + localStorage.setItem('mc-onboarded', '1') + }, scene) + await page.goto(base + '/settings?tab=voice', { waitUntil: 'domcontentloaded' }) + await page.waitForTimeout(2600) + } + + /** Must-see strings per scene; a miss fails the harness before any shot. */ + async function mustSee(...texts) { + for (const t of texts) { + const n = await page.getByText(t, { exact: false }).count() + if (!n) throw new Error(`ASSERT FAILED: "${t}" not rendered`) + } + } + + /** Crop from the Provider select down through the scene's distinguishing + * element (install button / progress label / ready row). Anchoring only on + * the provider label once produced two byte-identical "different" scenes — + * the install block sat below the fixed-height window — so the crop is now + * the UNION of the anchor and the element that makes the scene the scene. */ + async function sttCard(name, sceneText) { + const anchor = page.getByText('faster-whisper (local — no separate ffmpeg install)').first() + const marker = page.getByText(sceneText, { exact: false }).first() + const a = await anchor.boundingBox() + const m = await marker.boundingBox() + if (!a) throw new Error('ASSERT FAILED: provider label has no box') + if (!m) throw new Error(`ASSERT FAILED: scene marker "${sceneText}" has no box`) + const pad = 24 + const x0 = Math.max(0, Math.min(a.x, m.x) - 340) + const y0 = Math.max(0, Math.min(a.y, m.y) - 150) + const y1 = Math.max(a.y + a.height, m.y + m.height) + 120 + const clip = { x: x0, width: Math.min(1180, 1760 - x0), y: y0, height: Math.min(y1 - y0 + pad, 1400 - y0) } + await page.screenshot({ path: `${OUT}/${name}.png`, clip }) + console.log('wrote', `${OUT}/${name}.png`) + } + + // Scene 1 — provider selected, library not installed: dropdown label, model + // picker (WHISPER_MODEL_PROVIDERS gating), install button + blurb. + stt.provider = 'faster' + stt.available = false + stt.install_step = 'idle' + await load('dark') + await mustSee( + 'faster-whisper (local — no separate ffmpeg install)', + 'Install faster-whisper', + 'Installs faster-whisper via pip', + 'turbo (~1.6 GB)', + ) + await sttCard('01-faster-not-installed-dark', 'Installs faster-whisper via pip') + + // Scene 2 — install in flight: progress bar + step label from the exact + // "Installing faster-whisper..." line the status parser matches. + stt.install_step = 'installing_faster' + stt.install_detail = 'Installing faster-whisper...' + await load('dark') + await mustSee('Installing faster-whisper…') + await sttCard('02-faster-installing-dark', 'Installing faster-whisper…') + + // Scene 3 — ready: the library imports, model picker still visible. + stt.install_step = 'done' + stt.install_detail = '' + stt.available = true + await load('light') + await mustSee('faster-whisper (local — no separate ffmpeg install)') + await sttCard('03-faster-ready-light', 'faster-whisper (local — no separate ffmpeg install)') + + // Evidence integrity: two scenes producing the same bytes means the crop + // missed the distinguishing element and the "proof" proves nothing. + const { readFileSync } = await import('node:fs') + const { createHash } = await import('node:crypto') + const digest = f => createHash('sha256').update(readFileSync(`${OUT}/${f}.png`)).digest('hex') + const frames = ['01-faster-not-installed-dark', '02-faster-installing-dark', '03-faster-ready-light'] + const seen = new Map() + for (const f of frames) { + const d = digest(f) + if (seen.has(d)) throw new Error(`ASSERT FAILED: ${f}.png is byte-identical to ${seen.get(d)}.png`) + seen.set(d, f) + } + console.log('frames verified distinct') + + await browser.close() + srv.close() +} + +main().catch(err => { console.error(err); process.exit(1) }) diff --git a/website/src/i18n/locales/bn.json b/website/src/i18n/locales/bn.json index 6511867e8c8..6fe8c49c5b3 100644 --- a/website/src/i18n/locales/bn.json +++ b/website/src/i18n/locales/bn.json @@ -12024,8 +12024,10 @@ "failed_to_save_stt_config": "STT কনফিগ সেভ করা যায়নি", "input_device_used_to_capture_your_voice": "আপনার কণ্ঠস্বর ধারণ করতে ব্যবহৃত ইনপুট ডিভাইস", "install_failed": "ইনস্টল করা যায়নি", + "install_faster_whisper": "faster-whisper ইনস্টল করুন", "install_mlx_whisper": "MLX Whisper ইনস্টল করুন", "install_whisper": "Whisper ইনস্টল করুন", + "installs_faster_whisper_no_ffmpeg_needed": "pip দিয়ে faster-whisper ইনস্টল করে। আলাদা করে ffmpeg ইনস্টল করতে হয় না: PyAV-এর সঙ্গে দেওয়া FFmpeg দিয়ে একই প্রসেসে ডিকোড করে। Windows on ARM-এ পাওয়া যায় না।", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "pipx দিয়ে mlx-whisper ও ffmpeg ইনস্টল করে। শুধু Apple Silicon (arm64)।", "installs_openai_whisper_ffmpeg_uses_system_pytho": "openai-whisper ও ffmpeg ইনস্টল করে। সিস্টেমের python3 (≥ 3.10) ব্যবহার করে।", "language": "ভাষা", @@ -12037,6 +12039,7 @@ "not_installed": "ইনস্টল করা নেই", "provider": "প্রোভাইডার", "provider_apple": "Apple Speech (ডিভাইসে — macOS 26+)", + "provider_faster": "faster-whisper (লোকাল — আলাদা করে ffmpeg ইনস্টল লাগে না)", "provider_mlx": "Whisper MLX (লোকাল — শুধু Apple Silicon)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (লোকাল)", @@ -12048,6 +12051,8 @@ "step_done": "সম্পন্ন!", "step_error": "ব্যর্থ", "step_installing_brew": "Homebrew ইনস্টল হচ্ছে…", + "step_installing_faster": "faster-whisper ইনস্টল হচ্ছে…", + "faster_unsupported_windows_arm": "faster-whisper, Windows on ARM-এ উপলব্ধ নয় — এই প্ল্যাটফর্মের জন্য কোনো CTranslate2 হুইল নেই। পরিবর্তে whisper (লোকাল) বা AWS Transcribe প্রোভাইডার ব্যবহার করুন।", "step_installing_ffmpeg": "ffmpeg ইনস্টল হচ্ছে…", "step_installing_mlx": "mlx-whisper ইনস্টল হচ্ছে…", "step_installing_python": "Python ইনস্টল হচ্ছে…", diff --git a/website/src/i18n/locales/de.json b/website/src/i18n/locales/de.json index d57b53fa3a4..d159afa36fa 100644 --- a/website/src/i18n/locales/de.json +++ b/website/src/i18n/locales/de.json @@ -12023,8 +12023,10 @@ "failed_to_save_stt_config": "STT-Konfiguration konnte nicht gespeichert werden", "input_device_used_to_capture_your_voice": "Eingabegerät zur Aufnahme Ihrer Stimme", "install_failed": "Installation fehlgeschlagen", + "install_faster_whisper": "faster-whisper installieren", "install_mlx_whisper": "MLX Whisper installieren", "install_whisper": "Whisper installieren", + "installs_faster_whisper_no_ffmpeg_needed": "Installiert faster-whisper über pip. Keine separate ffmpeg-Installation: die Dekodierung läuft prozessintern über das in PyAV mitgelieferte FFmpeg. Nicht verfügbar unter Windows on ARM.", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "Installiert mlx-whisper über pipx + ffmpeg. Nur Apple Silicon (arm64).", "installs_openai_whisper_ffmpeg_uses_system_pytho": "Installiert openai-whisper + ffmpeg. Nutzt das System-python3 (≥ 3.10).", "language": "Sprache", @@ -12036,6 +12038,7 @@ "not_installed": "nicht installiert", "provider": "Anbieter", "provider_apple": "Apple Speech (auf dem Gerät — macOS 26+)", + "provider_faster": "faster-whisper (lokal — keine separate ffmpeg-Installation)", "provider_mlx": "Whisper MLX (lokal — nur Apple Silicon)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (lokal)", @@ -12047,6 +12050,8 @@ "step_done": "Fertig!", "step_error": "Fehlgeschlagen", "step_installing_brew": "Homebrew wird installiert…", + "step_installing_faster": "faster-whisper wird installiert…", + "faster_unsupported_windows_arm": "faster-whisper ist unter Windows on ARM nicht verfügbar – für diese Plattform existiert kein CTranslate2-Wheel. Verwenden Sie stattdessen den Anbieter whisper (lokal) oder AWS Transcribe.", "step_installing_ffmpeg": "ffmpeg wird installiert…", "step_installing_mlx": "mlx-whisper wird installiert…", "step_installing_python": "Python wird installiert…", diff --git a/website/src/i18n/locales/en-XA.json b/website/src/i18n/locales/en-XA.json index 8a4adb8bbf4..415dd29d011 100644 --- a/website/src/i18n/locales/en-XA.json +++ b/website/src/i18n/locales/en-XA.json @@ -11674,7 +11674,12 @@ "step_installing_whisper": "[Ìñşţàĺĺìñğ ẁĥìşþèŕ (~1.6 ĞƁ)… ····················]", "step_installing_xcode": "[Ìñşţàĺĺìñğ Ẋçøðè ÇĹÌ Ţøøĺş… ···················]", "step_starting": "[Şţàŕţìñğ… ··············]", - "voice_input_remote_instance_note": "[Ṽøìçè ìñþùţ øñ à ŕèɱøţè ìñşţàñçè ŕèǫùìŕèş ɱìçŕøþĥøñè þèŕɱìşşìøñ ðèĺèğàţìøñ àçŕøşş ţĥè ìƒŕàɱè ƀøùñðàŕý. ̃ ţĥè ɱìç ðøèşñ'ţ ŕèşþøñð ĥèŕè, ùşè ţĥè ĺøçàĺ (þàŕèñţ) ðàşĥƀøàŕð ìñşţèàð. ·····················································]" + "voice_input_remote_instance_note": "[Ṽøìçè ìñþùţ øñ à ŕèɱøţè ìñşţàñçè ŕèǫùìŕèş ɱìçŕøþĥøñè þèŕɱìşşìøñ ðèĺèğàţìøñ àçŕøşş ţĥè ìƒŕàɱè ƀøùñðàŕý. ̃ ţĥè ɱìç ðøèşñ'ţ ŕèşþøñð ĥèŕè, ùşè ţĥè ĺøçàĺ (þàŕèñţ) ðàşĥƀøàŕð ìñşţèàð. ·····················································]", + "provider_faster": "[ƒàşţèŕ-ẁĥìşþèŕ (ĺøçàĺ — ñø şèþàŕàţè ƒƒɱþèğ ìñşţàĺĺ) ··················]", + "install_faster_whisper": "[Ìñşţàĺĺ ƒàşţèŕ-ẁĥìşþèŕ ···············]", + "installs_faster_whisper_no_ffmpeg_needed": "[Ìñşţàĺĺş ƒàşţèŕ-ẁĥìşþèŕ ṽìà þìþ. Ñø şèþàŕàţè ƒƒɱþèğ ìñşţàĺĺ: ìţ ðèçøðèş àùðìø ìñ-þŕøçèşş ţĥŕøùğĥ ÞýÀṼ'ş ƀùñðĺèð ƑƑɱþèğ. Ñøţ àṽàìĺàƀĺè øñ Ẁìñðøẁş øñ ÀŔṀ. ··············································]", + "step_installing_faster": "[Ìñşţàĺĺìñğ ƒàşţèŕ-ẁĥìşþèŕ… ··················]", + "faster_unsupported_windows_arm": "[ƒàşţèŕ-ẁĥìşþèŕ ìş ñøţ àṽàìĺàƀĺè øñ Ẁìñðøẁş øñ ÀŔṀ — ñø ÇŢŕàñşĺàţè2 ẁĥèèĺ èẋìşţş ƒøŕ ţĥìş þĺàţƒøŕɱ. Ùşè ţĥè ẁĥìşþèŕ (ĺøçàĺ) øŕ ÀẀŞ Ţŕàñşçŕìƀè þŕøṽìðèŕ ìñşţèàð. ···············································]" }, "teamsPanel": { "above_unlike_slack_the_bot_framework_has_no_outb": "[àƀøṽè. Ùñĺìķè Şĺàçķ, ţĥè Ɓøţ Ƒŕàɱèẁøŕķ ĥàş ñø\n øùţƀøùñð-øñĺý ɱøðè. ························]", diff --git a/website/src/i18n/locales/en.manual.json b/website/src/i18n/locales/en.manual.json index 3a27280f8b2..15e3897c975 100644 --- a/website/src/i18n/locales/en.manual.json +++ b/website/src/i18n/locales/en.manual.json @@ -4652,7 +4652,12 @@ "step_installing_whisper": "Installing whisper (~1.6 GB)…", "step_installing_xcode": "Installing Xcode CLI Tools…", "step_starting": "Starting…", - "voice_input_remote_instance_note": "Voice input on a remote instance requires microphone permission delegation across the iframe boundary. If the mic doesn't respond here, use the local (parent) dashboard instead." + "voice_input_remote_instance_note": "Voice input on a remote instance requires microphone permission delegation across the iframe boundary. If the mic doesn't respond here, use the local (parent) dashboard instead.", + "provider_faster": "faster-whisper (local — no separate ffmpeg install)", + "install_faster_whisper": "Install faster-whisper", + "installs_faster_whisper_no_ffmpeg_needed": "Installs faster-whisper via pip. No separate ffmpeg install: it decodes audio in-process through PyAV's bundled FFmpeg. Not available on Windows on ARM.", + "step_installing_faster": "Installing faster-whisper…", + "faster_unsupported_windows_arm": "faster-whisper is not available on Windows on ARM — no CTranslate2 wheel exists for this platform. Use the whisper (local) or AWS Transcribe provider instead." }, "teamsPanel": { "active": "Active", diff --git a/website/src/i18n/locales/es.json b/website/src/i18n/locales/es.json index a12322144ca..b3da51322d5 100644 --- a/website/src/i18n/locales/es.json +++ b/website/src/i18n/locales/es.json @@ -12183,8 +12183,10 @@ "failed_to_save_stt_config": "No se pudo guardar la configuración de STT", "input_device_used_to_capture_your_voice": "Dispositivo de entrada usado para capturar tu voz", "install_failed": "Instalación fallida", + "install_faster_whisper": "Instalar faster-whisper", "install_mlx_whisper": "Instalar MLX Whisper", "install_whisper": "Instalar Whisper", + "installs_faster_whisper_no_ffmpeg_needed": "Instala faster-whisper mediante pip. No hay que instalar ffmpeg aparte: decodifica en el mismo proceso con el FFmpeg incluido en PyAV. No está disponible en Windows on ARM.", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "Instala mlx-whisper mediante pipx + ffmpeg. Solo para Apple Silicon (arm64).", "installs_openai_whisper_ffmpeg_uses_system_pytho": "Instala openai-whisper + ffmpeg. Usa el python3 del sistema (≥ 3.10).", "language": "Idioma", @@ -12196,6 +12198,7 @@ "not_installed": "no instalado", "provider": "Proveedor", "provider_apple": "Apple Speech (en el dispositivo — macOS 26+)", + "provider_faster": "faster-whisper (local — sin instalar ffmpeg aparte)", "provider_mlx": "Whisper MLX (local — solo Apple Silicon)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (local)", @@ -12207,6 +12210,8 @@ "step_done": "¡Listo!", "step_error": "Falló", "step_installing_brew": "Instalando Homebrew…", + "step_installing_faster": "Instalando faster-whisper…", + "faster_unsupported_windows_arm": "faster-whisper no está disponible en Windows on ARM: no existe ningún wheel de CTranslate2 para esta plataforma. Usa el proveedor whisper (local) o AWS Transcribe en su lugar.", "step_installing_ffmpeg": "Instalando ffmpeg…", "step_installing_mlx": "Instalando mlx-whisper…", "step_installing_python": "Instalando Python…", diff --git a/website/src/i18n/locales/fr.json b/website/src/i18n/locales/fr.json index 038b65f7884..55786352ad9 100644 --- a/website/src/i18n/locales/fr.json +++ b/website/src/i18n/locales/fr.json @@ -12183,8 +12183,10 @@ "failed_to_save_stt_config": "Échec de l’enregistrement de la configuration STT", "input_device_used_to_capture_your_voice": "Périphérique d'entrée utilisé pour capter votre voix", "install_failed": "Échec de l’installation", + "install_faster_whisper": "Installer faster-whisper", "install_mlx_whisper": "Installer MLX Whisper", "install_whisper": "Installer Whisper", + "installs_faster_whisper_no_ffmpeg_needed": "Installe faster-whisper via pip. Aucune installation séparée de ffmpeg : le décodage se fait dans le processus, via le FFmpeg fourni avec PyAV. Non disponible sous Windows on ARM.", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "Installe mlx-whisper via pipx + ffmpeg. Apple Silicon (arm64) uniquement.", "installs_openai_whisper_ffmpeg_uses_system_pytho": "Installe openai-whisper + ffmpeg. Utilise le python3 du système (≥ 3.10).", "language": "Langue", @@ -12196,6 +12198,7 @@ "not_installed": "non installé", "provider": "Fournisseur", "provider_apple": "Apple Speech (sur l’appareil — macOS 26+)", + "provider_faster": "faster-whisper (local — sans installer ffmpeg séparément)", "provider_mlx": "Whisper MLX (local — Apple Silicon uniquement)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (local)", @@ -12207,6 +12210,8 @@ "step_done": "Terminé !", "step_error": "Échec", "step_installing_brew": "Installation de Homebrew…", + "step_installing_faster": "Installation de faster-whisper…", + "faster_unsupported_windows_arm": "faster-whisper n'est pas disponible sur Windows on ARM : aucun wheel CTranslate2 n'existe pour cette plateforme. Utilisez plutôt le fournisseur whisper (local) ou AWS Transcribe.", "step_installing_ffmpeg": "Installation de ffmpeg…", "step_installing_mlx": "Installation de mlx-whisper…", "step_installing_python": "Installation de Python…", diff --git a/website/src/i18n/locales/hi.json b/website/src/i18n/locales/hi.json index 8a2648aa221..9f6b0f0112f 100644 --- a/website/src/i18n/locales/hi.json +++ b/website/src/i18n/locales/hi.json @@ -12024,8 +12024,10 @@ "failed_to_save_stt_config": "STT कॉन्फ़िग सहेजने में विफल", "input_device_used_to_capture_your_voice": "आपकी आवाज़ कैप्चर करने के लिए उपयोग होने वाला इनपुट डिवाइस", "install_failed": "इंस्टॉल विफल", + "install_faster_whisper": "faster-whisper इंस्टॉल करें", "install_mlx_whisper": "MLX Whisper इंस्टॉल करें", "install_whisper": "Whisper इंस्टॉल करें", + "installs_faster_whisper_no_ffmpeg_needed": "pip के ज़रिए faster-whisper इंस्टॉल करता है। ffmpeg अलग से इंस्टॉल करने की ज़रूरत नहीं: यह PyAV में शामिल FFmpeg से उसी प्रोसेस में डिकोड करता है। Windows on ARM पर उपलब्ध नहीं है।", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "pipx के ज़रिए mlx-whisper + ffmpeg इंस्टॉल करता है। सिर्फ़ Apple Silicon (arm64) पर।", "installs_openai_whisper_ffmpeg_uses_system_pytho": "openai-whisper + ffmpeg इंस्टॉल करता है। सिस्टम python3 (≥ 3.10) इस्तेमाल करता है।", "language": "भाषा", @@ -12037,6 +12039,7 @@ "not_installed": "इंस्टॉल नहीं है", "provider": "प्रदाता", "provider_apple": "Apple Speech (डिवाइस पर — macOS 26+)", + "provider_faster": "faster-whisper (स्थानीय — ffmpeg अलग से इंस्टॉल करने की ज़रूरत नहीं)", "provider_mlx": "Whisper MLX (स्थानीय — केवल Apple Silicon)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (स्थानीय)", @@ -12048,6 +12051,8 @@ "step_done": "पूर्ण!", "step_error": "विफल", "step_installing_brew": "Homebrew इंस्टॉल हो रहा है…", + "step_installing_faster": "faster-whisper इंस्टॉल हो रहा है…", + "faster_unsupported_windows_arm": "faster-whisper, Windows on ARM पर उपलब्ध नहीं है — इस प्लेटफ़ॉर्म के लिए कोई CTranslate2 व्हील मौजूद नहीं है। इसके बजाय whisper (लोकल) या AWS Transcribe प्रदाता का उपयोग करें।", "step_installing_ffmpeg": "ffmpeg इंस्टॉल हो रहा है…", "step_installing_mlx": "mlx-whisper इंस्टॉल हो रहा है…", "step_installing_python": "Python इंस्टॉल हो रहा है…", diff --git a/website/src/i18n/locales/it.json b/website/src/i18n/locales/it.json index 226b53fa646..b183c4e289f 100644 --- a/website/src/i18n/locales/it.json +++ b/website/src/i18n/locales/it.json @@ -12182,8 +12182,10 @@ "failed_to_save_stt_config": "Impossibile salvare la configurazione STT", "input_device_used_to_capture_your_voice": "Dispositivo di input usato per acquisire la tua voce", "install_failed": "Installazione non riuscita", + "install_faster_whisper": "Installa faster-whisper", "install_mlx_whisper": "Installa MLX Whisper", "install_whisper": "Installa Whisper", + "installs_faster_whisper_no_ffmpeg_needed": "Installa faster-whisper tramite pip. Nessuna installazione separata di ffmpeg: decodifica nello stesso processo con il FFmpeg incluso in PyAV. Non disponibile su Windows on ARM.", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "Installa mlx-whisper tramite pipx + ffmpeg. Solo Apple Silicon (arm64).", "installs_openai_whisper_ffmpeg_uses_system_pytho": "Installa openai-whisper + ffmpeg. Usa il python3 di sistema (≥ 3.10).", "language": "Lingua", @@ -12195,6 +12197,7 @@ "not_installed": "non installato", "provider": "Provider", "provider_apple": "Apple Speech (sul dispositivo — macOS 26+)", + "provider_faster": "faster-whisper (locale — senza installare ffmpeg a parte)", "provider_mlx": "Whisper MLX (locale — solo Apple Silicon)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (locale)", @@ -12206,6 +12209,8 @@ "step_done": "Fatto!", "step_error": "Non riuscito", "step_installing_brew": "Installazione di Homebrew…", + "step_installing_faster": "Installazione di faster-whisper…", + "faster_unsupported_windows_arm": "faster-whisper non è disponibile su Windows on ARM: per questa piattaforma non esiste alcun wheel CTranslate2. Usa invece il provider whisper (locale) o AWS Transcribe.", "step_installing_ffmpeg": "Installazione di ffmpeg…", "step_installing_mlx": "Installazione di mlx-whisper…", "step_installing_python": "Installazione di Python…", diff --git a/website/src/i18n/locales/ja.json b/website/src/i18n/locales/ja.json index d61bbb07716..3082c2b7489 100644 --- a/website/src/i18n/locales/ja.json +++ b/website/src/i18n/locales/ja.json @@ -11864,8 +11864,10 @@ "failed_to_save_stt_config": "STT 設定を保存できませんでした", "input_device_used_to_capture_your_voice": "音声をキャプチャするために使用される入力デバイス", "install_failed": "インストール失敗", + "install_faster_whisper": "faster-whisper をインストール", "install_mlx_whisper": "MLX Whisperをインストール", "install_whisper": "Whisperをインストール", + "installs_faster_whisper_no_ffmpeg_needed": "pip で faster-whisper をインストールします。ffmpeg を個別に導入する必要はありません(PyAV に同梱の FFmpeg でプロセス内でデコードします)。Windows on ARM では利用できません。", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "mlx-whisper を pipx + ffmpeg でインストールします。Apple Silicon(arm64)のみ。", "installs_openai_whisper_ffmpeg_uses_system_pytho": "openai-whisper + ffmpeg をインストールします。システムの python3(3.10 以上)を使用します。", "language": "言語", @@ -11877,6 +11879,7 @@ "not_installed": "インストール済みではありません", "provider": "プロバイダー", "provider_apple": "Apple Speech (オンデバイス — macOS 26+)", + "provider_faster": "faster-whisper(ローカル — ffmpeg の個別インストール不要)", "provider_mlx": "Whisper MLX (ローカル — Apple Silicon のみ)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (ローカル)", @@ -11888,6 +11891,8 @@ "step_done": "完了!", "step_error": "失敗", "step_installing_brew": "Homebrewをインストール中…", + "step_installing_faster": "faster-whisper をインストール中…", + "faster_unsupported_windows_arm": "faster-whisper は Windows on ARM では利用できません。このプラットフォーム向けの CTranslate2 ホイールが存在しないためです。代わりに whisper(ローカル)または AWS Transcribe プロバイダーをご利用ください。", "step_installing_ffmpeg": "ffmpeg をインストール中…", "step_installing_mlx": "mlx-whisperをインストール中…", "step_installing_python": "Python をインストール中…", diff --git a/website/src/i18n/locales/ko.json b/website/src/i18n/locales/ko.json index ba990db3302..c5529d77eb4 100644 --- a/website/src/i18n/locales/ko.json +++ b/website/src/i18n/locales/ko.json @@ -11864,8 +11864,10 @@ "failed_to_save_stt_config": "STT 설정을 저장하지 못했습니다", "input_device_used_to_capture_your_voice": "음성을 캡처하는 데 사용할 입력 장치", "install_failed": "설치 실패", + "install_faster_whisper": "faster-whisper 설치", "install_mlx_whisper": "MLX Whisper 설치", "install_whisper": "Whisper 설치", + "installs_faster_whisper_no_ffmpeg_needed": "pip로 faster-whisper를 설치합니다. ffmpeg를 별도로 설치할 필요가 없습니다. PyAV에 번들된 FFmpeg로 프로세스 내에서 오디오를 디코딩합니다. Windows on ARM에서는 사용할 수 없습니다.", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "pipx로 mlx-whisper와 ffmpeg를 설치합니다. Apple Silicon(arm64)만 지원합니다.", "installs_openai_whisper_ffmpeg_uses_system_pytho": "openai-whisper와 ffmpeg를 설치합니다. 시스템 python3(3.10 이상)을 사용합니다.", "language": "언어", @@ -11877,6 +11879,7 @@ "not_installed": "설치되지 않음", "provider": "제공자", "provider_apple": "Apple Speech (온디바이스 — macOS 26+)", + "provider_faster": "faster-whisper (로컬 — 별도 ffmpeg 설치 불필요)", "provider_mlx": "Whisper MLX (로컬 — Apple Silicon 전용)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (로컬)", @@ -11888,6 +11891,8 @@ "step_done": "완료!", "step_error": "실패", "step_installing_brew": "Homebrew를 설치하는 중…", + "step_installing_faster": "faster-whisper를 설치하는 중…", + "faster_unsupported_windows_arm": "faster-whisper는 Windows on ARM에서 사용할 수 없습니다. 이 플랫폼용 CTranslate2 휠이 없습니다. 대신 whisper(로컬) 또는 AWS Transcribe 공급자를 사용하십시오.", "step_installing_ffmpeg": "ffmpeg를 설치하는 중…", "step_installing_mlx": "mlx-whisper를 설치하는 중…", "step_installing_python": "Python을 설치하는 중…", diff --git a/website/src/i18n/locales/pt.json b/website/src/i18n/locales/pt.json index b578dc110c2..51a5a68f02b 100644 --- a/website/src/i18n/locales/pt.json +++ b/website/src/i18n/locales/pt.json @@ -12182,8 +12182,10 @@ "failed_to_save_stt_config": "Falha ao salvar a configuração de STT", "input_device_used_to_capture_your_voice": "Dispositivo de entrada usado para capturar sua voz", "install_failed": "Falha na instalação", + "install_faster_whisper": "Instalar faster-whisper", "install_mlx_whisper": "Instalar MLX Whisper", "install_whisper": "Instalar Whisper", + "installs_faster_whisper_no_ffmpeg_needed": "Instala o faster-whisper via pip. Não é preciso instalar o ffmpeg à parte: a decodificação ocorre no próprio processo, com o FFmpeg incluído no PyAV. Não está disponível no Windows on ARM.", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "Instala o mlx-whisper via pipx + ffmpeg. Apenas Apple Silicon (arm64).", "installs_openai_whisper_ffmpeg_uses_system_pytho": "Instala o openai-whisper + ffmpeg. Usa o python3 do sistema (≥ 3.10).", "language": "Idioma", @@ -12195,6 +12197,7 @@ "not_installed": "não instalado", "provider": "Provedor", "provider_apple": "Apple Speech (no dispositivo — macOS 26+)", + "provider_faster": "faster-whisper (local — sem instalar o ffmpeg à parte)", "provider_mlx": "Whisper MLX (local — apenas Apple Silicon)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (local)", @@ -12206,6 +12209,8 @@ "step_done": "Concluído!", "step_error": "Falhou", "step_installing_brew": "Instalando o Homebrew…", + "step_installing_faster": "Instalando o faster-whisper…", + "faster_unsupported_windows_arm": "faster-whisper não está disponível no Windows on ARM — não existe wheel do CTranslate2 para esta plataforma. Use o provedor whisper (local) ou AWS Transcribe.", "step_installing_ffmpeg": "Instalando o ffmpeg…", "step_installing_mlx": "Instalando o mlx-whisper…", "step_installing_python": "Instalando o Python…", diff --git a/website/src/i18n/locales/ru.json b/website/src/i18n/locales/ru.json index ce74eacfd61..46fc263a651 100644 --- a/website/src/i18n/locales/ru.json +++ b/website/src/i18n/locales/ru.json @@ -12342,8 +12342,10 @@ "failed_to_save_stt_config": "Не удалось сохранить настройки STT", "input_device_used_to_capture_your_voice": "Устройство ввода для записи вашего голоса", "install_failed": "Установка не удалась", + "install_faster_whisper": "Установить faster-whisper", "install_mlx_whisper": "Установить MLX Whisper", "install_whisper": "Установить Whisper", + "installs_faster_whisper_no_ffmpeg_needed": "Устанавливает faster-whisper через pip. Отдельно устанавливать ffmpeg не нужно: декодирование идёт внутри процесса через FFmpeg, входящий в PyAV. Недоступно в Windows on ARM.", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "Устанавливает mlx-whisper через pipx и ffmpeg. Только Apple Silicon (arm64).", "installs_openai_whisper_ffmpeg_uses_system_pytho": "Устанавливает openai-whisper и ffmpeg. Использует системный python3 (≥ 3.10).", "language": "Язык", @@ -12355,6 +12357,7 @@ "not_installed": "не установлено", "provider": "Провайдер", "provider_apple": "Apple Speech (на устройстве — macOS 26+)", + "provider_faster": "faster-whisper (локально — без отдельной установки ffmpeg)", "provider_mlx": "Whisper MLX (локально — только Apple Silicon)", "provider_transcribe": "Transcribe (AWS)", "provider_whisper": "Whisper (локально)", @@ -12366,6 +12369,8 @@ "step_done": "Готово!", "step_error": "Ошибка", "step_installing_brew": "Установка Homebrew...", + "step_installing_faster": "Установка faster-whisper...", + "faster_unsupported_windows_arm": "faster-whisper недоступен в Windows on ARM — для этой платформы нет wheel-пакета CTranslate2. Используйте вместо него провайдер whisper (локальный) или AWS Transcribe.", "step_installing_ffmpeg": "Установка ffmpeg...", "step_installing_mlx": "Установка mlx-whisper...", "step_installing_python": "Установка Python...", diff --git a/website/src/i18n/locales/zh-CN.json b/website/src/i18n/locales/zh-CN.json index 409c8dd1805..c8c4329aa21 100644 --- a/website/src/i18n/locales/zh-CN.json +++ b/website/src/i18n/locales/zh-CN.json @@ -11864,8 +11864,10 @@ "failed_to_save_stt_config": "保存 STT 配置失败", "input_device_used_to_capture_your_voice": "用于采集语音的输入设备", "install_failed": "安装失败", + "install_faster_whisper": "安装 faster-whisper", "install_mlx_whisper": "安装 MLX Whisper", "install_whisper": "安装 Whisper", + "installs_faster_whisper_no_ffmpeg_needed": "通过 pip 安装 faster-whisper。无需单独安装 ffmpeg:它使用 PyAV 内置的 FFmpeg 在进程内解码。不支持 Windows on ARM。", "installs_mlx_whisper_via_pipx_ffmpeg_apple_silic": "通过 pipx 安装 mlx-whisper 和 ffmpeg。仅支持 Apple Silicon(arm64)。", "installs_openai_whisper_ffmpeg_uses_system_pytho": "安装 openai-whisper 和 ffmpeg。使用系统 python3(≥ 3.10)。", "language": "语言", @@ -11877,6 +11879,7 @@ "not_installed": "未安装", "provider": "提供方", "provider_apple": "Apple 语音(设备端 — 需 macOS 26 及以上)", + "provider_faster": "faster-whisper(本地 — 无需单独安装 ffmpeg)", "provider_mlx": "Whisper MLX(本地 — 仅 Apple Silicon)", "provider_transcribe": "Transcribe(AWS)", "provider_whisper": "Whisper(本地)", @@ -11888,6 +11891,8 @@ "step_done": "完成!", "step_error": "失败", "step_installing_brew": "正在安装 Homebrew…", + "step_installing_faster": "正在安装 faster-whisper…", + "faster_unsupported_windows_arm": "faster-whisper 在 Windows on ARM 上不可用:该平台没有 CTranslate2 的 wheel 包。请改用 whisper(本地)或 AWS Transcribe 提供程序。", "step_installing_ffmpeg": "正在安装 ffmpeg…", "step_installing_mlx": "正在安装 mlx-whisper…", "step_installing_python": "正在安装 Python…", diff --git a/website/src/pages/settings/SttSettings.tsx b/website/src/pages/settings/SttSettings.tsx index 84332104dfd..1e26959639e 100644 --- a/website/src/pages/settings/SttSettings.tsx +++ b/website/src/pages/settings/SttSettings.tsx @@ -49,6 +49,7 @@ interface SttConfig { install_error: string prereqs: string[] transcribe_unsupported?: boolean + faster_unsupported?: boolean bundled_interpreter?: boolean ffmpeg_missing?: boolean } @@ -63,7 +64,7 @@ interface SttConfig { * full literal keys indexed inline at the `i18nT()` call — the only shape * `scripts/check-i18n-keys.mjs` can resolve statically. */ -const STEP_LABEL_KEY: Record = { +export const STEP_LABEL_KEY: Record = { starting: 'pages.settings.sttSettings.step_starting', checking: 'pages.settings.sttSettings.step_checking', installing_xcode: 'pages.settings.sttSettings.step_installing_xcode', @@ -72,6 +73,7 @@ const STEP_LABEL_KEY: Record = { installing_ffmpeg: 'pages.settings.sttSettings.step_installing_ffmpeg', installing_whisper: 'pages.settings.sttSettings.step_installing_whisper', installing_mlx: 'pages.settings.sttSettings.step_installing_mlx', + installing_faster: 'pages.settings.sttSettings.step_installing_faster', done: 'pages.settings.sttSettings.step_done', error: 'pages.settings.sttSettings.step_error', } @@ -92,13 +94,23 @@ function stepLabel(step: string): string { * The provider *names* (Whisper, MLX, Transcribe) are DNT — only the * parenthetical qualifier is copy. */ -const PROVIDER_LABEL_KEY: Record = { +export const PROVIDER_LABEL_KEY: Record = { whisper: 'pages.settings.sttSettings.provider_whisper', mlx: 'pages.settings.sttSettings.provider_mlx', apple: 'pages.settings.sttSettings.provider_apple', transcribe: 'pages.settings.sttSettings.provider_transcribe', + faster: 'pages.settings.sttSettings.provider_faster', } +/** + * Providers that name their model with a Whisper size (`turbo`, `small`, …) and so + * share the `model` field and its picker. `mlx` is excluded: it takes a + * HuggingFace repo id in `mlx_model` instead, which is a different control. + * + * File scope so `check-i18n-keys.mjs` can resolve it, same as `PROVIDER_LABEL_KEY`. + */ +export const WHISPER_MODEL_PROVIDERS = ['whisper', 'faster'] + /** Localised dropdown label for a provider id, falling back to the raw id. */ function providerLabel(provider: string): string { // `hasOwnProperty`, not `in`: the id list comes from `SttConfig.providers`. @@ -364,6 +376,11 @@ export default function SttSettings({ cardIndex }: { const installing = isInstalling(stt) const isTranscribe = stt.provider === 'transcribe' const provider = stt.provider || 'whisper' + // `faster` on a platform with no CTranslate2 wheel: the Install button can only + // ever fail here (the backend refuses the request outright), so it is hidden and + // replaced with the alternatives — the same reasoning that hides it for + // Transcribe, whose requirement the button also cannot satisfy. + const fasterUnsupported = provider === 'faster' && !!stt.faster_unsupported const providerOptions = stt.providers?.length ? stt.providers : ['whisper', 'transcribe'] // Gate the streaming controls on the CAPABILITY, not on a provider name. The // backend owns the list (`stt_stream._STREAMING_PROVIDERS`) and serves it, so @@ -430,7 +447,7 @@ export default function SttSettings({ cardIndex }: { - {provider === 'whisper' && ( + {WHISPER_MODEL_PROVIDERS.includes(provider) && ( `${n} (${s})`)} onChange={v => set({ model: v })} disabled={saving} /> )} @@ -479,6 +496,18 @@ export default function SttSettings({ cardIndex }: {

)} + {fasterUnsupported && ( + // No CTranslate2 wheel exists for this platform and there is no + // sdist to build from, so pip cannot resolve the dependency at all. + // Nothing the user does to this machine changes that, so name the + // providers that DO work instead of leaving an Install button whose + // every press returns the same 400. +
+

+ {i18nT('pages.settings.sttSettings.faster_unsupported_windows_arm')} +

+
+ )} {stt.prereqs?.length > 0 && !installing && (

{i18nT('pages.settings.sttSettings.run_these_commands_in_your_terminal_first')}

@@ -511,24 +540,31 @@ export default function SttSettings({ cardIndex }: { {stt.install_detail &&

{stt.install_detail}

}
+ style={{ width: stt.install_step === 'checking' ? '10%' : stt.install_step === 'installing_xcode' ? '15%' : stt.install_step === 'installing_brew' ? '25%' : stt.install_step === 'installing_python' ? '35%' : stt.install_step === 'installing_ffmpeg' ? '50%' : stt.install_step === 'installing_whisper' || stt.install_step === 'installing_faster' ? '70%' : '5%' }} />
- ) : !isTranscribe && ( + ) : !isTranscribe && !fasterUnsupported && ( // Hidden for Transcribe: the button installs a local Whisper // runtime, which cannot change Transcribe's availability — its // requirement is the `voice` extra surfaced in the prereq block // above, and the backend rejects the install for this provider. + // Hidden for an unsupported `faster` platform for the same reason: + // the backend refuses that request too, so the button could only + // ever produce the notice already shown above. <> installMut.mutate()}> {provider === 'mlx' ? <> {i18nT('pages.settings.sttSettings.install_mlx_whisper')} - : <> {i18nT('pages.settings.sttSettings.install_whisper')}} + : provider === 'faster' + ? <> {i18nT('pages.settings.sttSettings.install_faster_whisper')} + : <> {i18nT('pages.settings.sttSettings.install_whisper')}}

{provider === 'mlx' ? i18nT('pages.settings.sttSettings.installs_mlx_whisper_via_pipx_ffmpeg_apple_silic') - : i18nT('pages.settings.sttSettings.installs_openai_whisper_ffmpeg_uses_system_pytho')} + : provider === 'faster' + ? i18nT('pages.settings.sttSettings.installs_faster_whisper_no_ffmpeg_needed') + : i18nT('pages.settings.sttSettings.installs_openai_whisper_ffmpeg_uses_system_pytho')}

)} diff --git a/website/src/test/SttSettingsFaster.test.ts b/website/src/test/SttSettingsFaster.test.ts new file mode 100644 index 00000000000..1d9a84659b0 --- /dev/null +++ b/website/src/test/SttSettingsFaster.test.ts @@ -0,0 +1,146 @@ +// The faster-whisper provider's presence in the STT settings UI. +// +// The maps under test are the UI's whole contract with the backend's provider and +// install-step vocabularies: a provider missing from `PROVIDER_LABEL_KEY` renders +// as a bare id in the dropdown, a step missing from `STEP_LABEL_KEY` renders as an +// empty progress label, and a provider missing from `WHISPER_MODEL_PROVIDERS` gets +// no model picker at all despite the backend reading `stt.model` for it. All three +// are silent failures, which is why they are pinned rather than left to a +// screenshot. +// +// They are exported from the shipping module for the same reason +// `useMeetingSession`'s pure helpers are: so the test binds to the real values +// instead of a copy that can drift. + +import { describe, it, expect } from 'vitest' + +import { CATALOGS as RUNTIME_CATALOGS } from '../i18n/catalogs' +import { SUPPORTED_LANGUAGES } from '../i18n/languages' +import { + PROVIDER_LABEL_KEY, + STEP_LABEL_KEY, + WHISPER_MODEL_PROVIDERS, +} from '../pages/settings/SttSettings' +import EN_MANUAL from '../i18n/locales/en.manual.json' + +const manualStt = (EN_MANUAL as { pages: { settings: { sttSettings: Record } } }) + .pages.settings.sttSettings + +describe('provider labels', () => { + it('labels every provider the backend can advertise', () => { + // `_VALID_STT_PROVIDERS` in the config loader, mirrored by hand because it is + // Python. An id absent here falls back to the raw string, so the dropdown would + // read "faster". Keeping `apple` listed matters as much as `faster`: this list + // is the mirror, so an omission here is the failure it is meant to catch. + expect(Object.keys(PROVIDER_LABEL_KEY).sort()).toEqual( + ['apple', 'faster', 'mlx', 'transcribe', 'whisper'], + ) + }) + + it('has a catalog string behind each label key', () => { + for (const [provider, key] of Object.entries(PROVIDER_LABEL_KEY)) { + const leaf = key.replace('pages.settings.sttSettings.', '') + expect(manualStt[leaf], provider).toBeTruthy() + } + }) +}) + +describe('the model picker gate', () => { + it('covers the providers that name models by Whisper size', () => { + // Both read `stt.model`, so both need the picker. Before this, selecting + // `faster` showed no model control while the backend still used the field. + expect(WHISPER_MODEL_PROVIDERS).toContain('whisper') + expect(WHISPER_MODEL_PROVIDERS).toContain('faster') + }) + + it('excludes providers that do not', () => { + // `mlx` takes a HuggingFace repo id in `mlx_model` — a different control. + // `transcribe` runs server-side and has no local model at all. + expect(WHISPER_MODEL_PROVIDERS).not.toContain('mlx') + expect(WHISPER_MODEL_PROVIDERS).not.toContain('transcribe') + }) +}) + +describe('install progress steps', () => { + it('labels the faster-whisper install step', () => { + // The backend emits `installing_faster`; an unmapped step renders blank. + expect(STEP_LABEL_KEY.installing_faster).toBe( + 'pages.settings.sttSettings.step_installing_faster', + ) + }) + + it('has a catalog string behind each step key', () => { + for (const [step, key] of Object.entries(STEP_LABEL_KEY)) { + const leaf = key.replace('pages.settings.sttSettings.', '') + expect(manualStt[leaf], step).toBeTruthy() + } + }) +}) + +describe('the faster-whisper install copy', () => { + it('promises no SEPARATE ffmpeg install, not the absence of ffmpeg', () => { + // The distinction is the whole accuracy of this string. FFmpeg is not gone -- + // it arrives inside PyAV's wheel and faster-whisper decodes through it + // in-process. What is true, and what makes this provider worth choosing on a + // machine where the CLI toolchain is the hard part, is that there is no + // separate ffmpeg to install. Claiming "no ffmpeg needed" would tell the user + // something false about what lands on their machine. + const blurb = manualStt.installs_faster_whisper_no_ffmpeg_needed + expect(blurb).toBeTruthy() + expect(blurb.toLowerCase()).toContain('ffmpeg') + expect(blurb.toLowerCase()).toContain('separate') + expect(blurb).toMatch(/PyAV/i) + }) + + it('warns about the platform with no CTranslate2 wheel', () => { + expect(manualStt.installs_faster_whisper_no_ffmpeg_needed).toContain('Windows on ARM') + }) + + it('has a button label', () => { + expect(manualStt.install_faster_whisper).toBeTruthy() + }) +}) + +describe('the unsupported-platform notice', () => { + // Pre-click gating. Without a served `faster_unsupported` flag the card could only + // discover the platform is unsupported by pressing Install and reading a 400 -- + // the same dead end on every press, which is the failure this notice removes. + it('exists in the catalog', () => { + expect(manualStt.faster_unsupported_windows_arm).toBeTruthy() + }) + + it('names the platform and the reason, not just a failure', () => { + const notice = manualStt.faster_unsupported_windows_arm + expect(notice).toContain('Windows on ARM') + // The reason matters: it is what tells the user retrying cannot help. + expect(notice).toContain('CTranslate2') + }) + + it('names both working alternatives', () => { + // A bare refusal leaves the user stuck. These two are the providers that do + // work here, and they are the same pair cli_doctor prints. + const notice = manualStt.faster_unsupported_windows_arm + expect(notice.toLowerCase()).toContain('whisper') + expect(notice).toMatch(/transcribe/i) + }) + + it('is translated into every shipped catalog', () => { + // Read the catalogs exactly as the runtime composes them — including English's + // generated (`en.json`) + manual (`en.manual.json`) merge. Globbing the locale + // JSON directly would flag `en.json`, which is regenerated wholesale from source + // scanning and legitimately never carries a hand-authored key. + const seen: string[] = [] + for (const [code, bundle] of Object.entries(RUNTIME_CATALOGS)) { + const root = (bundle as { translation: unknown }).translation as { + pages?: { settings?: { sttSettings?: Record } } + } + const value = root.pages?.settings?.sttSettings?.faster_unsupported_windows_arm + // A missing key renders the dotted path into the UI, and this notice is the + // only thing standing between a win-arm user and an unexplained dead end. + expect(value, code).toBeTruthy() + seen.push(code) + } + // Guard the guard: an empty catalog map would make the loop vacuously pass. + expect(seen.length).toBeGreaterThanOrEqual(SUPPORTED_LANGUAGES.length) + }) +})