feat(config): surface custom and plugin voice providers in config schema

This commit is contained in:
lost9999 2026-06-06 14:13:14 +08:00 committed by Teknium
parent 3a6e40b297
commit 1e17492784
1 changed files with 87 additions and 0 deletions

View File

@ -862,6 +862,93 @@ for _k, _v in CONFIG_SCHEMA.items():
CONFIG_SCHEMA = _ordered_schema
def _custom_provider_options(kind: str, builtin_names: List[str]) -> List[str]:
"""Return a merged provider option list without hard-coding vendor names.
*kind* is ``"tts"`` or ``"stt"``. The result keeps the built-in names
first, then appends:
1. ``tts.providers.*`` / ``stt.providers.*`` keys declared in
``config.yaml`` (these are command-type providers the user already
configured).
2. Any additional ``kind.provider`` value found on disk that is not yet
in the list this covers custom names that only appear as the active
provider without a dedicated ``providers.<name>`` block.
"""
names = list(builtin_names)
seen = {n.lower() for n in names}
def _add(name: str) -> None:
if isinstance(name, str):
normalized = name.strip().lower()
if normalized and normalized not in seen:
names.append(name.strip())
seen.add(normalized)
cfg = load_config()
cfg_provider = cfg_get(cfg, f"{kind}.provider")
_add(cfg_provider)
providers_map = cfg.get(kind, {}).get("providers") or {}
if isinstance(providers_map, dict):
for name in providers_map.keys():
_add(name)
return names
def _installed_tts_plugin_names() -> List[str]:
"""Return ``name`` values from user-installed TTS plugin manifests."""
plugin_dir = get_hermes_home() / "plugins"
names: List[str] = []
if not plugin_dir.is_dir():
return names
cfg = load_config()
enabled = set()
raw_enabled = (cfg.get("plugins") or {}).get("enabled") or []
if isinstance(raw_enabled, list):
for item in raw_enabled:
if isinstance(item, str):
enabled.add(item.strip().lower())
for child in sorted(plugin_dir.iterdir()):
manifest = child / "plugin.yaml"
if not manifest.is_file():
continue
if child.name.lower() not in enabled:
continue
try:
data = yaml.safe_load(manifest.read_text(encoding="utf-8")) or {}
except Exception:
continue
provides = data.get("provides") or []
if "tts" in provides:
name = str(data.get("name") or child.name).strip()
if name:
names.append(name)
return names
# Dynamically surface user-configured / plugin-backed provider names so the
# Desktop settings page does not silently drop custom providers.
for _audio_kind in ("tts", "stt"):
_key = f"{_audio_kind}.provider"
_override = _SCHEMA_OVERRIDES.get(_key)
if isinstance(_override, dict) and isinstance(_override.get("options"), list):
_builtin_names = list(_override["options"])
_custom_names = _custom_provider_options(_audio_kind, _builtin_names)
if _audio_kind == "tts":
_custom_names.extend(_installed_tts_plugin_names())
_override["options"] = sorted(set(_custom_names), key=str.lower)
class ConfigUpdate(BaseModel):
config: dict
profile: Optional[str] = None