@@ -20,6 +20,7 @@ const MAX_TEXTAREA_HEIGHT_PX = 320
2020interface Message {
2121 role : 'system' | 'user' | 'assistant'
2222 content : string
23+ reasoning_content ?: string
2324}
2425
2526/**
@@ -60,6 +61,7 @@ export function MessagesInput({
6061 const [ localMessages , setLocalMessages ] = useState < Message [ ] > ( [ { role : 'user' , content : '' } ] )
6162 const accessiblePrefixes = useAccessibleReferencePrefixes ( blockId )
6263 const [ openPopoverIndex , setOpenPopoverIndex ] = useState < number | null > ( null )
64+ const [ showReasoningContent , setShowReasoningContent ] = useState < Record < number , boolean > > ( { } )
6365 const subBlockInput = useSubBlockInput ( {
6466 blockId,
6567 subBlockId,
@@ -74,8 +76,24 @@ export function MessagesInput({
7476 useEffect ( ( ) => {
7577 if ( isPreview && previewValue && Array . isArray ( previewValue ) ) {
7678 setLocalMessages ( previewValue )
79+ // Auto-show reasoning content if it exists
80+ const reasoningMap : Record < number , boolean > = { }
81+ previewValue . forEach ( ( msg , idx ) => {
82+ if ( msg . role === 'assistant' && msg . reasoning_content ) {
83+ reasoningMap [ idx ] = true
84+ }
85+ } )
86+ setShowReasoningContent ( reasoningMap )
7787 } else if ( messages && Array . isArray ( messages ) && messages . length > 0 ) {
7888 setLocalMessages ( messages )
89+ // Auto-show reasoning content if it exists
90+ const reasoningMap : Record < number , boolean > = { }
91+ messages . forEach ( ( msg , idx ) => {
92+ if ( msg . role === 'assistant' && msg . reasoning_content ) {
93+ reasoningMap [ idx ] = true
94+ }
95+ } )
96+ setShowReasoningContent ( reasoningMap )
7997 }
8098 } , [ isPreview , previewValue , messages ] )
8199
@@ -117,6 +135,24 @@ export function MessagesInput({
117135 [ localMessages , setMessages , isPreview , disabled ]
118136 )
119137
138+ /**
139+ * Updates a specific message's reasoning_content
140+ */
141+ const updateMessageReasoningContent = useCallback (
142+ ( index : number , reasoning_content : string ) => {
143+ if ( isPreview || disabled ) return
144+
145+ const updatedMessages = [ ...localMessages ]
146+ updatedMessages [ index ] = {
147+ ...updatedMessages [ index ] ,
148+ reasoning_content,
149+ }
150+ setLocalMessages ( updatedMessages )
151+ setMessages ( updatedMessages )
152+ } ,
153+ [ localMessages , setMessages , isPreview , disabled ]
154+ )
155+
120156 /**
121157 * Updates a specific message's role
122158 */
@@ -199,6 +235,21 @@ export function MessagesInput({
199235 [ localMessages , setMessages , isPreview , disabled ]
200236 )
201237
238+ /**
239+ * Toggles reasoning content visibility for a message
240+ */
241+ const toggleReasoningContent = useCallback (
242+ ( index : number ) => {
243+ if ( isPreview || disabled ) return
244+
245+ setShowReasoningContent ( ( prev ) => ( {
246+ ...prev ,
247+ [ index ] : ! prev [ index ] ,
248+ } ) )
249+ } ,
250+ [ isPreview , disabled ]
251+ )
252+
202253 /**
203254 * Capitalizes the first letter of the role
204255 */
@@ -355,25 +406,26 @@ export function MessagesInput({
355406 className = 'flex cursor-pointer items-center justify-between px-[8px] pt-[6px]'
356407 onClick = { ( e ) => handleHeaderClick ( index , e ) }
357408 >
358- < Popover
359- open = { openPopoverIndex === index }
360- onOpenChange = { ( open ) => setOpenPopoverIndex ( open ? index : null ) }
361- >
362- < PopoverTrigger asChild >
363- < button
364- type = 'button'
365- disabled = { isPreview || disabled }
366- className = { cn (
367- '-ml-1.5 -my-1 rounded px-1.5 py-1 font-medium text-[13px] text-[var(--text-primary)] leading-none transition-colors hover:bg-[var(--surface-5)] hover:text-[var(--text-secondary)]' ,
368- ( isPreview || disabled ) &&
369- 'cursor-default hover:bg-transparent hover:text-[var(--text-primary)]'
370- ) }
371- onClick = { ( e ) => e . stopPropagation ( ) }
372- aria-label = 'Select message role'
373- >
374- { formatRole ( message . role ) }
375- </ button >
376- </ PopoverTrigger >
409+ < div className = 'flex items-center gap-2' >
410+ < Popover
411+ open = { openPopoverIndex === index }
412+ onOpenChange = { ( open ) => setOpenPopoverIndex ( open ? index : null ) }
413+ >
414+ < PopoverTrigger asChild >
415+ < button
416+ type = 'button'
417+ disabled = { isPreview || disabled }
418+ className = { cn (
419+ '-ml-1.5 -my-1 rounded px-1.5 py-1 font-medium text-[13px] text-[var(--text-primary)] leading-none transition-colors hover:bg-[var(--surface-5)] hover:text-[var(--text-secondary)]' ,
420+ ( isPreview || disabled ) &&
421+ 'cursor-default hover:bg-transparent hover:text-[var(--text-primary)]'
422+ ) }
423+ onClick = { ( e ) => e . stopPropagation ( ) }
424+ aria-label = 'Select message role'
425+ >
426+ { formatRole ( message . role ) }
427+ </ button >
428+ </ PopoverTrigger >
377429 < PopoverContent minWidth = { 140 } align = 'start' >
378430 < div className = 'flex flex-col gap-[2px]' >
379431 { ( [ 'system' , 'user' , 'assistant' ] as const ) . map ( ( role ) => (
@@ -390,10 +442,25 @@ export function MessagesInput({
390442 ) ) }
391443 </ div >
392444 </ PopoverContent >
393- </ Popover >
445+ </ Popover >
446+ </ div >
394447
395448 { ! isPreview && ! disabled && (
396- < div className = 'flex items-center' >
449+ < div className = 'flex items-center gap-1' >
450+ { message . role === 'assistant' && (
451+ < Button
452+ variant = 'ghost'
453+ onClick = { ( e : React . MouseEvent ) => {
454+ e . stopPropagation ( )
455+ toggleReasoningContent ( index )
456+ } }
457+ disabled = { disabled }
458+ className = '-my-1 h-6 px-2 text-[11px] text-[var(--text-muted)] hover:text-[var(--text-primary)]'
459+ aria-label = 'Toggle reasoning content'
460+ >
461+ { showReasoningContent [ index ] ? '− Reasoning' : '+ Reasoning' }
462+ </ Button >
463+ ) }
397464 { currentMessages . length > 1 && (
398465 < >
399466 < Button
@@ -452,13 +519,22 @@ export function MessagesInput({
452519
453520 { /* Content Input with overlay for variable highlighting */ }
454521 < div className = 'relative w-full' >
522+ { message . role === 'assistant' && showReasoningContent [ index ] && (
523+ < div className = 'px-[8px] pt-[4px] pb-[2px]' >
524+ < span className = 'font-medium text-[11px] text-[var(--text-muted)]' > Content</ span >
525+ </ div >
526+ ) }
455527 < textarea
456528 ref = { ( el ) => {
457529 textareaRefs . current [ fieldId ] = el
458530 } }
459531 className = 'allow-scroll box-border min-h-[80px] w-full resize-none whitespace-pre-wrap break-words border-none bg-transparent px-[8px] pt-[8px] font-[inherit] font-medium text-sm text-transparent leading-[inherit] caret-[var(--text-primary)] outline-none placeholder:text-[var(--text-muted)] focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed'
460532 rows = { 3 }
461- placeholder = 'Enter message content...'
533+ placeholder = {
534+ message . role === 'assistant'
535+ ? 'Enter assistant response content...'
536+ : 'Enter message content...'
537+ }
462538 value = { message . content }
463539 onChange = { ( e ) => {
464540 fieldHandlers . onChange ( e )
@@ -544,6 +620,119 @@ export function MessagesInput({
544620 </ div >
545621 ) }
546622 </ div >
623+
624+ { /* Reasoning Content Input (only for assistant role when toggled on) */ }
625+ { message . role === 'assistant' && showReasoningContent [ index ] && ( ( ) => {
626+ const reasoningFieldId = `message-${ index } -reasoning`
627+ const reasoningFieldState = subBlockInput . fieldHelpers . getFieldState ( reasoningFieldId )
628+ const reasoningFieldHandlers = subBlockInput . fieldHelpers . createFieldHandlers (
629+ reasoningFieldId ,
630+ message . reasoning_content || '' ,
631+ ( newValue : string ) => {
632+ updateMessageReasoningContent ( index , newValue )
633+ }
634+ )
635+
636+ const handleReasoningEnvSelect = subBlockInput . fieldHelpers . createEnvVarSelectHandler (
637+ reasoningFieldId ,
638+ message . reasoning_content || '' ,
639+ ( newValue : string ) => {
640+ updateMessageReasoningContent ( index , newValue )
641+ }
642+ )
643+
644+ const handleReasoningTagSelect = subBlockInput . fieldHelpers . createTagSelectHandler (
645+ reasoningFieldId ,
646+ message . reasoning_content || '' ,
647+ ( newValue : string ) => {
648+ updateMessageReasoningContent ( index , newValue )
649+ }
650+ )
651+
652+ const reasoningTextareaRefObject = {
653+ current : textareaRefs . current [ reasoningFieldId ] ?? null ,
654+ } as React . RefObject < HTMLTextAreaElement >
655+
656+ return (
657+ < div className = 'relative w-full border-t border-[var(--border-1)]' >
658+ < div className = 'px-[8px] pt-[8px] pb-[2px]' >
659+ < span className = 'font-medium text-[11px] text-[var(--text-muted)]' > Reasoning Content</ span >
660+ </ div >
661+ < textarea
662+ ref = { ( el ) => {
663+ textareaRefs . current [ reasoningFieldId ] = el
664+ } }
665+ className = 'allow-scroll box-border min-h-[80px] w-full resize-none whitespace-pre-wrap break-words border-none bg-transparent px-[8px] pt-[4px] font-[inherit] font-medium text-sm text-transparent leading-[inherit] caret-[var(--text-primary)] outline-none placeholder:text-[var(--text-muted)] focus:outline-none focus-visible:outline-none disabled:cursor-not-allowed'
666+ rows = { 3 }
667+ placeholder = 'Enter reasoning content (optional, for o1/o3 models)...'
668+ value = { message . reasoning_content || '' }
669+ onChange = { ( e ) => {
670+ reasoningFieldHandlers . onChange ( e )
671+ autoResizeTextarea ( reasoningFieldId )
672+ } }
673+ onKeyDown = { reasoningFieldHandlers . onKeyDown }
674+ onDrop = { reasoningFieldHandlers . onDrop }
675+ onDragOver = { reasoningFieldHandlers . onDragOver }
676+ onScroll = { ( e ) => {
677+ const overlay = overlayRefs . current [ reasoningFieldId ]
678+ if ( overlay ) {
679+ overlay . scrollTop = e . currentTarget . scrollTop
680+ overlay . scrollLeft = e . currentTarget . scrollLeft
681+ }
682+ } }
683+ disabled = { isPreview || disabled }
684+ />
685+ < div
686+ ref = { ( el ) => {
687+ overlayRefs . current [ reasoningFieldId ] = el
688+ } }
689+ className = 'scrollbar-none pointer-events-none absolute top-[34px] left-0 box-border w-full overflow-auto whitespace-pre-wrap break-words border-none bg-transparent px-[8px] pt-[4px] font-[inherit] font-medium text-[var(--text-primary)] text-sm leading-[inherit]'
690+ >
691+ { formatDisplayText ( message . reasoning_content || '' , {
692+ accessiblePrefixes,
693+ highlightAll : ! accessiblePrefixes ,
694+ } ) }
695+ </ div >
696+
697+ { /* Env var dropdown for reasoning content */ }
698+ < EnvVarDropdown
699+ visible = { reasoningFieldState . showEnvVars && ! isPreview && ! disabled }
700+ onSelect = { handleReasoningEnvSelect }
701+ searchTerm = { reasoningFieldState . searchTerm }
702+ inputValue = { message . reasoning_content || '' }
703+ cursorPosition = { reasoningFieldState . cursorPosition }
704+ onClose = { ( ) => subBlockInput . fieldHelpers . hideFieldDropdowns ( reasoningFieldId ) }
705+ workspaceId = { subBlockInput . workspaceId }
706+ maxHeight = '192px'
707+ inputRef = { reasoningTextareaRefObject }
708+ />
709+
710+ { /* Tag dropdown for reasoning content */ }
711+ < TagDropdown
712+ visible = { reasoningFieldState . showTags && ! isPreview && ! disabled }
713+ onSelect = { handleReasoningTagSelect }
714+ blockId = { blockId }
715+ activeSourceBlockId = { reasoningFieldState . activeSourceBlockId }
716+ inputValue = { message . reasoning_content || '' }
717+ cursorPosition = { reasoningFieldState . cursorPosition }
718+ onClose = { ( ) => subBlockInput . fieldHelpers . hideFieldDropdowns ( reasoningFieldId ) }
719+ inputRef = { reasoningTextareaRefObject }
720+ />
721+
722+ { ! isPreview && ! disabled && (
723+ < div
724+ className = 'absolute right-1 bottom-1 flex h-4 w-4 cursor-ns-resize items-center justify-center rounded-[4px] border border-[var(--border-1)] bg-[var(--surface-5)] dark:bg-[var(--surface-5)]'
725+ onMouseDown = { ( e ) => handleResizeStart ( reasoningFieldId , e ) }
726+ onDragStart = { ( e ) => {
727+ e . preventDefault ( )
728+ } }
729+ >
730+ < ChevronsUpDown className = 'h-3 w-3 text-[var(--text-muted)]' />
731+ </ div >
732+ ) }
733+ </ div >
734+ )
735+ } ) ( ) }
547736 </ >
548737 )
549738 } ) ( ) }
0 commit comments