diff --git a/apps/desktop/src/app/session/hooks/use-model-controls.ts b/apps/desktop/src/app/session/hooks/use-model-controls.ts index 7d59b8e40875..edc2747e5996 100644 --- a/apps/desktop/src/app/session/hooks/use-model-controls.ts +++ b/apps/desktop/src/app/session/hooks/use-model-controls.ts @@ -53,52 +53,55 @@ export function useModelControls({ queryClient, requestGateway }: ModelControlsO // only fills an EMPTY selection so a user's pick (plain UI state in // $currentModel) survives the lifecycle refreshes that fire on boot / fresh // draft / session events. A live session owns the footer, so skip entirely. - const refreshCurrentModel = useCallback(async (force = false) => { - try { - if ($activeSessionId.get()) { - return - } - - // A manual pick stays sticky UNLESS it was removed from the catalog (its - // model no longer exists on the provider), in which case keeping it would - // 404 every new chat — fall through to reseed from the profile default. - // Reads the model-options cache the composer already populated; an - // unknown/not-yet-loaded catalog conservatively preserves the pick. - const keepManualPick = () => { - if (force || !$currentModel.get() || getCurrentModelSource() !== 'manual') { - return false + const refreshCurrentModel = useCallback( + async (force = false) => { + try { + if ($activeSessionId.get()) { + return } - const options = queryClient.getQueryData(['model-options', 'global']) + // A manual pick stays sticky UNLESS it was removed from the catalog (its + // model no longer exists on the provider), in which case keeping it would + // 404 every new chat — fall through to reseed from the profile default. + // Reads the model-options cache the composer already populated; an + // unknown/not-yet-loaded catalog conservatively preserves the pick. + const keepManualPick = () => { + if (force || !$currentModel.get() || getCurrentModelSource() !== 'manual') { + return false + } - return !manualPickRemoved(options?.providers, $currentProvider.get(), $currentModel.get()) + const options = queryClient.getQueryData(['model-options', 'global']) + + return !manualPickRemoved(options?.providers, $currentProvider.get(), $currentModel.get()) + } + + if (keepManualPick()) { + return + } + + const result = await getGlobalModelInfo() + + if ($activeSessionId.get() || keepManualPick()) { + return + } + + if (typeof result.model === 'string') { + setCurrentModel(result.model) + } + + if (typeof result.provider === 'string') { + setCurrentProvider(result.provider) + } + + if (typeof result.model === 'string' || typeof result.provider === 'string') { + setCurrentModelSource('default') + } + } catch { + // The delayed session.info event still updates this once the agent is ready. } - - if (keepManualPick()) { - return - } - - const result = await getGlobalModelInfo() - - if ($activeSessionId.get() || keepManualPick()) { - return - } - - if (typeof result.model === 'string') { - setCurrentModel(result.model) - } - - if (typeof result.provider === 'string') { - setCurrentProvider(result.provider) - } - - if (typeof result.model === 'string' || typeof result.provider === 'string') { - setCurrentModelSource('default') - } - } catch { - // The delayed session.info event still updates this once the agent is ready. - } - }, [queryClient]) + }, + [queryClient] + ) // Returns whether the switch succeeded so callers can await it before applying // follow-up changes. The composer model is plain UI state: with no live diff --git a/apps/desktop/src/components/pane-shell/tree/reactive-unhide.test.ts b/apps/desktop/src/components/pane-shell/tree/reactive-unhide.test.ts index 1699fa034db3..90244a96c271 100644 --- a/apps/desktop/src/components/pane-shell/tree/reactive-unhide.test.ts +++ b/apps/desktop/src/components/pane-shell/tree/reactive-unhide.test.ts @@ -43,16 +43,23 @@ describe('reactive pane unhide', () => { // Declare a minimal default tree mirroring the production DEFAULT_TREE's // row shape (sessions | workspace | right-column-with-files). tree.declareDefaultTree( - model.split('row', [ - model.group(['sessions'], { id: 'grp-sessions' }), - model.group(['workspace'], { id: 'grp-main' }), - model.split('column', [ - model.split('row', [ - model.group(['files'], { id: 'grp-files' }) - ], [1], 'spl-rail'), - model.group(['terminal'], { id: 'grp-terminal' }) - ], [1.6, 1], 'spl-right') - ], [0.85, 3, 1.6]) + model.split( + 'row', + [ + model.group(['sessions'], { id: 'grp-sessions' }), + model.group(['workspace'], { id: 'grp-main' }), + model.split( + 'column', + [ + model.split('row', [model.group(['files'], { id: 'grp-files' })], [1], 'spl-rail'), + model.group(['terminal'], { id: 'grp-terminal' }) + ], + [1.6, 1], + 'spl-right' + ) + ], + [0.85, 3, 1.6] + ) ) // Mirror controller.tsx:512.