Skip to content

Commit ec0940b

Browse files
authored
fix: improve model selector on mobile (#1983)
1 parent d7e737e commit ec0940b

4 files changed

Lines changed: 87 additions & 72 deletions

File tree

src/browser/components/ChatInput/index.tsx

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,67 +2186,71 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
21862186
</div>
21872187
)}
21882188

2189-
<div className="@container flex flex-wrap items-center gap-x-3 gap-y-1">
2190-
{/* Model Selector - always visible */}
2191-
<div
2192-
className="flex items-center"
2193-
data-component="ModelSelectorGroup"
2194-
data-tutorial="model-selector"
2195-
>
2196-
<ModelSelector
2197-
ref={modelSelectorRef}
2198-
value={baseModel}
2199-
onChange={setPreferredModel}
2200-
models={models}
2201-
onComplete={() => inputRef.current?.focus()}
2202-
defaultModel={defaultModel}
2203-
onSetDefaultModel={setDefaultModel}
2204-
onHideModel={hideModel}
2205-
hiddenModels={hiddenModels}
2206-
onUnhideModel={unhideModel}
2207-
onOpenSettings={() => open("models")}
2208-
/>
2209-
<Tooltip>
2210-
<TooltipTrigger asChild>
2211-
<HelpIndicator>?</HelpIndicator>
2212-
</TooltipTrigger>
2213-
<TooltipContent align="start" className="max-w-80 whitespace-normal">
2214-
<strong>Click to edit</strong>
2215-
<br />
2216-
<strong>{formatKeybind(KEYBINDS.CYCLE_MODEL)}</strong> to cycle models
2217-
<br />
2218-
<br />
2219-
<strong>Abbreviations:</strong>
2220-
{MODEL_ABBREVIATION_EXAMPLES.map((ex) => (
2221-
<React.Fragment key={ex.abbrev}>
2222-
<br /><code>/model {ex.abbrev}</code> - {ex.displayName}
2223-
</React.Fragment>
2224-
))}
2225-
<br />
2226-
<br />
2227-
<strong>Full format:</strong>
2228-
<br />
2229-
<code>/model provider:model-name</code>
2230-
<br />
2231-
(e.g., <code>/model anthropic:claude-sonnet-4-5</code>)
2232-
</TooltipContent>
2233-
</Tooltip>
2234-
</div>
2235-
2236-
{/* Thinking Slider - slider hidden on narrow containers, label always clickable */}
2237-
<div
2238-
className="flex items-center [&_.thinking-slider]:[@container(max-width:550px)]:hidden"
2239-
data-component="ThinkingSliderGroup"
2240-
>
2241-
<ThinkingSliderComponent modelString={baseModel} />
2242-
</div>
2189+
<div className="@container flex flex-wrap items-center gap-x-3 gap-y-1 [@container(max-width:480px)]:flex-col [@container(max-width:480px)]:items-stretch [@container(max-width:480px)]:gap-1">
2190+
{/* Row 1 on mobile: Model Selector + Thinking Slider */}
2191+
<div className="flex items-center gap-x-3 [@container(max-width:480px)]:w-full">
2192+
<div
2193+
className="flex items-center"
2194+
data-component="ModelSelectorGroup"
2195+
data-tutorial="model-selector"
2196+
>
2197+
<ModelSelector
2198+
ref={modelSelectorRef}
2199+
value={baseModel}
2200+
onChange={setPreferredModel}
2201+
models={models}
2202+
onComplete={() => inputRef.current?.focus()}
2203+
defaultModel={defaultModel}
2204+
onSetDefaultModel={setDefaultModel}
2205+
onHideModel={hideModel}
2206+
hiddenModels={hiddenModels}
2207+
onUnhideModel={unhideModel}
2208+
onOpenSettings={() => open("models")}
2209+
/>
2210+
<div className="hidden [@media(hover:hover)_and_(pointer:fine)]:block">
2211+
<Tooltip>
2212+
<TooltipTrigger asChild>
2213+
<HelpIndicator>?</HelpIndicator>
2214+
</TooltipTrigger>
2215+
<TooltipContent align="start" className="max-w-80 whitespace-normal">
2216+
<strong>Click to edit</strong>
2217+
<br />
2218+
<strong>{formatKeybind(KEYBINDS.CYCLE_MODEL)}</strong> to cycle models
2219+
<br />
2220+
<br />
2221+
<strong>Abbreviations:</strong>
2222+
{MODEL_ABBREVIATION_EXAMPLES.map((ex) => (
2223+
<React.Fragment key={ex.abbrev}>
2224+
<br /><code>/model {ex.abbrev}</code> - {ex.displayName}
2225+
</React.Fragment>
2226+
))}
2227+
<br />
2228+
<br />
2229+
<strong>Full format:</strong>
2230+
<br />
2231+
<code>/model provider:model-name</code>
2232+
<br />
2233+
(e.g., <code>/model anthropic:claude-sonnet-4-5</code>)
2234+
</TooltipContent>
2235+
</Tooltip>
2236+
</div>
2237+
</div>
22432238

2244-
<div className="ml-4 flex items-center" data-component="ModelSettingsGroup">
2245-
<ModelSettings model={baseModel || ""} />
2239+
{/* Thinking Slider - slider hidden on narrow containers, label always clickable */}
2240+
<div
2241+
className="flex items-center [&_.thinking-slider]:[@container(max-width:550px)]:hidden"
2242+
data-component="ThinkingSliderGroup"
2243+
>
2244+
<ThinkingSliderComponent modelString={baseModel} />
2245+
</div>
2246+
<div className="ml-4 flex items-center" data-component="ModelSettingsGroup">
2247+
<ModelSettings model={baseModel || ""} />
2248+
</div>
22462249
</div>
22472250

2251+
{/* Row 2 on mobile: Context Usage + Agent Mode + Send Button */}
22482252
<div
2249-
className="ml-auto flex items-center gap-2"
2253+
className="ml-auto flex items-center gap-2 [@container(max-width:480px)]:ml-0 [@container(max-width:480px)]:w-full [@container(max-width:480px)]:justify-end"
22502254
data-component="ModelControls"
22512255
data-tutorial="mode-selector"
22522256
>
@@ -2267,11 +2271,16 @@ const ChatInputInner: React.FC<ChatInputProps> = (props) => {
22672271
aria-label="Send message"
22682272
style={{ backgroundColor: focusBorderColor }}
22692273
className={cn(
2270-
"border-border-light inline-flex items-center gap-1 rounded-sm border px-1.5 py-0.5 text-[11px] font-medium transition-colors duration-200 hover:brightness-110 disabled:opacity-50 disabled:hover:brightness-100",
2274+
"border-border-light inline-flex items-center justify-center rounded-sm border px-1.5 py-0.5 text-[11px] font-medium transition-colors duration-200 hover:brightness-110 disabled:opacity-50 disabled:hover:brightness-100",
2275+
// Mobile: wider tap target + larger icon, keep icon centered.
2276+
"[@container(max-width:480px)]:h-9 [@container(max-width:480px)]:w-11 [@container(max-width:480px)]:px-0 [@container(max-width:480px)]:py-0 [@container(max-width:480px)]:text-sm",
22712277
currentAgent?.uiColor ? "text-white" : "text-text"
22722278
)}
22732279
>
2274-
<SendHorizontal className="h-3.5 w-3.5" strokeWidth={2.5} />
2280+
<SendHorizontal
2281+
className="h-3.5 w-3.5 [@container(max-width:480px)]:h-4 [@container(max-width:480px)]:w-4"
2282+
strokeWidth={2.5}
2283+
/>
22752284
</button>
22762285
</TooltipTrigger>
22772286
<TooltipContent align="center">

src/browser/components/ContextUsageIndicatorButton.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,10 @@ export const ContextUsageIndicatorButton: React.FC<ContextUsageIndicatorButtonPr
297297
aria-label={ariaLabel}
298298
aria-expanded={isOpen}
299299
aria-haspopup="dialog"
300-
className="hover:bg-sidebar-hover flex h-6 cursor-pointer items-center gap-1.5 rounded px-1"
300+
className={cn(
301+
"hover:bg-sidebar-hover flex h-6 cursor-pointer items-center gap-1.5 rounded px-1",
302+
"[@container(max-width:480px)]:h-8 [@container(max-width:480px)]:gap-2 [@container(max-width:480px)]:px-2"
303+
)}
301304
type="button"
302305
onClick={handleTriggerClick}
303306
onPointerEnter={handleTriggerPointerEnter}
@@ -306,16 +309,16 @@ export const ContextUsageIndicatorButton: React.FC<ContextUsageIndicatorButtonPr
306309
{/* Idle compaction badge - shows hourglass with hours when enabled */}
307310
{isIdleCompactionEnabled && (
308311
<div
309-
className="text-muted flex items-center gap-0.5 text-[10px]"
312+
className="text-muted flex items-center gap-0.5 text-[10px] [@container(max-width:480px)]:text-xs"
310313
title={`Auto-compact after ${idleHours}h idle`}
311314
>
312-
<Hourglass className="h-3 w-3" />
315+
<Hourglass className="h-3 w-3 [@container(max-width:480px)]:h-3.5 [@container(max-width:480px)]:w-3.5" />
313316
<span>{idleHours}h</span>
314317
</div>
315318
)}
316319
{/* Show meter when there's usage, or show empty placeholder for settings access */}
317320
{data.totalTokens > 0 ? (
318-
<div className="relative h-2 w-20">
321+
<div className="relative h-2 w-20 [@container(max-width:480px)]:w-28">
319322
<TokenMeter
320323
segments={data.segments}
321324
orientation="horizontal"
@@ -328,7 +331,7 @@ export const ContextUsageIndicatorButton: React.FC<ContextUsageIndicatorButtonPr
328331
</div>
329332
) : (
330333
/* Empty meter placeholder - allows access to settings with no usage */
331-
<div className="bg-dark relative h-2 w-20 rounded-full" />
334+
<div className="bg-dark relative h-2 w-20 rounded-full [@container(max-width:480px)]:w-28" />
332335
)}
333336
</button>
334337
</PopoverAnchor>

src/browser/components/ModelSelector.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
309309
)}
310310
</div>
311311
{showDropdown && (
312-
<div className="bg-separator border-border-light absolute bottom-full left-0 z-[1020] mb-1 max-h-[200px] min-w-80 overflow-y-auto rounded border shadow-[0_4px_12px_rgba(0,0,0,0.3)]">
312+
<div className="bg-separator border-border-light absolute bottom-full left-0 z-[1020] mb-1 max-h-[200px] min-w-80 overflow-x-hidden overflow-y-auto rounded border shadow-[0_4px_12px_rgba(0,0,0,0.3)]">
313313
{filteredModels.length === 0 ? (
314314
<div className="text-muted-light font-monospace px-2.5 py-1.5 text-[11px]">
315315
No matching models
@@ -330,7 +330,7 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
330330
onClick={() => handleSelectModel(model)}
331331
>
332332
{/* Grid: model name | gateway | visibility | default */}
333-
<div className="grid w-full grid-cols-[1fr_20px_20px_20px] items-center gap-1">
333+
<div className="grid w-full min-w-0 grid-cols-[1fr_20px_30px_30px] items-center gap-1">
334334
<span className="min-w-0 truncate">{model}</span>
335335
{/* Gateway toggle */}
336336
{gateway.canToggleModel(model) ? (
@@ -401,7 +401,7 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
401401
>
402402
<Eye
403403
className={cn(
404-
"h-3 w-3",
404+
"h-4 w-4 md:h-3 md:w-3",
405405
hiddenSet.has(model) ? "opacity-30" : "opacity-70"
406406
)}
407407
/>
@@ -431,10 +431,10 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
431431
onMouseDown={(e) => e.preventDefault()}
432432
onClick={(e) => handleSetDefault(e, model)}
433433
className={cn(
434-
"flex h-5 w-5 items-center justify-center rounded-sm border transition-colors duration-150",
434+
"flex h-5 w-5 items-center justify-center rounded-sm transition-colors duration-150",
435435
defaultModel === model
436-
? "text-yellow-400 border-yellow-400/40 cursor-default"
437-
: "text-muted-light border-border-light/40 hover:border-foreground/60 hover:text-foreground"
436+
? "text-yellow-400 cursor-default"
437+
: "text-muted-light hover:text-foreground"
438438
)}
439439
aria-label={
440440
defaultModel === model
@@ -443,7 +443,10 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
443443
}
444444
disabled={defaultModel === model}
445445
>
446-
<Star className="h-3 w-3" />
446+
<Star
447+
className="h-4 w-4 md:h-3 md:w-3"
448+
fill={defaultModel === model ? "currentColor" : "none"}
449+
/>
447450
</button>
448451
</TooltipTrigger>
449452
<TooltipContent align="center">

src/browser/components/ThinkingSlider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ export const ThinkingSliderComponent: React.FC<ThinkingControlProps> = ({ modelS
179179
<button
180180
type="button"
181181
onClick={cycleThinkingLevel}
182-
className="cursor-pointer border-none bg-transparent p-0"
182+
className="flex cursor-pointer items-center border-none bg-transparent p-0"
183183
aria-label={`Thinking level: ${effectiveThinkingLevel}. Click to cycle.`}
184184
>
185185
<span

0 commit comments

Comments
 (0)