feat: live choice lists for model, voice and crosstalk shows (PR-11, STORY-479/482) - #852
Merged
Merged
Conversation
Re-planned live choice lists: model + voice probes (T579/T580/T581) and Crosstalk:Shows as checkboxes (T585/T586). Skipped facts + it.todo until each task turns its scenarios green.
ProbedChoiceCache holds one entry per probe, keyed on the probe's endpoint. Results stay fresh for 60 s, failures included, so a down backend costs at most one call a minute. Each attempt is single-flight with a 2 s timeout on TimeProvider. A failure after a success on the same endpoint serves the last good list as Stale; an endpoint change drops it. Un-skips the cache-level STORY-479 facts (AC4-AC6, AC11-AC15) plus a concurrent single-flight scenario.
…Y-479)
Llm:Model and Station:Voice become Choice settings whose lists come from
the servers themselves: GET {Llm:Endpoint}/v1/models (new OpenAiModelLister
behind ILlmModelLister in Core) and the existing Kokoro voice listing. Both
go through ProbedChoiceCache (60 s, last-good per endpoint, 2 s timeout).
ISettingChoiceResolver replaces the controller's ChoicesFor/
LocalizedChoicesFor and resolves only the keys a response carries, so a PUT
of an unrelated key never waits on the LLM or TTS server. SettingDto gains
choicesStale and choicesFailed. A saved value missing from its list is
appended as "{0} (not found)" (resx Choice.NotFound) - this now applies to
Station:Theme and Station:IconPack as well, per F205.7e. PUT validation is
unchanged: a model or voice not in the list is still accepted.
Boot fails when an allowlist ChoiceSource names a probe that DI did not
register. The probes take their listers lazily so booting makes no LLM or
TTS request. Llm:Model help copy no longer says "free text".
…API (STORY-482) Crosstalk:Shows becomes SettingKind.MultiChoice (wire "multi-choice") sourced from the new "shows" IChoiceCatalog, which reads IShowStore on every request and returns (slug, name) pairs. A store failure flags choicesFailed for that key only; only the caller's own cancellation propagates. Each saved slug missing from the list is appended once as "<slug> (not found)"; blanks never. The stored value and PUT validation are unchanged. The boot check now takes catalog names from DI, KindWireValue is exhaustive, and an architecture law pins F205.7h (MultiChoice needs a live source or a choices list).
…Y-479) ChoiceSettingControl reads the new choicesStale/choicesFailed flags: a stale list adds "This list may be out of date."; a failed list disables the select, still holding the saved value, with "Couldn't load the list." A blank value shows a disabled "Choose…" placeholder (or the flagged station default), so nothing blank is offered and no path renders a text box. VoiceSettingControl and its free-text fallback are deleted; Station:Voice now uses the generic dropdown fed by the settings API. useVoiceList and /api/voices stay for the other pages. The retired voice spec's badge fact moves to the choice spec.
A new MultiChoiceSettingControl shows one labelled checkbox per choice. The checkboxes sit in a role="group" that the field label names. On save the control sends the checked slugs as a JSON array, in list order. When nothing is checked it sends "", so the unset default stays clean. Under choicesFailed every checkbox is disabled. The stale and failed notes match ChoiceSettingControl. No path renders a text input. SettingsForm now routes kind "multi-choice" to the new control, and the kind union in settings-types.ts gains "multi-choice". Story482 AC4, AC5 and AC8 are real specs now.
T576 gave the settings number input min and max but no step, so the browser's default step of 1 applied. The shipped value 0.5 for Library:CueDetection:MinSilenceDurationSec then failed native constraint validation. Every "Save settings" click stopped before the PUT, with no message. Found during the T587 wire check on the dev station. The input now uses step="any". The server still validates each key's range. A new descriptor-form scenario pins the attribute and checks that the form is valid with 0.5.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 What
Three settings now offer only real choices, read live from the station instead of typed by hand (F205.7, STORY-479, STORY-482):
Llm:Model) lists the models the LLM server reports on/v1/models.Station:Voice) lists the voices Kokoro reports.Crosstalk:Shows) shows a checkbox per show, where it used to be a hand-typed JSON array.No text box remains on any of these fields. A saved value that is no longer in the list still shows, as " (not found)".
This PR also fixes a bug already on
main: no setting could be saved from the settings page. Details are under 🐛 below.Refs #778. This is the last PR of the settings rework.
✨ Changes
GET /api/settings.choices,choicesStaleandchoicesFailed.PUTvalidation is unchanged: a value is never rejected because the list doesn't contain it.multi-choicekind.Crosstalk:Showsreports the failure; the rest of the page still loads.VoiceSettingControland its free-text fallback are deleted.🐛 Fix: fractional number settings blocked every save
T576 (#847) gave the settings number fields
minandmaxlimits but nostep. The browser then only accepted whole numbers, and the shipped value0.5forLibrary:CueDetection:MinSilenceDurationSecfailed its check. As a result, every "Save settings" click silently did nothing. The browser sent no request and showed no message.The fields now accept decimals (
step="any"), and the server still checks each setting's range. This bug is onmainbut not in any release. The T587 wire check found it.✅ Verification
Test suites:
tsc,typecheck:specsand jest all pass (126 suites, 1371 tests).dotnet buildfinished with 0 errors. Test counts:Wire check (T587) on the dev station, running images built from this branch:
/v1/modelsreturns.mistral:7b (not found), showed "Couldn't load the list." and had no text box./api/voicesreturns."". Ticking it again and saving stored["nova-late-nite"], back to the original value.In check 2 the outage was simulated in the api container only: runes pointed at an unroutable address in its
/etc/hosts. Ollama on runes kept running.