Skip to content

Commit 65bfd31

Browse files
feat(agentflow): MessagesInput, StructuredOutputBuilder + ExpandTextDialog (FLOWISE-263, FLOWISE-267) (#5965)
* feat(agentflow): MessagesInput, StructuredOutputBuilder + ExpandTextDialog (FLOWISE-263, FLOWISE-267) Wire MessagesInput and StructuredOutputBuilder atoms into EditNodeDialog for agentMessages/llmMessages and agentStructuredOutput/llmStructuredOutput array params. Extract shared ExpandTextDialog atom used by both MessagesInput and NodeInputHandler for expanding multiline text fields. - MessagesInput: role dropdown + multiline content with variable/expand icons - StructuredOutputBuilder: key/type/description fields with conditional enum values and JSON schema fields - ExpandTextDialog: reusable expand dialog for long text editing - EditNodeDialog: name-based routing for specialized array components - maxItems forward-compat guard on Add buttons - Remove dead default exports for barrel-only modules Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * address gemini review feedback * Enhance StructuredOutputBuilder with required asterisk for Description, add info tooltips for Enum Values and JSON Schema, and implement ExpandTextDialog for JSON Schema editing. Update tests to cover new features and interactions. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 06f4156 commit 65bfd31

12 files changed

Lines changed: 1854 additions & 4 deletions
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { fireEvent, render, screen } from '@testing-library/react'
2+
3+
import { ExpandTextDialog } from './ExpandTextDialog'
4+
5+
const mockOnConfirm = jest.fn()
6+
const mockOnCancel = jest.fn()
7+
8+
beforeEach(() => {
9+
jest.clearAllMocks()
10+
})
11+
12+
describe('ExpandTextDialog', () => {
13+
it('should not render content when closed', () => {
14+
render(<ExpandTextDialog open={false} value='' onConfirm={mockOnConfirm} onCancel={mockOnCancel} />)
15+
16+
expect(screen.queryByTestId('expand-content-input')).not.toBeInTheDocument()
17+
})
18+
19+
it('should render with the provided value when open', () => {
20+
render(<ExpandTextDialog open={true} value='Hello world' onConfirm={mockOnConfirm} onCancel={mockOnCancel} />)
21+
22+
const textarea = screen.getByTestId('expand-content-input').querySelector('textarea')!
23+
expect(textarea).toHaveValue('Hello world')
24+
})
25+
26+
it('should render title when provided', () => {
27+
render(<ExpandTextDialog open={true} value='' title='Content' onConfirm={mockOnConfirm} onCancel={mockOnCancel} />)
28+
29+
expect(screen.getByText('Content')).toBeInTheDocument()
30+
})
31+
32+
it('should not render title when not provided', () => {
33+
render(<ExpandTextDialog open={true} value='' onConfirm={mockOnConfirm} onCancel={mockOnCancel} />)
34+
35+
expect(screen.queryByRole('heading')).not.toBeInTheDocument()
36+
})
37+
38+
it('should call onConfirm with edited value when Save is clicked', () => {
39+
render(<ExpandTextDialog open={true} value='Original' onConfirm={mockOnConfirm} onCancel={mockOnCancel} />)
40+
41+
const textarea = screen.getByTestId('expand-content-input').querySelector('textarea')!
42+
fireEvent.change(textarea, { target: { value: 'Updated' } })
43+
fireEvent.click(screen.getByRole('button', { name: 'Save' }))
44+
45+
expect(mockOnConfirm).toHaveBeenCalledWith('Updated')
46+
})
47+
48+
it('should call onCancel when Cancel is clicked', () => {
49+
render(<ExpandTextDialog open={true} value='Original' onConfirm={mockOnConfirm} onCancel={mockOnCancel} />)
50+
51+
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
52+
53+
expect(mockOnCancel).toHaveBeenCalled()
54+
expect(mockOnConfirm).not.toHaveBeenCalled()
55+
})
56+
57+
it('should disable textarea and Save button when disabled', () => {
58+
render(<ExpandTextDialog open={true} value='test' disabled={true} onConfirm={mockOnConfirm} onCancel={mockOnCancel} />)
59+
60+
const textarea = screen.getByTestId('expand-content-input').querySelector('textarea')!
61+
expect(textarea).toBeDisabled()
62+
expect(screen.getByRole('button', { name: 'Save' })).toBeDisabled()
63+
})
64+
65+
it('should render placeholder when provided', () => {
66+
render(<ExpandTextDialog open={true} value='' placeholder='Type here...' onConfirm={mockOnConfirm} onCancel={mockOnCancel} />)
67+
68+
const textarea = screen.getByTestId('expand-content-input').querySelector('textarea')!
69+
expect(textarea).toHaveAttribute('placeholder', 'Type here...')
70+
})
71+
})
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { useCallback, useEffect, useState } from 'react'
2+
3+
import { Button, Dialog, DialogActions, DialogContent, TextField, Typography } from '@mui/material'
4+
5+
export interface ExpandTextDialogProps {
6+
open: boolean
7+
value: string
8+
title?: string
9+
placeholder?: string
10+
disabled?: boolean
11+
onConfirm: (value: string) => void
12+
onCancel: () => void
13+
}
14+
15+
/**
16+
* A reusable expand dialog for editing long text content in a larger viewport.
17+
* Used by NodeInputHandler (multiline string fields) and MessagesInput (message content).
18+
*/
19+
export function ExpandTextDialog({ open, value, title, placeholder, disabled = false, onConfirm, onCancel }: ExpandTextDialogProps) {
20+
const [localValue, setLocalValue] = useState(value)
21+
22+
// Sync local state when dialog opens with a new value
23+
useEffect(() => {
24+
if (open) {
25+
setLocalValue(value)
26+
}
27+
}, [open, value])
28+
29+
const handleConfirm = useCallback(() => {
30+
onConfirm(localValue)
31+
}, [localValue, onConfirm])
32+
33+
return (
34+
<Dialog open={open} fullWidth maxWidth='md'>
35+
<DialogContent>
36+
{title && (
37+
<Typography variant='h6' sx={{ mb: 2 }}>
38+
{title}
39+
</Typography>
40+
)}
41+
<TextField
42+
fullWidth
43+
multiline
44+
minRows={12}
45+
value={localValue}
46+
disabled={disabled}
47+
onChange={(e) => setLocalValue(e.target.value)}
48+
placeholder={placeholder}
49+
data-testid='expand-content-input'
50+
/>
51+
</DialogContent>
52+
<DialogActions>
53+
<Button onClick={onCancel}>Cancel</Button>
54+
<Button variant='contained' disabled={disabled} onClick={handleConfirm}>
55+
Save
56+
</Button>
57+
</DialogActions>
58+
</Dialog>
59+
)
60+
}

0 commit comments

Comments
 (0)