From 098378170d89bc0e4f394b916df574eadf8fcf5c Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Sun, 5 Jul 2026 23:23:38 +0530 Subject: [PATCH] default model shows correctly along with the dropdown --- .../components/chat-input-with-mentions.tsx | 55 ++++++++++++++++--- .../src/components/onboarding-modal.tsx | 4 +- .../onboarding/use-onboarding-state.ts | 4 +- .../src/components/settings-dialog.tsx | 4 +- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx b/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx index ba87140d..13540e6f 100644 --- a/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx +++ b/apps/x/apps/renderer/src/components/chat-input-with-mentions.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { ArrowUp, @@ -303,6 +303,11 @@ function ChatInputInner({ const [configuredModels, setConfiguredModels] = useState([]) const [activeModelKey, setActiveModelKey] = useState('') + // The effective runtime default (what a run actually uses when the user + // hasn't picked a model) — shown in the picker instead of guessing from + // list order, which can disagree with the real default. + const [defaultModel, setDefaultModel] = useState(null) + const loadModelConfigEpoch = useRef(0) const [lockedModel, setLockedModel] = useState(null) const [searchEnabled, setSearchEnabled] = useState(false) const [searchAvailable, setSearchAvailable] = useState(false) @@ -398,6 +403,17 @@ function ChatInputInner({ // Load the list of models the user can choose from. // Signed-in: gateway model list. Signed-out: providers configured in models.json. const loadModelConfig = useCallback(async () => { + // Concurrent runs race (mount fires one before the sign-in state resolves, + // which fires another) — only the newest run may write state, else a slow + // stale run can clobber the fresh list with an empty one. + const epoch = ++loadModelConfigEpoch.current + try { + const def = await window.ipc.invoke('llm:getDefaultModel', null) + if (loadModelConfigEpoch.current !== epoch) return + setDefaultModel({ provider: def.provider as ProviderName, model: def.model }) + } catch { + if (loadModelConfigEpoch.current === epoch) setDefaultModel(null) + } try { if (isRowboatConnected) { const listResult = await window.ipc.invoke('models:list', null) @@ -407,6 +423,7 @@ function ChatInputInner({ const models: ConfiguredModel[] = (rowboatProvider?.models || []).map( (m: { id: string }) => ({ provider: 'rowboat', model: m.id }) ) + if (loadModelConfigEpoch.current !== epoch) return setConfiguredModels(models) } else { const result = await window.ipc.invoke('workspace:readFile', { path: 'config/models.json' }) @@ -457,10 +474,12 @@ function ChatInputInner({ for (const m of saved) push(flavor, m) } } + if (loadModelConfigEpoch.current !== epoch) return setConfiguredModels(models) } - } catch { - // No config yet + } catch (err) { + // No config yet — but surface unexpected failures for diagnosis. + console.error('[chat-input] failed to load model list', err) } }, [isRowboatConnected]) @@ -647,15 +666,25 @@ function ChatInputInner({ checkSearch() }, [isActive, isRowboatConnected]) + // The dropdown's items: always include the effective default so the picker + // is never empty (and never missing the model that actually runs) even + // while the full list is still loading. + const pickerModels = useMemo(() => { + if (!defaultModel) return configuredModels + const defaultKey = `${defaultModel.provider}/${defaultModel.model}` + if (configuredModels.some((m) => `${m.provider}/${m.model}` === defaultKey)) return configuredModels + return [defaultModel, ...configuredModels] + }, [configuredModels, defaultModel]) + // Selecting a model affects only the *next* run created from this tab. // Once a run exists, model is frozen on the run and the dropdown is read-only. const handleModelChange = useCallback((key: string) => { if (lockedModel) return - const entry = configuredModels.find((m) => `${m.provider}/${m.model}` === key) + const entry = pickerModels.find((m) => `${m.provider}/${m.model}` === key) if (!entry) return setActiveModelKey(key) onSelectedModelChange?.({ provider: entry.provider, model: entry.model }) - }, [configuredModels, lockedModel, onSelectedModelChange]) + }, [pickerModels, lockedModel, onSelectedModelChange]) // Restore the tab draft when this input mounts. useEffect(() => { @@ -1259,7 +1288,7 @@ function ChatInputInner({ {providerDisplayNames[lockedModel.provider] || lockedModel.provider} — fixed for this chat - ) : configuredModels.length > 0 ? ( + ) : pickerModels.length > 0 ? ( - - {configuredModels.map((m) => { + + {pickerModels.map((m) => { const key = `${m.provider}/${m.model}` return ( diff --git a/apps/x/apps/renderer/src/components/onboarding-modal.tsx b/apps/x/apps/renderer/src/components/onboarding-modal.tsx index 173e085f..fea6c6b3 100644 --- a/apps/x/apps/renderer/src/components/onboarding-modal.tsx +++ b/apps/x/apps/renderer/src/components/onboarding-modal.tsx @@ -183,8 +183,8 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) { // Preferred default models for each provider const preferredDefaults: Partial> = { - openai: "gpt-5.2", - anthropic: "claude-opus-4-6-20260202", + openai: "gpt-5.4", + anthropic: "claude-opus-4-8", } // Initialize default models from catalog diff --git a/apps/x/apps/renderer/src/components/onboarding/use-onboarding-state.ts b/apps/x/apps/renderer/src/components/onboarding/use-onboarding-state.ts index 8e01aae8..d548ef32 100644 --- a/apps/x/apps/renderer/src/components/onboarding/use-onboarding-state.ts +++ b/apps/x/apps/renderer/src/components/onboarding/use-onboarding-state.ts @@ -155,8 +155,8 @@ export function useOnboardingState(open: boolean, onComplete: () => void) { // Preferred default models for each provider const preferredDefaults: Partial> = { - openai: "gpt-5.2", - anthropic: "claude-opus-4-6-20260202", + openai: "gpt-5.4", + anthropic: "claude-opus-4-8", } // Initialize default models from catalog diff --git a/apps/x/apps/renderer/src/components/settings-dialog.tsx b/apps/x/apps/renderer/src/components/settings-dialog.tsx index e89e69f7..259a7253 100644 --- a/apps/x/apps/renderer/src/components/settings-dialog.tsx +++ b/apps/x/apps/renderer/src/components/settings-dialog.tsx @@ -327,8 +327,8 @@ const moreProviders: Array<{ id: LlmProviderFlavor; name: string; description: s ] const preferredDefaults: Partial> = { - openai: "gpt-5.2", - anthropic: "claude-opus-4-6-20260202", + openai: "gpt-5.4", + anthropic: "claude-opus-4-8", } const defaultBaseURLs: Partial> = {