diff --git a/desktop/src/components/chat/ChatInput.test.tsx b/desktop/src/components/chat/ChatInput.test.tsx
index 3c7caaa1..471c6d1c 100644
--- a/desktop/src/components/chat/ChatInput.test.tsx
+++ b/desktop/src/components/chat/ChatInput.test.tsx
@@ -69,9 +69,21 @@ vi.mock('../controls/PermissionModeSelector', () => ({
PermissionModeSelector: () => ,
}))
-vi.mock('../controls/ModelSelector', () => ({
- ModelSelector: () => ,
-}))
+vi.mock('../controls/ModelSelector', async () => {
+ const React = await vi.importActual('react')
+ return {
+ ModelSelector: React.forwardRef<{ open: () => void }, Record>((_props, ref) => {
+ const [open, setOpen] = React.useState(false)
+ React.useImperativeHandle(ref, () => ({ open: () => setOpen(true) }), [])
+ return (
+ <>
+
+ {open && Model selector opened
}
+ >
+ )
+ }),
+ }
+})
import { ChatInput } from './ChatInput'
import { useChatStore } from '../../stores/chatStore'
@@ -1335,6 +1347,32 @@ describe('ChatInput file mentions', () => {
})
})
+ it('opens the model selector for /model without sending a user message', async () => {
+ useSettingsStore.setState({
+ chatSendBehavior: 'enter',
+ })
+
+ render()
+
+ await waitFor(() => {
+ expect(mocks.getGitInfo).toHaveBeenCalledWith(sessionId)
+ })
+
+ const input = screen.getByRole('textbox') as HTMLTextAreaElement
+ fireEvent.change(input, {
+ target: {
+ value: '/model',
+ selectionStart: 6,
+ },
+ })
+
+ fireEvent.keyDown(input, { key: 'Enter' })
+
+ expect(mocks.wsSend).not.toHaveBeenCalled()
+ expect(await screen.findByTestId('model-selector-dropdown')).toHaveTextContent('Model selector opened')
+ expect(input).toHaveValue('')
+ })
+
it('prioritizes active-session slash commands by command name when filtering', async () => {
useChatStore.setState({
sessions: {
diff --git a/desktop/src/components/chat/ChatInput.tsx b/desktop/src/components/chat/ChatInput.tsx
index 0aff84d1..36d36c78 100644
--- a/desktop/src/components/chat/ChatInput.tsx
+++ b/desktop/src/components/chat/ChatInput.tsx
@@ -15,7 +15,7 @@ import {
import { sessionsApi, type SessionGitInfo } from '../../api/sessions'
import { agentsApi } from '../../api/agents'
import { PermissionModeSelector } from '../controls/PermissionModeSelector'
-import { ModelSelector } from '../controls/ModelSelector'
+import { ModelSelector, type ModelSelectorHandle } from '../controls/ModelSelector'
import type { AttachmentRef } from '../../types/chat'
import { AttachmentGallery } from './AttachmentGallery'
import { ComposerDropOverlay } from './ComposerDropOverlay'
@@ -109,6 +109,7 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
const textareaRef = useRef(null)
const panelRef = useRef(null)
const fileInputRef = useRef(null)
+ const modelSelectorRef = useRef(null)
const plusMenuRef = useRef(null)
const slashMenuRef = useRef(null)
const fileSearchRef = useRef(null)
@@ -628,6 +629,15 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
return
}
+ if (pendingSlashUiAction?.type === 'model') {
+ modelSelectorRef.current?.open()
+ setComposerInput('')
+ setSlashMenuOpen(false)
+ setFileSearchOpen(false)
+ setPlusMenuOpen(false)
+ return
+ }
+
if (showLaunchControls && (!launchReady || launchTransitioning)) return
const workspaceReferencePrompt = !isMemberSession
@@ -1296,7 +1306,7 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
/>
)}
{!isMemberSession && activeTabId && (
-
+
)}