@@ -53,7 +53,7 @@ import {
5353 isEditableElement ,
5454} from "@/browser/utils/ui/keybinds" ;
5555import { ModelSelector , type ModelSelectorRef } from "../ModelSelector" ;
56- import { useModelLRU } from "@/browser/hooks/useModelLRU " ;
56+ import { useModelsFromSettings } from "@/browser/hooks/useModelsFromSettings " ;
5757import { SendHorizontal , X } from "lucide-react" ;
5858import { VimTextArea } from "../VimTextArea" ;
5959import { ImageAttachments , type ImageAttachment } from "../ImageAttachments" ;
@@ -180,7 +180,16 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
180180 const { open } = useSettings ( ) ;
181181 const { selectedWorkspace } = useWorkspaceContext ( ) ;
182182 const [ mode , setMode ] = useMode ( ) ;
183- const { recentModels, addModel, defaultModel, setDefaultModel } = useModelLRU ( ) ;
183+ const {
184+ models,
185+ customModels,
186+ hiddenModels,
187+ hideModel,
188+ unhideModel,
189+ ensureModelInSettings,
190+ defaultModel,
191+ setDefaultModel,
192+ } = useModelsFromSettings ( ) ;
184193 const commandListId = useId ( ) ;
185194 const telemetry = useTelemetry ( ) ;
186195 const [ vimEnabled , setVimEnabled ] = usePersistedState < boolean > ( VIM_ENABLED_KEY , false , {
@@ -229,6 +238,14 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
229238 // - baseModel: canonical format for UI display and policy checks (e.g., ThinkingSlider)
230239 const preferredModel = sendMessageOptions . model ;
231240 const baseModel = sendMessageOptions . baseModel ;
241+
242+ const setPreferredModel = useCallback (
243+ ( model : string ) => {
244+ ensureModelInSettings ( model ) ; // Ensure model exists in Settings
245+ updatePersistedState ( storageKeys . modelKey , model ) ; // Update workspace or project-specific
246+ } ,
247+ [ storageKeys . modelKey , ensureModelInSettings ]
248+ ) ;
232249 const deferredModel = useDeferredValue ( preferredModel ) ;
233250 const deferredInput = useDeferredValue ( input ) ;
234251 const tokenCountPromise = useMemo ( ( ) => {
@@ -243,15 +260,29 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
243260 [ tokenCountPromise ]
244261 ) ;
245262
246- // Setter for model - updates localStorage directly so useSendMessageOptions picks it up
247- const setPreferredModel = useCallback (
248- ( model : string ) => {
249- addModel ( model ) ; // Update LRU
250- updatePersistedState ( storageKeys . modelKey , model ) ; // Update workspace or project-specific
251- } ,
252- [ storageKeys . modelKey , addModel ]
263+ // Model cycling candidates. Prefer the user's custom model list (as configured in Settings).
264+ // If no custom models are configured, fall back to the full suggested list.
265+ const cycleModels = useMemo (
266+ ( ) => ( customModels . length > 0 ? customModels : models ) ,
267+ [ customModels , models ]
253268 ) ;
254269
270+ const cycleToNextModel = useCallback ( ( ) => {
271+ if ( cycleModels . length < 2 ) {
272+ return ;
273+ }
274+
275+ const currentIndex = cycleModels . indexOf ( baseModel ) ;
276+ const nextIndex = currentIndex === - 1 ? 0 : ( currentIndex + 1 ) % cycleModels . length ;
277+ const nextModel = cycleModels [ nextIndex ] ;
278+ if ( nextModel ) {
279+ setPreferredModel ( nextModel ) ;
280+ }
281+ } , [ baseModel , cycleModels , setPreferredModel ] ) ;
282+
283+ const openModelSelector = useCallback ( ( ) => {
284+ modelSelectorRef . current ?. open ( ) ;
285+ } , [ ] ) ;
255286 // Creation-specific state (hook always called, but only used when variant === "creation")
256287 // This avoids conditional hook calls which violate React rules
257288 const creationState = useCreationWorkspace (
@@ -388,14 +419,21 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
388419 if ( matchesKeybind ( event , KEYBINDS . FOCUS_INPUT_A ) ) {
389420 event . preventDefault ( ) ;
390421 focusMessageInput ( ) ;
422+ return ;
423+ }
424+
425+ if ( matchesKeybind ( event , KEYBINDS . CYCLE_MODEL ) ) {
426+ event . preventDefault ( ) ;
427+ focusMessageInput ( ) ;
428+ cycleToNextModel ( ) ;
391429 }
392430 } ;
393431
394432 window . addEventListener ( "keydown" , handleGlobalKeyDown ) ;
395433 return ( ) => {
396434 window . removeEventListener ( "keydown" , handleGlobalKeyDown ) ;
397435 } ;
398- } , [ focusMessageInput ] ) ;
436+ } , [ cycleToNextModel , focusMessageInput , openModelSelector ] ) ;
399437
400438 // When entering editing mode, save current draft and populate with message content
401439 useEffect ( ( ) => {
@@ -1221,10 +1259,10 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
12211259 return ;
12221260 }
12231261
1224- // Handle open model selector
1225- if ( matchesKeybind ( e , KEYBINDS . OPEN_MODEL_SELECTOR ) ) {
1262+ // Cycle models (Ctrl+/)
1263+ if ( matchesKeybind ( e , KEYBINDS . CYCLE_MODEL ) ) {
12261264 e . preventDefault ( ) ;
1227- modelSelectorRef . current ?. open ( ) ;
1265+ cycleToNextModel ( ) ;
12281266 return ;
12291267 }
12301268
@@ -1306,7 +1344,7 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
13061344 hints . push ( `${ formatKeybind ( interruptKeybind ) } to interrupt` ) ;
13071345 }
13081346 hints . push ( `${ formatKeybind ( KEYBINDS . SEND_MESSAGE ) } to ${ canInterrupt ? "queue" : "send" } ` ) ;
1309- hints . push ( `${ formatKeybind ( KEYBINDS . OPEN_MODEL_SELECTOR ) } to change model ` ) ;
1347+ hints . push ( `Click model to choose, ${ formatKeybind ( KEYBINDS . CYCLE_MODEL ) } to cycle ` ) ;
13101348 hints . push ( `/vim to toggle Vim mode (${ vimEnabled ? "on" : "off" } )` ) ;
13111349
13121350 return `Type a message... (${ hints . join ( ", " ) } )` ;
@@ -1500,18 +1538,23 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
15001538 ref = { modelSelectorRef }
15011539 value = { baseModel }
15021540 onChange = { setPreferredModel }
1503- recentModels = { recentModels }
1541+ models = { models }
15041542 onComplete = { ( ) => inputRef . current ?. focus ( ) }
15051543 defaultModel = { defaultModel }
15061544 onSetDefaultModel = { setDefaultModel }
1545+ onHideModel = { hideModel }
1546+ hiddenModels = { hiddenModels }
1547+ onUnhideModel = { unhideModel }
1548+ onOpenSettings = { ( ) => open ( "models" ) }
15071549 />
15081550 < Tooltip >
15091551 < TooltipTrigger asChild >
15101552 < HelpIndicator > ?</ HelpIndicator >
15111553 </ TooltipTrigger >
15121554 < TooltipContent align = "start" className = "max-w-80 whitespace-normal" >
1513- < strong > Click to edit</ strong > or use{ " " }
1514- { formatKeybind ( KEYBINDS . OPEN_MODEL_SELECTOR ) }
1555+ < strong > Click to edit</ strong >
1556+ < br />
1557+ < strong > { formatKeybind ( KEYBINDS . CYCLE_MODEL ) } </ strong > to cycle models
15151558 < br />
15161559 < br />
15171560 < strong > Abbreviations:</ strong >
0 commit comments