@@ -43,7 +43,7 @@ interface ComposerProps {
4343 /** On the empty home the placeholder types itself and the field is taller; in a chat it is the plain footer input. */
4444 isInitialView : boolean
4545 isSending : boolean
46- onChange : ( value : string ) => void
46+ onChange : ( value : string , contexts ?: ChatContext [ ] ) => void
4747 restoredContexts ?: ChatContext [ ]
4848 onSubmit : ( text : string , contexts ?: ChatContext [ ] ) => void
4949 onStop : ( ) => void
@@ -91,6 +91,7 @@ export function Composer({
9191 organizationId : organization . id ,
9292 contextsEnabled : ! imagesOnly ,
9393 initialValue : value ,
94+ initialContexts : restoredContexts ,
9495 onPasteFiles : files . processFiles ,
9596 } )
9697 const { textareaRef } = editor
@@ -105,13 +106,16 @@ export function Composer({
105106 if ( value ) textareaRef . current ?. focus ( )
106107 }
107108 } , [ value , textareaRef ] )
109+ const lastContexts = useRef ( editor . contexts )
108110 useEffect ( ( ) => {
109111 if ( editorRef . current . getValue ( ) !== editor . value ) return
110- if ( editor . value !== lastPublished . current ) {
111- lastPublished . current = editor . value
112- onChange ( editor . value )
112+ const plainValue = editorRef . current . getPlainValue ( )
113+ if ( plainValue !== lastPublished . current || editor . contexts !== lastContexts . current ) {
114+ lastPublished . current = plainValue
115+ lastContexts . current = editor . contexts
116+ onChange ( plainValue , editor . contexts . length ? editor . contexts : undefined )
113117 }
114- } , [ editor . value , onChange ] )
118+ } , [ editor . value , editor . contexts , onChange ] )
115119 useEffect ( ( ) => {
116120 if ( ! restoredContexts ) return
117121 // A queued skill may belong to a workspace whose picker has never opened here.
@@ -182,6 +186,25 @@ export function Composer({
182186
183187 const leadingControls = (
184188 < >
189+ { imagesOnly && ! showModeSelector && (
190+ < Search className = 'size-[16px] shrink-0 text-[var(--text-icon)]' />
191+ ) }
192+ { ! imagesOnly && contextPicker ( 'resources' , Plus , 'Add resources' ) }
193+
194+ { ! imagesOnly && (
195+ < Tooltip . Root >
196+ < Tooltip . Trigger asChild >
197+ < Chip
198+ shape = 'round'
199+ leftIcon = { Paperclip }
200+ onClick = { files . handleFileSelect }
201+ aria-label = 'Attach file'
202+ />
203+ </ Tooltip . Trigger >
204+ < Tooltip . Content side = 'top' > Attach file</ Tooltip . Content >
205+ </ Tooltip . Root >
206+ ) }
207+ { ! imagesOnly && contextPicker ( 'skills' , Slash , 'Skills' ) }
185208 { showModeSelector && (
186209 < ConversationModeSelector
187210 value = { requestMode }
@@ -195,7 +218,7 @@ export function Composer({
195218 files . attachedFiles . some ( ( file ) => ! isAssistantImageType ( file . type ) ) )
196219 ) {
197220 toast . info (
198- 'Remove resource and skill mentions and non-image attachments before switching to Search .'
221+ 'Remove resource and skill mentions and non-image attachments before switching to Ask .'
199222 )
200223 return
201224 }
@@ -205,25 +228,6 @@ export function Composer({
205228 }
206229 />
207230 ) }
208- { imagesOnly && ! showModeSelector && (
209- < Search className = 'size-[16px] shrink-0 text-[var(--text-icon)]' />
210- ) }
211- { ! imagesOnly && contextPicker ( 'resources' , Plus , 'Add resources' ) }
212-
213- { ! imagesOnly && (
214- < Tooltip . Root >
215- < Tooltip . Trigger asChild >
216- < Chip
217- shape = 'round'
218- leftIcon = { Paperclip }
219- onClick = { files . handleFileSelect }
220- aria-label = 'Attach file'
221- />
222- </ Tooltip . Trigger >
223- < Tooltip . Content side = 'top' > Attach file</ Tooltip . Content >
224- </ Tooltip . Root >
225- ) }
226- { ! imagesOnly && contextPicker ( 'skills' , Slash , 'Skills' ) }
227231 </ >
228232 )
229233 const voiceControl = voice . isSupported && (
0 commit comments