fix: union the authoritative vision-model list into VisionProviderPicker (#4157) - #4264
Merged
Conversation
…ker (#4157) The universe builder's vision picker filtered local backends by the client id regex alone, so an installed VLM from a family the regex predates was hidden and the picker rendered a "no vision-capable model installed" blocker. Every other vision picker already unions the server's per-provider capability set; this one now does too. That set arrives after the first provider load, which changes the `modelFilter`'s identity — and `useProviderModels` handled that badly in two ways: `load` depended on the filter, so a widened filter re-ran `api.getProviders()` for data it already had; and `hasSetInitialRef` froze the auto-pick at whatever the first, blinder filter produced, so the authoritative list never got a say. The hook now re-runs its initial pick on a filter change without refetching, and stands down permanently once the user picks or clears a model — `''` alone can't tell a deliberate clear from "the filter matched nothing", so a latch does. The picker also holds its blocker until the capability scan settles, so it no longer flashes "no vision model" mid-fetch on a local backend.
…ect, not during render
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
VisionProviderPicker(the universe builder's image-describe, style-reference and corrective-reference pickers) filtered local backends with the client-side id regex alone, so an installed VLM from a family the regex predates was hidden — the picker showed a "no vision-capable model installed" blocker next to an empty dropdown. Every other vision picker (AiAssignmentsTab,CreativeDirectorModelsDrawer,PromptFromMedia) already unions the server's authoritative per-provider set fromuseVisionModelIds; this one now passes it asvisionLocalModelFilter's third argument too.That set resolves after the first provider load, so the
modelFilter's identity changes — whichuseProviderModelshandled badly in two ways, both fixed here:loaddepended onpickInitialModel(and so onmodelFilter), so a filter change re-ran the wholeapi.getProviders()fetch for data it already had. The picker now reaches the async body through a ref, keepingload's identity stable.hasSetInitialReffroze the auto-pick at whatever the first, blinder filter produced, so the authoritative list never got a say —selectedModelstayed''whileavailableModelsfilled in. A new effect re-runs the initial pick when the filter's identity changes and the current selection is no longer in the list. It stands down permanently once the user picks or clears a model:''alone can't distinguish a deliberate clear from "the filter matched nothing", so auserPickedModelReflatch carries that distinction. Scoped to filtered, non-allowDefaultpickers, so the ~25 other consumers of the hook are untouched.The picker also holds its "no vision model" blocker until the capability scan settles on a local backend (the
visionPendingidiom already used inStackerNews/CreativeDirectorModelsDrawer), so it no longer flashes the blocker mid-fetch, and it reports that window asloadingto its callers.PromptFromMediapasses a filter with the same changing identity and silently inherits both fixes.Test plan
client/src/hooks/useProviderModels.test.js— new block for amodelFilterwhose identity changes: re-picks once the filter widens; no secondgetProviders()call; leaves a deliberate clear and a user-picked model alone; re-arms after a provider change;allowDefaultstill keeps the empty-model sentinel.client/src/components/universe/VisionProviderPicker.test.jsx— new file (the component had none): offers a VLM the id regex doesn't recognize once the server list lands, still blocks when the backend genuinely has none, doesn't flash the blocker mid-scan, leaves a cloud provider unfiltered, and explains the no-provider case.VisionDescribeModal.test.jsxstubsuseVisionModelIds— the picker now runs a capability scan on mount, which the suite would otherwise hit outsideact().cd client && npx vitest run→ 646 files, 7877 tests passing (one unrelatedQuotaBurn.test.jsxparallel-run flake, green in isolation).npx biome lint --error-on-warningsclean on every touched file.Closes #4157