fix(engines): keep the license reason so Supertonic-3 and PocketTTS can be enabled

The Model Catalogue shows an engine's license Accept button only when its
reason matches /license not accepted/i. public_backends() replaces probe
text with owned sentences, and no category covered a license gate, so the
reason arrived as the generic "Engine unavailable" line and the only way
to enable Supertonic-3 or PocketTTS never rendered (#2017).

A license category, matched first, keeps those words. The test reads the
regex out of EngineCompatibilityMatrix.jsx, so a wording change on either
side fails CI instead of silently hiding the button.
This commit is contained in:
Palash Debnath
2026-09-10 07:34:06 -07:00
parent b0cae840c4
commit 664c9ea4b5
3 changed files with 48 additions and 0 deletions
+1
View File
@@ -9,6 +9,7 @@ the frozen-backend fallback mirror it for their toolchains.
## [Unreleased]
**Highlights**
- Supertonic-3 and PocketTTS show their license Accept button again, so they can be enabled (#2017)
- A pronunciation entry that is stored but not applied yet says so, instead of looking like it did not match (#1949)
- A bare 500 report now names the backend error class, so two unrelated faults stop filing the same issue (#1773)
- A rejected dubbing source language now names the code it rejected (#1960)
+12
View File
@@ -43,6 +43,15 @@ _UNAVAILABLE_NOT_INSTALLED = (
"This engine's package isn't installed yet. Install it from "
"Model Catalogue → Engines."
)
# An engine gated behind an in-app license review (Supertonic-3, PocketTTS).
# The Model Catalogue shows its Accept button only when the reason matches
# /license not accepted/i (EngineCompatibilityMatrix.reasonMentionsLicense), so
# this sentence must keep those words: collapsing it into the generic line hid
# the only way to enable those engines.
_UNAVAILABLE_LICENSE = (
"License not accepted yet. Review and accept it in "
"Model Catalogue → Engines to enable this engine."
)
_UNAVAILABLE_NEEDS_CONFIG = (
"This engine needs to be configured before it can run. Open "
"Model Catalogue → Engines to finish setting it up."
@@ -56,6 +65,9 @@ _UNAVAILABLE_FILE_MISSING = (
# missing file often also says "not installed", and the file case has the more
# useful remedy of the two.
_UNAVAILABLE_SIGNATURES = (
# First: its probe text also says "Open Model Catalogue", and the
# license is the one gap only the user can close.
(_UNAVAILABLE_LICENSE, ("license not accepted",)),
(_UNAVAILABLE_FILE_MISSING, (
"file is missing", "file is empty", "file is unreadable",
"script missing", "binary", "not found at",
@@ -106,3 +106,38 @@ def test_the_input_row_is_not_mutated():
original = {"id": "e", "reason": "voxcpm package not installed."}
public_backends([original])
assert original["reason"] == "voxcpm package not installed."
@pytest.mark.parametrize(
"diagnostic",
[
# The engines' own wording (Supertonic3Backend / PocketTTSBackend).
"Supertonic-3 license not accepted. Open Model Catalogue → Engines → "
"Supertonic-3 and click Accept to enable. (MIT code license + OpenRAIL-M "
"model license.)",
"PocketTTS license not accepted. Open Model Catalogue → Engines → "
"PocketTTS and review the MIT code license, CC-BY-4.0 model license, "
"and gated-access conditions before enabling it.",
],
)
@pytest.mark.parametrize("one_click", [None, True, False])
def test_a_license_gate_keeps_the_words_the_accept_button_needs(diagnostic, one_click):
"""The Accept button renders only when the reason matches the matrix's
/license not accepted/i. Collapsing the reason into the generic line left
Supertonic-3 and PocketTTS with no way to be enabled."""
import re
from pathlib import Path
row = {"id": "e", "reason": diagnostic}
if one_click is not None:
row["one_click_install"] = one_click
reason = public_backends([row])[0]["reason"]
matrix = (
Path(__file__).resolve().parents[1]
/ "frontend/src/components/EngineCompatibilityMatrix.jsx"
).read_text(encoding="utf-8")
m = re.search(r"function reasonMentionsLicense\(reason\)[^}]*?return /([^/]+)/(\w*)\.test", matrix, re.S)
assert m, "EngineCompatibilityMatrix.reasonMentionsLicense changed shape"
flags = re.I if "i" in m.group(2) else 0
assert re.search(m.group(1), reason, flags), reason