Skip to content

Commit b634046

Browse files
committed
FIx styling and create credentiall behavior
1 parent d2fe109 commit b634046

6 files changed

Lines changed: 135 additions & 86 deletions

File tree

packages/agentflow/src/core/theme/createAgentflowTheme.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export function createAgentflowTheme(isDarkMode: boolean): Theme {
3232
// Custom card color (now type-safe thanks to types.ts)
3333
card: {
3434
main: tokens.colors.background.card[mode]
35+
},
36+
warningBanner: {
37+
background: tokens.colors.semantic.warningBg,
38+
text: tokens.colors.semantic.warningText
3539
}
3640
},
3741
typography: {

packages/agentflow/src/core/theme/tokens.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const baseColors = {
4141
success: '#4caf50',
4242
error: '#f44336',
4343
warning: '#ff9800',
44+
warningBg: '#fefcbf',
45+
warningText: '#744210',
4446
info: '#2196f3',
4547

4648
// Node type colors (brand colors)
@@ -112,6 +114,8 @@ export const tokens = {
112114
success: baseColors.success,
113115
error: baseColors.error,
114116
warning: baseColors.warning,
117+
warningBg: baseColors.warningBg,
118+
warningText: baseColors.warningText,
115119
info: baseColors.info
116120
},
117121

packages/agentflow/src/core/theme/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,20 @@ declare module '@mui/material/styles' {
1212
card: {
1313
main: string
1414
}
15+
warningBanner: {
16+
background: string
17+
text: string
18+
}
1519
}
1620

1721
interface PaletteOptions {
1822
card?: {
1923
main: string
2024
}
25+
warningBanner?: {
26+
background: string
27+
text: string
28+
}
2129
}
2230
}
2331

packages/agentflow/src/features/node-editor/AsyncInput.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ describe('AsyncInput – Create New credential', () => {
490490
expect(screen.getByTestId('create-credential-dialog')).toBeTruthy()
491491
})
492492

493-
it('after credential creation, onChange is called with new ID and refetch is called', async () => {
493+
it('after credential creation, onChange is called with new ID and component remounts to refetch', async () => {
494494
const mockChange = jest.fn()
495495
mockUseAsyncOptions.mockReturnValue({
496496
...idleResult(),
@@ -506,6 +506,8 @@ describe('AsyncInput – Create New credential', () => {
506506
/>
507507
)
508508

509+
const initialCallCount = mockUseAsyncOptions.mock.calls.length
510+
509511
// Open dropdown and select "- Create New -"
510512
fireEvent.mouseDown(screen.getByRole('combobox'))
511513
await waitFor(() => screen.getByText('- Create New -'))
@@ -515,6 +517,7 @@ describe('AsyncInput – Create New credential', () => {
515517
fireEvent.click(screen.getByText('Create'))
516518

517519
expect(mockChange).toHaveBeenCalledWith('new-cred-id')
518-
expect(mockRefetch).toHaveBeenCalledTimes(1)
520+
// The inner dropdown component remounts (via key change), re-running useAsyncOptions
521+
expect(mockUseAsyncOptions.mock.calls.length).toBeGreaterThan(initialCallCount)
519522
})
520523
})

packages/agentflow/src/features/node-editor/AsyncInput.tsx

Lines changed: 110 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,64 @@ function buildAsyncParams(
2929
}
3030

3131
function AsyncOptionsInput({ inputParam, value, disabled, onChange, nodeName, inputValues }: AsyncInputProps) {
32-
const params = buildAsyncParams(inputParam.loadMethod, nodeName, inputValues)
33-
const { options, loading, error, refetch } = useAsyncOptions({
34-
loadMethod: inputParam.loadMethod,
35-
credentialNames: inputParam.credentialNames,
36-
params
37-
})
38-
3932
const isCredential = !!inputParam.credentialNames?.length
4033
const [createDialogOpen, setCreateDialogOpen] = useState(false)
34+
const [reloadKey, setReloadKey] = useState(0)
4135

4236
const handleCreated = useCallback(
4337
(newCredentialId: string) => {
4438
setCreateDialogOpen(false)
4539
onChange(newCredentialId)
46-
refetch()
40+
// Changing reloadKey forces AsyncOptionsDropdown to remount, which
41+
// re-runs useAsyncOptions and fetches fresh options including the
42+
// newly created credential — matching original Flowise behaviour.
43+
setReloadKey((k) => k + 1)
4744
},
48-
[onChange, refetch]
45+
[onChange]
46+
)
47+
48+
return (
49+
<>
50+
<AsyncOptionsDropdown
51+
key={reloadKey}
52+
inputParam={inputParam}
53+
value={value}
54+
disabled={disabled}
55+
onChange={onChange}
56+
nodeName={nodeName}
57+
inputValues={inputValues}
58+
isCredential={isCredential}
59+
onCreateNew={() => setCreateDialogOpen(true)}
60+
/>
61+
{isCredential && (
62+
<CreateCredentialDialog
63+
open={createDialogOpen}
64+
credentialNames={inputParam.credentialNames!}
65+
onClose={() => setCreateDialogOpen(false)}
66+
onCreated={handleCreated}
67+
/>
68+
)}
69+
</>
4970
)
71+
}
72+
73+
/** Inner component that owns the useAsyncOptions hook. Remounted via key to force a fresh fetch. */
74+
function AsyncOptionsDropdown({
75+
inputParam,
76+
value,
77+
disabled,
78+
onChange,
79+
nodeName,
80+
inputValues,
81+
isCredential,
82+
onCreateNew
83+
}: AsyncInputProps & { isCredential: boolean; onCreateNew: () => void }) {
84+
const params = buildAsyncParams(inputParam.loadMethod, nodeName, inputValues)
85+
const { options, loading, error, refetch } = useAsyncOptions({
86+
loadMethod: inputParam.loadMethod,
87+
credentialNames: inputParam.credentialNames,
88+
params
89+
})
5090

5191
if (error) {
5292
return (
@@ -67,85 +107,75 @@ function AsyncOptionsInput({ inputParam, value, disabled, onChange, nodeName, in
67107
const matchedValue = displayOptions.find((o) => o.name === value) ?? null
68108

69109
return (
70-
<>
71-
<Autocomplete<NodeOption>
72-
size='small'
73-
disabled={disabled}
74-
options={displayOptions}
75-
value={matchedValue}
76-
getOptionLabel={(o) => o.label}
77-
isOptionEqualToValue={(o, v) => o.name === v.name}
78-
onChange={(_e, selection) => {
79-
if (selection?.name === CREATE_NEW_SENTINEL) {
80-
setCreateDialogOpen(true)
81-
return
82-
}
83-
onChange(selection?.name ?? '')
84-
}}
85-
loading={loading}
86-
noOptionsText={loading ? 'Loading…' : 'No options available'}
87-
sx={{ mt: 1 }}
88-
renderOption={(props, option) => (
89-
<Box component='li' {...props} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
90-
{option.name === CREATE_NEW_SENTINEL ? (
91-
<Typography variant='h5' color='primary'>
92-
{option.label}
93-
</Typography>
94-
) : (
110+
<Autocomplete<NodeOption>
111+
size='small'
112+
disabled={disabled}
113+
options={displayOptions}
114+
value={matchedValue}
115+
getOptionLabel={(o) => o.label}
116+
isOptionEqualToValue={(o, v) => o.name === v.name}
117+
onChange={(_e, selection) => {
118+
if (selection?.name === CREATE_NEW_SENTINEL) {
119+
onCreateNew()
120+
return
121+
}
122+
onChange(selection?.name ?? '')
123+
}}
124+
loading={loading}
125+
noOptionsText={loading ? 'Loading…' : 'No options available'}
126+
sx={{ mt: 1 }}
127+
renderOption={(props, option) => (
128+
<Box component='li' {...props} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
129+
{option.name === CREATE_NEW_SENTINEL ? (
130+
<Typography variant='h5' color='primary'>
131+
{option.label}
132+
</Typography>
133+
) : (
134+
<>
135+
{option.imageSrc && (
136+
<Box
137+
component='img'
138+
src={option.imageSrc}
139+
alt={option.label}
140+
sx={{ width: 30, height: 30, padding: '1px', borderRadius: '50%', flexShrink: 0 }}
141+
/>
142+
)}
143+
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
144+
<Typography variant='h5'>{option.label}</Typography>
145+
{option.description && <Typography variant='caption'>{option.description}</Typography>}
146+
</Box>
147+
</>
148+
)}
149+
</Box>
150+
)}
151+
renderInput={(params) => (
152+
<TextField
153+
{...params}
154+
InputProps={{
155+
...params.InputProps,
156+
startAdornment: (
95157
<>
96-
{option.imageSrc && (
158+
{matchedValue?.imageSrc && (
97159
<Box
98160
component='img'
99-
src={option.imageSrc}
100-
alt={option.label}
101-
sx={{ width: 30, height: 30, padding: '1px', borderRadius: '50%', flexShrink: 0 }}
161+
src={matchedValue.imageSrc}
162+
alt={matchedValue.label}
163+
sx={{ width: 32, height: 32, borderRadius: '50%', mr: 0.5, flexShrink: 0 }}
102164
/>
103165
)}
104-
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
105-
<Typography variant='h5'>{option.label}</Typography>
106-
{option.description && <Typography variant='caption'>{option.description}</Typography>}
107-
</Box>
166+
{params.InputProps.startAdornment}
108167
</>
109-
)}
110-
</Box>
111-
)}
112-
renderInput={(params) => (
113-
<TextField
114-
{...params}
115-
InputProps={{
116-
...params.InputProps,
117-
startAdornment: (
118-
<>
119-
{matchedValue?.imageSrc && (
120-
<Box
121-
component='img'
122-
src={matchedValue.imageSrc}
123-
alt={matchedValue.label}
124-
sx={{ width: 32, height: 32, borderRadius: '50%', mr: 0.5, flexShrink: 0 }}
125-
/>
126-
)}
127-
{params.InputProps.startAdornment}
128-
</>
129-
),
130-
endAdornment: (
131-
<Fragment>
132-
{loading ? <CircularProgress color='inherit' size={20} /> : null}
133-
{params.InputProps.endAdornment}
134-
</Fragment>
135-
)
136-
}}
137-
/>
138-
)}
139-
/>
140-
{isCredential && (
141-
<CreateCredentialDialog
142-
open={createDialogOpen}
143-
credentialNames={inputParam.credentialNames!}
144-
onClose={() => setCreateDialogOpen(false)}
145-
onCreated={handleCreated}
168+
),
169+
endAdornment: (
170+
<Fragment>
171+
{loading ? <CircularProgress color='inherit' size={20} /> : null}
172+
{params.InputProps.endAdornment}
173+
</Fragment>
174+
)
175+
}}
146176
/>
147177
)}
148-
</>
178+
/>
149179
)
150180
}
151181

packages/agentflow/src/features/node-editor/CreateCredentialDialog.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,13 @@ export function CreateCredentialDialog({ open, credentialNames, onClose, onCreat
224224
display: 'flex',
225225
flexDirection: 'row',
226226
borderRadius: 10,
227-
background: theme.palette.warning.light,
227+
background: theme.palette.warningBanner.background,
228228
padding: 10,
229229
marginTop: 10,
230230
marginBottom: 10
231231
}}
232232
>
233-
<span style={{ color: theme.palette.warning.dark }}>{parser(selectedSchema.description)}</span>
233+
<span style={{ color: theme.palette.warningBanner.text }}>{parser(selectedSchema.description)}</span>
234234
</div>
235235
</Box>
236236
)}
@@ -323,14 +323,14 @@ function CredentialField({ input, value, onChange, disabled = false }: Credentia
323323
display: 'flex',
324324
flexDirection: 'row',
325325
borderRadius: 10,
326-
background: theme.palette.warning.light,
326+
background: theme.palette.warningBanner.background,
327327
padding: 10,
328328
marginTop: 10,
329329
marginBottom: 10
330330
}}
331331
>
332332
<IconAlertTriangle size={36} color={theme.palette.warning.main} />
333-
<span style={{ color: theme.palette.warning.dark, marginLeft: 10 }}>{input.warning}</span>
333+
<span style={{ color: theme.palette.warningBanner.text, marginLeft: 10 }}>{input.warning}</span>
334334
</div>
335335
)}
336336

0 commit comments

Comments
 (0)