diff options
Diffstat (limited to 'app/ui/hub.py')
| -rw-r--r-- | app/ui/hub.py | 67 |
1 files changed, 49 insertions, 18 deletions
diff --git a/app/ui/hub.py b/app/ui/hub.py index 29d2229..abb375a 100644 --- a/app/ui/hub.py +++ b/app/ui/hub.py @@ -65,6 +65,7 @@ from converter.clients import ( BACKEND_QWEN, LANGUAGE_CHOICES, QWEN3_TTS_SPEAKERS, + audiocpp_entry_supports_design, audiocpp_entry_voice_capability, audiocpp_family_voice_policy, normalize_language, @@ -1221,8 +1222,10 @@ def _audiocpp_fields(stdscr, api_url: Optional[str] = None, fields, prefix + "model_id"))] if model_voice_policy(fields) == AUDIOCPP_VOICE_OPTIONAL: # Mixed tts+clone family: the blank pick means plain TTS - # (no reference voice), so it always leads the menu. - return [("", "(built-in)")] + voices + # (the model's own built-in voice, no reference cloned), so + # it always leads the menu. The pair is (label, value): the + # readable label describes the blank value. + return [("<built-in> (no clone)", "")] + voices return voices return [] # design or pure TTS: the field is hidden @@ -1282,25 +1285,53 @@ def _audiocpp_fields(stdscr, api_url: Optional[str] = None, initial = voices_for(default_model) initial_voice = initial[0] if initial else "" - # The Model picker reads as a two-column table: pad every id to the - # widest one so the (type) column starts on the same position. + # The Model picker reads as a table: pad every id to the widest one, + # then render each entry's capabilities as fixed columns (how plain + # synthesis is voiced | clone | design) so every capability word sits + # in its own column across rows — easy to scan at a glance. id_width = max(len(entry.get("id") or "") for entry in models) - def _label(entry: dict) -> str: + def _capabilities(entry: dict) -> tuple: + """The entry's capability words in fixed column order. + + Column 1 voices plain synthesis ("speaker" for built-in speakers, + "tts" for families that need no voice at all), column 2 is + "clone" when the entry clones a reference, column 3 "design" when + it can design a voice from an Instructions description. + """ family = entry.get("family") or "" - capability = audiocpp_entry_voice_capability( - family, entry.get("task") or "tts", entry.get("id") or "") - if capability == AUDIOCPP_VOICE_CLONE: - # The generic clone capability is refined by the family's - # voice policy: pure-TTS families need no voice at all, mixed - # families may run with or without one, clone-only families - # (and unknown families) always clone a reference. - capability = { - AUDIOCPP_VOICE_NONE: "tts", - AUDIOCPP_VOICE_OPTIONAL: "tts/clone", - AUDIOCPP_VOICE_REQUIRED: "clone", - }[audiocpp_family_voice_policy(family)] - return f"{entry.get('id') or '':<{id_width}} ({capability})" + task = entry.get("task") or "tts" + model_id = entry.get("id") or "" + capability = audiocpp_entry_voice_capability(family, task, model_id) + if capability == AUDIOCPP_VOICE_SPEAKER: + return ("speaker", "", "") + if capability == AUDIOCPP_VOICE_DESIGN: + return ("", "", "design") + # The generic clone capability is refined by the family's voice + # policy: pure-TTS families need no voice at all, mixed families + # may run with or without one, clone-only families (and unknown + # families) always clone a reference. + words = { + AUDIOCPP_VOICE_NONE: ("tts", "", ""), + AUDIOCPP_VOICE_OPTIONAL: ("tts", "clone", ""), + AUDIOCPP_VOICE_REQUIRED: ("", "clone", ""), + }[audiocpp_family_voice_policy(family)] + if audiocpp_entry_supports_design(family, task, model_id): + return words[:2] + ("design",) + return words + + _capability_words = [_capabilities(entry) for entry in models] + _column_widths = [max((len(words[index]) + for words in _capability_words), default=0) + for index in range(3)] + + def _label(entry: dict) -> str: + words = _capabilities(entry) + row = f"{entry.get('id') or '':<{id_width}}" + for word, width in zip(words, _column_widths): + if width: + row += f" {word:<{width}}" + return row.rstrip() def entry_supports_options(fs) -> bool: """True when the selected entry's family defines request options. |
