Skip to content
Open
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 @@ -20,6 +20,7 @@ const MAX_TEXTAREA_HEIGHT_PX = 320
interface Message {
role: 'system' | 'user' | 'assistant'
content: string
reasoning_content?: string
}

/**
Expand Down Expand Up @@ -60,6 +61,7 @@ export function MessagesInput({
const [localMessages, setLocalMessages] = useState<Message[]>([{ role: 'user', content: '' }])
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
const [openPopoverIndex, setOpenPopoverIndex] = useState<number | null>(null)
const [showReasoningContent, setShowReasoningContent] = useState<Record<number, boolean>>({})
const subBlockInput = useSubBlockInput({
blockId,
subBlockId,
Expand All @@ -74,8 +76,24 @@ export function MessagesInput({
useEffect(() => {
if (isPreview && previewValue && Array.isArray(previewValue)) {
setLocalMessages(previewValue)
// Auto-show reasoning content if it exists
const reasoningMap: Record<number, boolean> = {}
previewValue.forEach((msg, idx) => {
if (msg.role === 'assistant' && msg.reasoning_content) {
reasoningMap[idx] = true
}
})
setShowReasoningContent(reasoningMap)
} else if (messages && Array.isArray(messages) && messages.length > 0) {
setLocalMessages(messages)
// Auto-show reasoning content if it exists
const reasoningMap: Record<number, boolean> = {}
messages.forEach((msg, idx) => {
if (msg.role === 'assistant' && msg.reasoning_content) {
reasoningMap[idx] = true
}
})
setShowReasoningContent(reasoningMap)
}
}, [isPreview, previewValue, messages])

Expand Down Expand Up @@ -117,6 +135,24 @@ export function MessagesInput({
[localMessages, setMessages, isPreview, disabled]
)

/**
* Updates a specific message's reasoning_content
*/
const updateMessageReasoningContent = useCallback(
(index: number, reasoning_content: string) => {
if (isPreview || disabled) return

const updatedMessages = [...localMessages]
updatedMessages[index] = {
...updatedMessages[index],
reasoning_content,
}
setLocalMessages(updatedMessages)
setMessages(updatedMessages)
},
[localMessages, setMessages, isPreview, disabled]
)

/**
* Updates a specific message's role
*/
Expand Down Expand Up @@ -199,6 +235,21 @@ export function MessagesInput({
[localMessages, setMessages, isPreview, disabled]
)

/**
* Toggles reasoning content visibility for a message
*/
const toggleReasoningContent = useCallback(
(index: number) => {
if (isPreview || disabled) return

setShowReasoningContent((prev) => ({
...prev,
[index]: !prev[index],
}))
},
[isPreview, disabled]
)

/**
* Capitalizes the first letter of the role
*/
Expand Down Expand Up @@ -355,25 +406,26 @@ export function MessagesInput({
className='flex cursor-pointer items-center justify-between px-[8px] pt-[6px]'
onClick={(e) => handleHeaderClick(index, e)}
>
<Popover
open={openPopoverIndex === index}
onOpenChange={(open) => setOpenPopoverIndex(open ? index : null)}
>
<PopoverTrigger asChild>
<button
type='button'
disabled={isPreview || disabled}
className={cn(
'-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)]',
(isPreview || disabled) &&
'cursor-default hover:bg-transparent hover:text-[var(--text-primary)]'
)}
onClick={(e) => e.stopPropagation()}
aria-label='Select message role'
>
{formatRole(message.role)}
</button>
</PopoverTrigger>
<div className='flex items-center gap-2'>
<Popover
open={openPopoverIndex === index}
onOpenChange={(open) => setOpenPopoverIndex(open ? index : null)}
>
<PopoverTrigger asChild>
<button
type='button'
disabled={isPreview || disabled}
className={cn(
'-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)]',
(isPreview || disabled) &&
'cursor-default hover:bg-transparent hover:text-[var(--text-primary)]'
)}
onClick={(e) => e.stopPropagation()}
aria-label='Select message role'
>
{formatRole(message.role)}
</button>
</PopoverTrigger>
<PopoverContent minWidth={140} align='start'>
<div className='flex flex-col gap-[2px]'>
{(['system', 'user', 'assistant'] as const).map((role) => (
Expand All @@ -390,10 +442,25 @@ export function MessagesInput({
))}
</div>
</PopoverContent>
</Popover>
</Popover>
</div>

{!isPreview && !disabled && (
<div className='flex items-center'>
<div className='flex items-center gap-1'>
{message.role === 'assistant' && (
<Button
variant='ghost'
onClick={(e: React.MouseEvent) => {
e.stopPropagation()
toggleReasoningContent(index)
}}
disabled={disabled}
className='-my-1 h-6 px-2 text-[11px] text-[var(--text-muted)] hover:text-[var(--text-primary)]'
aria-label='Toggle reasoning content'
>
{showReasoningContent[index] ? 'βˆ’ Reasoning' : '+ Reasoning'}
</Button>
)}
{currentMessages.length > 1 && (
<>
<Button
Expand Down Expand Up @@ -452,13 +519,22 @@ export function MessagesInput({

{/* Content Input with overlay for variable highlighting */}
<div className='relative w-full'>
{message.role === 'assistant' && showReasoningContent[index] && (
<div className='px-[8px] pt-[4px] pb-[2px]'>
<span className='font-medium text-[11px] text-[var(--text-muted)]'>Content</span>
</div>
)}
<textarea
ref={(el) => {
textareaRefs.current[fieldId] = el
}}
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'
rows={3}
placeholder='Enter message content...'
placeholder={
message.role === 'assistant'
? 'Enter assistant response content...'
: 'Enter message content...'
}
value={message.content}
onChange={(e) => {
fieldHandlers.onChange(e)
Expand Down Expand Up @@ -544,6 +620,119 @@ export function MessagesInput({
</div>
)}
</div>

{/* Reasoning Content Input (only for assistant role when toggled on) */}
{message.role === 'assistant' && showReasoningContent[index] && (() => {
const reasoningFieldId = `message-${index}-reasoning`
const reasoningFieldState = subBlockInput.fieldHelpers.getFieldState(reasoningFieldId)
const reasoningFieldHandlers = subBlockInput.fieldHelpers.createFieldHandlers(
reasoningFieldId,
message.reasoning_content || '',
(newValue: string) => {
updateMessageReasoningContent(index, newValue)
}
)

const handleReasoningEnvSelect = subBlockInput.fieldHelpers.createEnvVarSelectHandler(
reasoningFieldId,
message.reasoning_content || '',
(newValue: string) => {
updateMessageReasoningContent(index, newValue)
}
)

const handleReasoningTagSelect = subBlockInput.fieldHelpers.createTagSelectHandler(
reasoningFieldId,
message.reasoning_content || '',
(newValue: string) => {
updateMessageReasoningContent(index, newValue)
}
)

const reasoningTextareaRefObject = {
current: textareaRefs.current[reasoningFieldId] ?? null,
} as React.RefObject<HTMLTextAreaElement>

return (
<div className='relative w-full border-t border-[var(--border-1)]'>
<div className='px-[8px] pt-[8px] pb-[2px]'>
<span className='font-medium text-[11px] text-[var(--text-muted)]'>Reasoning Content</span>
</div>
<textarea
ref={(el) => {
textareaRefs.current[reasoningFieldId] = el
}}
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'
rows={3}
placeholder='Enter reasoning content (optional, for o1/o3 models)...'
value={message.reasoning_content || ''}
onChange={(e) => {
reasoningFieldHandlers.onChange(e)
autoResizeTextarea(reasoningFieldId)
}}
onKeyDown={reasoningFieldHandlers.onKeyDown}
onDrop={reasoningFieldHandlers.onDrop}
onDragOver={reasoningFieldHandlers.onDragOver}
onScroll={(e) => {
const overlay = overlayRefs.current[reasoningFieldId]
if (overlay) {
overlay.scrollTop = e.currentTarget.scrollTop
overlay.scrollLeft = e.currentTarget.scrollLeft
}
}}
disabled={isPreview || disabled}
/>
<div
ref={(el) => {
overlayRefs.current[reasoningFieldId] = el
}}
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]'
>
{formatDisplayText(message.reasoning_content || '', {
accessiblePrefixes,
highlightAll: !accessiblePrefixes,
})}
</div>

{/* Env var dropdown for reasoning content */}
<EnvVarDropdown
visible={reasoningFieldState.showEnvVars && !isPreview && !disabled}
onSelect={handleReasoningEnvSelect}
searchTerm={reasoningFieldState.searchTerm}
inputValue={message.reasoning_content || ''}
cursorPosition={reasoningFieldState.cursorPosition}
onClose={() => subBlockInput.fieldHelpers.hideFieldDropdowns(reasoningFieldId)}
workspaceId={subBlockInput.workspaceId}
maxHeight='192px'
inputRef={reasoningTextareaRefObject}
/>

{/* Tag dropdown for reasoning content */}
<TagDropdown
visible={reasoningFieldState.showTags && !isPreview && !disabled}
onSelect={handleReasoningTagSelect}
blockId={blockId}
activeSourceBlockId={reasoningFieldState.activeSourceBlockId}
inputValue={message.reasoning_content || ''}
cursorPosition={reasoningFieldState.cursorPosition}
onClose={() => subBlockInput.fieldHelpers.hideFieldDropdowns(reasoningFieldId)}
inputRef={reasoningTextareaRefObject}
/>

{!isPreview && !disabled && (
<div
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)]'
onMouseDown={(e) => handleResizeStart(reasoningFieldId, e)}
onDragStart={(e) => {
e.preventDefault()
}}
>
<ChevronsUpDown className='h-3 w-3 text-[var(--text-muted)]' />
</div>
)}
</div>
)
})()}
</>
)
})()}
Expand Down
Loading