Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,8 @@ it('offers the advanced models and each model’s supported efforts', async () =
await act(async () =>
model.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
)
expect(document.body.style.pointerEvents).not.toBe('none')
expect(model.getAttribute('aria-description')).toBe('GPT-6 Astra')
const models = [...document.querySelectorAll<HTMLElement>('[role="menuitem"]')]
expect(models.map((item) => item.textContent)).toEqual(['GPT-6 Astra', 'GPT-6 Sol', 'Opus 5.5'])
await act(async () => models[1].click())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ export function GrowingTextarea({
ref={inputRef}
value={value}
rows={1}
className={cn(TEXTAREA_BASE_CLASSES, compact && 'px-0 py-[3px]', className)}
className={cn(
TEXTAREA_BASE_CLASSES,
compact && 'whitespace-pre px-0 py-[3px] [overflow-wrap:normal]',
className
)}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ export function InputToolbar({
<div
className={
editor
? 'grid grid-cols-[auto_minmax(0,1fr)_auto] items-end gap-x-3'
: 'flex items-center justify-between'
? '@container/input-toolbar grid grid-cols-[auto_minmax(0,1fr)_auto] items-end gap-x-3'
: '@container/input-toolbar flex flex-wrap items-center justify-between gap-y-1'
}
>
<div
ref={leadingRef}
className={cn(
'flex h-[30px] items-center gap-1',
'flex h-[30px] shrink-0 items-center @max-[280px]/input-toolbar:gap-0 gap-1',
editor && 'col-start-1',
editor && (expanded ? 'row-start-2' : 'row-start-1')
)}
Expand All @@ -60,15 +60,15 @@ export function InputToolbar({
<div
ref={trailingRef}
className={cn(
'flex h-[30px] items-center gap-1.5',
'ml-auto flex h-[30px] shrink-0 items-center @max-[280px]/input-toolbar:gap-0 gap-1',
editor && 'col-start-3',
editor && (expanded ? 'row-start-2' : 'row-start-1')
)}
>
{trailingControls ?? (
<>
{(selectionControl || showModelSelector || voiceControl) && (
<div className='flex items-center'>
<div className='flex items-center gap-[inherit]'>
{selectionControl ?? (showModelSelector && <ModelSelector />)}
{voiceControl}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
'use client'

import {
Chip,
ChipDropdown,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuItemLabel,
DropdownMenuRadioGroup,
DropdownMenuTrigger,
} from '@sim/emcn'
import { Brain, Check, Sparkles } from '@sim/emcn/icons'
import {
MOTHERSHIP_MODEL_OPTIONS,
MOTHERSHIP_SIMPLE_EFFORT_OPTIONS,
mothershipEffortOptions,
resolveMothershipModelSettings,
} from '@/lib/mothership/model-options'
import { FastModeToggle } from '@/app/workspace/[workspaceId]/home/components/user-input/components/fast-mode-toggle'
import { ModelSettingTrigger } from '@/app/workspace/[workspaceId]/home/components/user-input/components/model-setting-trigger'
import { useFeatureFlag } from '@/app/workspace/[workspaceId]/providers/feature-flags-provider'
import { useMothershipEffortStore } from '@/stores/mothership-effort/store'

/** Reasoning effort and Fast mode for Build chat composers. */
/** Model, reasoning effort, and Fast mode for Build chat composers. */
export function ModelSelector() {
const advanced = useFeatureFlag('mothership-model-selector')
const selection = useMothershipEffortStore((state) => state.modelSelection)
Expand All @@ -34,8 +34,13 @@ export function ModelSelector() {
? mothershipEffortOptions(modelSelection.model)
: MOTHERSHIP_SIMPLE_EFFORT_OPTIONS
const setEffort = useMothershipEffortStore((state) => state.setEffort)
const effortLabel = options.find((option) => option.value === effort)?.label ?? effort
const modelLabel =
MOTHERSHIP_MODEL_OPTIONS.find((option) => option.value === modelSelection.model)?.label ??
modelSelection.model

return (
<div className='flex items-center'>
<div className='flex items-center gap-[inherit]'>
{advanced && (
<>
{modelSelection.model !== 'claude-opus-5-5' && (
Expand All @@ -45,26 +50,28 @@ export function ModelSelector() {
description='Faster responses at a higher price'
/>
)}
<ChipDropdown
variant='default'
className='border-0'
aria-label='Model'
value={modelSelection.model}
options={MOTHERSHIP_MODEL_OPTIONS}
matchTriggerWidth={false}
onChange={(value) => {
const model = MOTHERSHIP_MODEL_OPTIONS.find((option) => option.value === value)
if (model) setModel(model.value)
}}
/>
<DropdownMenu modal={false}>
<ModelSettingTrigger
label='Model'
valueLabel={modelLabel}
icon={Sparkles}
showChevron
/>
<DropdownMenuContent side='top' align='end'>
{MOTHERSHIP_MODEL_OPTIONS.map((option) => (
<DropdownMenuItem key={option.value} onSelect={() => setModel(option.value)}>
<DropdownMenuItemLabel label={option.label} />
{modelSelection.model === option.value && (
<Check className='ml-auto! size-[16px]!' />
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Chip aria-label='Reasoning effort' className={advanced ? undefined : '-ml-2'}>
{options.find((option) => option.value === effort)?.label}
</Chip>
</DropdownMenuTrigger>
<ModelSettingTrigger label='Reasoning effort' valueLabel={effortLabel} icon={Brain} />
<DropdownMenuContent side='top' align='start'>
<DropdownMenuRadioGroup aria-label='Reasoning effort'>
{options.map((option) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { ComponentType } from 'react'
import { Chip, chipContentLabelClass, DropdownMenuTrigger, OverflowText, Tooltip } from '@sim/emcn'
import { ChevronDown } from '@sim/emcn/icons'

interface ModelSettingTriggerProps {
label: string
valueLabel: string
icon: ComponentType<{ className?: string }>
showChevron?: boolean
}

/** Keeps one accessible trigger while replacing its label with an icon in narrow composers. */
export function ModelSettingTrigger({
label,
valueLabel,
icon: Icon,
showChevron = false,
}: ModelSettingTriggerProps) {
return (
<Tooltip.Root preferAbove>
<Tooltip.Trigger asChild>
<DropdownMenuTrigger asChild>
<Chip
aria-label={label}
aria-description={valueLabel}
leftAdornment={
<>
<Icon className='@max-[320px]/input-toolbar:block hidden size-[14px] text-[var(--text-icon)]' />
<span className='flex @max-[320px]/input-toolbar:hidden min-w-0 items-center gap-2'>
<OverflowText
label={valueLabel}
className={chipContentLabelClass}
focusTarget='nearest-interactive'
/>
{showChevron && (
<ChevronDown className='size-[14px] shrink-0 text-[var(--text-icon)]' />
)}
</span>
</>
}
/>
</DropdownMenuTrigger>
</Tooltip.Trigger>
<Tooltip.Content>
{label}: {valueLabel}
</Tooltip.Content>
</Tooltip.Root>
)
}
Loading