Fix/agentflow Fixing agentflow UI style and make it similar to v2 (#6280)

* Fix output node not selectable

* Fix highlight color

* Fix previous node displaying label and not ID

* Fix tool arguements not being called

* Fix dropdown values to not be bold

* Add standard sizing for all input text

* FIx syntax highlighting styling

* Use predefined tokens for font weight and size

* Fix dialog size of Source

* Fix color of syntax highlight in dark mode

* Fix the input fileds color in dark and regular mode

* Fix gemini comments

* FIx inconsistent spacing

* Standardise gap between label and field

* Remove export for unused function and standardize dialog size accrosss code, rich text,
  and source

* Fix test error
This commit is contained in:
j-sanaa
2026-04-27 09:15:16 -07:00
committed by GitHub
parent fed53e46b8
commit 3d69baed40
20 changed files with 320 additions and 54 deletions
@@ -3,6 +3,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
import type { InputParam, NodeData } from '@/core/types'
import { ArrayInput } from './ArrayInput'
import type { VariableItem } from './VariablePicker'
// --- Mocks ---
const mockOnDataChange = jest.fn()
@@ -10,13 +11,20 @@ const mockOnDataChange = jest.fn()
jest.mock('./NodeInputHandler', () => ({
NodeInputHandler: ({
inputParam,
onDataChange
data,
onDataChange,
variableItems
}: {
inputParam: InputParam
data: NodeData
onDataChange: (args: { inputParam: InputParam; newValue: unknown }) => void
variableItems?: VariableItem[]
}) => (
<div data-testid={`input-handler-${inputParam.name}`}>
<div
data-testid={`input-handler-${inputParam.name}`}
data-inputs={JSON.stringify(data.inputs)}
data-variable-items={variableItems ? JSON.stringify(variableItems.map((v) => v.value)) : undefined}
>
<label>{inputParam.label}</label>
<input data-testid={`input-${inputParam.name}`} onChange={(e) => onDataChange({ inputParam, newValue: e.target.value })} />
</div>
@@ -485,4 +493,163 @@ describe('ArrayInput', () => {
const deleteButton = screen.getByTitle('Delete')
expect(deleteButton).toBeDisabled()
})
// Parent inputs are merged into itemData so async load methods inside array items
// (e.g. listToolInputArgs) can access parent-level fields like toolAgentflowSelectedTool.
it('should merge parent node inputs into itemData so async options inside array items can access parent fields', () => {
const dataWithParentField: NodeData = {
...mockNodeData,
inputs: {
toolAgentflowSelectedTool: 'calculator',
toolInputArgs: [{ inputArgName: '', inputArgValue: '' }]
}
} as NodeData
const toolInputArgsParam: InputParam = {
id: 'toolInputArgs',
name: 'toolInputArgs',
label: 'Tool Input Arguments',
type: 'array',
array: [
{ id: 'inputArgName', name: 'inputArgName', label: 'Input Argument Name', type: 'asyncOptions' } as InputParam,
{ id: 'inputArgValue', name: 'inputArgValue', label: 'Input Argument Value', type: 'string' } as InputParam
]
}
render(<ArrayInput inputParam={toolInputArgsParam} data={dataWithParentField} onDataChange={mockOnDataChange} />)
const argNameHandler = screen.getByTestId('input-handler-inputArgName')
const inputs = JSON.parse(argNameHandler.getAttribute('data-inputs') ?? '{}')
// The parent-level field must be visible to the array item's async input
expect(inputs.toolAgentflowSelectedTool).toBe('calculator')
// The item's own fields are also present
expect(inputs.inputArgName).toBe('')
})
})
// ── acceptNodeOutputAsVariable in ArrayInput ────────────────────────────────
describe('ArrayInput – acceptNodeOutputAsVariable', () => {
const baseVariableItems: VariableItem[] = [
{ label: 'question', description: "User's question", category: 'Chat Context', value: '{{question}}' }
]
it('prepends {{output}} to variableItems when nested param has acceptNodeOutputAsVariable', () => {
const inputParam: InputParam = {
id: 'updateState',
name: 'agentUpdateState',
label: 'Update State',
type: 'array',
acceptVariable: true,
array: [
{ id: 'key', name: 'key', label: 'Key', type: 'string' } as InputParam,
{
id: 'value',
name: 'value',
label: 'Value',
type: 'string',
acceptVariable: true,
acceptNodeOutputAsVariable: true
} as InputParam
]
}
const data: NodeData = {
id: 'node-1',
name: 'agentNode',
label: 'Agent',
inputs: { agentUpdateState: [{ key: 'myKey', value: '' }] }
} as NodeData
render(<ArrayInput inputParam={inputParam} data={data} onDataChange={mockOnDataChange} variableItems={baseVariableItems} />)
const valueHandler = screen.getByTestId('input-handler-value')
const passedItems: string[] = JSON.parse(valueHandler.getAttribute('data-variable-items') ?? '[]')
expect(passedItems[0]).toBe('{{output}}')
expect(passedItems).toContain('{{question}}')
})
it('does NOT prepend {{output}} when nested param lacks acceptNodeOutputAsVariable', () => {
const inputParam: InputParam = {
id: 'updateState',
name: 'agentUpdateState',
label: 'Update State',
type: 'array',
acceptVariable: true,
array: [{ id: 'value', name: 'value', label: 'Value', type: 'string', acceptVariable: true } as InputParam]
}
const data: NodeData = {
id: 'node-1',
name: 'agentNode',
label: 'Agent',
inputs: { agentUpdateState: [{ value: '' }] }
} as NodeData
render(<ArrayInput inputParam={inputParam} data={data} onDataChange={mockOnDataChange} variableItems={baseVariableItems} />)
const valueHandler = screen.getByTestId('input-handler-value')
const passedItems: string[] = JSON.parse(valueHandler.getAttribute('data-variable-items') ?? '[]')
expect(passedItems).not.toContain('{{output}}')
expect(passedItems).toContain('{{question}}')
})
it('includes structured output fields in prepended variables', () => {
const inputParam: InputParam = {
id: 'updateState',
name: 'agentUpdateState',
label: 'Update State',
type: 'array',
array: [
{
id: 'value',
name: 'value',
label: 'Value',
type: 'string',
acceptVariable: true,
acceptNodeOutputAsVariable: true
} as InputParam
]
}
const data: NodeData = {
id: 'node-1',
name: 'agentNode',
label: 'Agent',
inputs: {
agentUpdateState: [{ value: '' }],
agentStructuredOutput: [{ key: 'sentiment', description: 'Sentiment score' }]
}
} as NodeData
render(<ArrayInput inputParam={inputParam} data={data} onDataChange={mockOnDataChange} variableItems={baseVariableItems} />)
const valueHandler = screen.getByTestId('input-handler-value')
const passedItems: string[] = JSON.parse(valueHandler.getAttribute('data-variable-items') ?? '[]')
expect(passedItems).toContain('{{output}}')
expect(passedItems).toContain('{{output.sentiment}}')
})
it('does not pass variableItems when nested param has no acceptVariable', () => {
const inputParam: InputParam = {
id: 'updateState',
name: 'agentUpdateState',
label: 'Update State',
type: 'array',
array: [{ id: 'key', name: 'key', label: 'Key', type: 'string', acceptNodeOutputAsVariable: true } as InputParam]
}
const data: NodeData = {
id: 'node-1',
name: 'agentNode',
label: 'Agent',
inputs: { agentUpdateState: [{ key: '' }] }
} as NodeData
render(<ArrayInput inputParam={inputParam} data={data} onDataChange={mockOnDataChange} variableItems={baseVariableItems} />)
const keyHandler = screen.getByTestId('input-handler-key')
expect(keyHandler.getAttribute('data-variable-items')).toBeNull()
})
})
+37 -4
View File
@@ -11,6 +11,30 @@ import { type AsyncInputProps, type ConfigInputComponentProps, NodeInputHandler
import { useStableKeys } from './useStableKeys'
import type { VariableItem } from './VariablePicker'
/**
* Builds VariableItems representing the current node's own output.
* Added when a nested sub-field declares `acceptNodeOutputAsVariable: true`,
* matching the v2 suggestionOption behaviour where `{{output}}` and
* `{{output.<key>}}` appear for structured-output fields.
*/
function buildNodeOutputVariables(data: NodeData): VariableItem[] {
const vars: VariableItem[] = [
{ label: 'output', description: 'Output from the current node', category: 'Node Outputs', value: '{{output}}' }
]
const structured = (
(data.inputs?.agentStructuredOutput ?? data.inputs?.llmStructuredOutput ?? []) as Array<{ key: string; description?: string }>
).filter((item) => item?.key)
for (const item of structured) {
vars.push({
label: `output.${item.key}`,
description: item.description ?? `Structured output field: ${item.key}`,
category: 'Node Outputs',
value: `{{output.${item.key}}}`
})
}
return vars
}
export interface ArrayInputProps {
inputParam: InputParam
data: NodeData
@@ -118,10 +142,13 @@ export function ArrayInput({
<>
{/* Render each array item */}
{arrayItems.map((itemValues, index) => {
// Create item-specific data context for nested NodeInputHandler
// Create item-specific data context for nested NodeInputHandler.
// Merge parent inputs first so async load methods (e.g. listToolInputArgs)
// can read parent-level fields like toolAgentflowSelectedTool, while
// item-level fields take precedence via the spread order.
const itemData: NodeData = {
...data,
inputs: itemValues
inputs: { ...data.inputs, ...itemValues }
}
return (
@@ -178,7 +205,13 @@ export function ArrayInput({
onConfigChange={onConfigChange}
arrayIndex={index}
parentArrayParam={inputParam}
variableItems={param.acceptVariable ? variableItems : undefined}
variableItems={
param.acceptVariable
? param.acceptNodeOutputAsVariable
? [...buildNodeOutputVariables(data), ...(variableItems ?? [])]
: variableItems
: undefined
}
/>
))}
</Box>
@@ -191,7 +224,7 @@ export function ArrayInput({
size='small'
variant='outlined'
disabled={disabled}
sx={{ borderRadius: '16px', mt: 2 }}
sx={{ borderRadius: '16px', mt: 1 }}
startIcon={<IconPlus />}
onClick={handleAddItem}
>
@@ -171,13 +171,13 @@ export function ConditionBuilder({
</Typography>
</Box>
<Box sx={{ px: 2 }}>
<Box sx={{ px: 2, pb: 2 }}>
<Button
fullWidth
size='small'
variant='outlined'
disabled={disabled}
sx={{ borderRadius: '16px', mt: 2 }}
sx={{ borderRadius: '16px', mt: 1 }}
startIcon={<IconPlus />}
onClick={handleAddItem}
>
@@ -125,13 +125,15 @@ export function ExpandTextDialog({
<TextField
fullWidth
multiline
minRows={15}
value={localValue}
disabled={disabled}
onChange={(e) => setLocalValue(e.target.value)}
placeholder={placeholder}
data-testid='source-input'
inputProps={{ style: { fontFamily: 'monospace', fontSize: '0.85em' } }}
sx={{
'& .MuiInputBase-root': { height: 'calc(100vh - 220px)', alignItems: 'flex-start' },
'& textarea': { height: '100% !important', overflowY: 'auto !important', boxSizing: 'border-box' }
}}
/>
) : suggestionItems?.length ? (
<VariableInput
@@ -220,13 +220,13 @@ export function MessagesInput({ inputParam, data, disabled = false, variableItem
))}
{/* Add button */}
<Box sx={{ px: 2 }}>
<Box sx={{ px: 2, pb: 2 }}>
<Button
fullWidth
size='small'
variant='outlined'
disabled={isAddDisabled}
sx={{ borderRadius: '16px', mt: 2 }}
sx={{ borderRadius: '16px', mt: 1 }}
startIcon={<IconPlus />}
onClick={handleAddMessage}
>
@@ -210,13 +210,15 @@ export function NodeInputHandler({
}
if (isExpandable) {
return (
<RichTextEditor
value={typeof value === 'string' ? value : ''}
onChange={(html) => handleDataChange(html)}
placeholder={inputParam.placeholder}
disabled={disabled}
rows={inputParam.rows || 4}
/>
<Box sx={{ mt: 1 }}>
<RichTextEditor
value={typeof value === 'string' ? value : ''}
onChange={(html) => handleDataChange(html)}
placeholder={inputParam.placeholder}
disabled={disabled}
rows={inputParam.rows || 4}
/>
</Box>
)
}
return (
@@ -308,7 +310,7 @@ export function NodeInputHandler({
// acceptVariable: show a button that opens a dialog with JsonInput + variable support
return (
<Button
sx={{ borderRadius: 25, width: '100%', mb: 0, mt: 2 }}
sx={{ borderRadius: 25, width: '100%', mb: 0, mt: 1 }}
variant='outlined'
disabled={disabled}
onClick={() => setJsonDialogOpen(true)}
@@ -77,7 +77,8 @@ const StyledEditorContent = styled(EditorContent, {
overflowY: rows ? 'auto' : 'hidden',
overflowX: rows ? 'auto' : 'hidden',
lineHeight: rows ? `${tokens.typography.rowHeightRem}em` : `${tokens.typography.singleLineLineHeightEm}em`,
fontWeight: 500,
fontSize: tokens.typography.fontSize.md,
fontWeight: tokens.typography.fontWeight.medium,
color: disabled ? theme.palette.action.disabled : theme.palette.text.primary,
border: `1px solid ${tokens.colors.border.input[mode]}`,
borderRadius: '10px',
@@ -4,6 +4,7 @@ import { Box, Button, IconButton, TextField, Typography } from '@mui/material'
import { useTheme } from '@mui/material/styles'
import { IconPlus, IconTrash } from '@tabler/icons-react'
import { tokens } from '@/core/theme/tokens'
import type { StateUpdate } from '@/core/types'
import type { SuggestionItem } from './SuggestionDropdown'
@@ -87,7 +88,7 @@ export function StateKeyValueInput({ value, onChange, disabled = false, suggesti
<IconTrash size={18} />
</IconButton>
<Typography variant='body2' fontWeight={500} sx={{ mb: 0.5 }}>
<Typography variant='body2' fontWeight={tokens.typography.fontWeight.medium} sx={{ mb: 0.5 }}>
Key
</Typography>
<TextField
@@ -99,7 +100,7 @@ export function StateKeyValueInput({ value, onChange, disabled = false, suggesti
onChange={(e) => handleKeyChange(index, e.target.value)}
/>
<Typography variant='body2' fontWeight={500} sx={{ mt: 1.5, mb: 0.5 }}>
<Typography variant='body2' fontWeight={tokens.typography.fontWeight.medium} sx={{ mt: 1.5, mb: 0.5 }}>
Value
</Typography>
<VariableInput
@@ -245,13 +245,13 @@ export function StructuredOutputBuilder({ inputParam, data, disabled = false, on
))}
{/* Add button */}
<Box sx={{ px: 2 }}>
<Box sx={{ px: 2, pb: 2 }}>
<Button
fullWidth
size='small'
variant='outlined'
disabled={isAddDisabled}
sx={{ borderRadius: '16px', mt: 2 }}
sx={{ borderRadius: '16px', mt: 1 }}
startIcon={<IconPlus />}
onClick={handleAddEntry}
>
@@ -3,6 +3,8 @@ import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState }
import { Divider, List, ListItem, ListItemButton, Paper, Typography } from '@mui/material'
import { useTheme } from '@mui/material/styles'
import { tokens } from '@/core/theme/tokens'
/**
* Shape of items returned by the TipTap suggestion `items()` callback.
* Matches the UI package's SuggestionList item shape.
@@ -131,7 +133,7 @@ export const SuggestionDropdown = forwardRef<SuggestionDropdownRef, SuggestionDr
alignItems: 'flex-start'
}}
>
<Typography variant='body1' sx={{ fontWeight: 500 }}>
<Typography variant='body1' sx={{ fontWeight: tokens.typography.fontWeight.medium }}>
{item.label}
</Typography>
{item.description && (
@@ -61,7 +61,8 @@ const StyledEditorContent = styled(EditorContent, {
overflowY: rows ? 'auto' : 'hidden',
overflowX: rows ? 'auto' : 'hidden',
lineHeight: rows ? `${tokens.typography.rowHeightRem}em` : `${tokens.typography.singleLineLineHeightEm}em`,
fontWeight: 500,
fontSize: tokens.typography.fontSize.md,
fontWeight: tokens.typography.fontWeight.medium,
color: disabled ? theme.palette.action.disabled : theme.palette.text.primary,
border: `1px solid ${tokens.colors.border.input[mode]}`,
borderRadius: '10px',
@@ -88,14 +89,13 @@ const StyledEditorContent = styled(EditorContent, {
height: 0
},
// Mention (variable) chip styling — matches original UI green style
'& .mention': {
backgroundColor: mode === 'dark' ? 'rgba(76, 175, 80, 0.2)' : 'rgba(76, 175, 80, 0.08)',
borderRadius: '4px',
padding: '1px 4px',
fontWeight: 600,
color: mode === 'dark' ? '#81c784' : '#2e7d32',
whiteSpace: 'nowrap'
backgroundColor: '#b3f0b8',
borderRadius: '0.4rem',
boxDecorationBreak: 'clone',
WebkitBoxDecorationBreak: 'clone',
color: '#0d7115',
padding: '0.1rem 0.3rem'
},
// Block element spacing
@@ -135,9 +135,9 @@ describe('createAgentflowTheme', () => {
expect(theme.typography.h4.fontWeight).toBe(600)
})
it('should set h5 to 0.875rem / 600 weight', () => {
it('should set h5 to 0.875rem / 500 weight', () => {
expect(theme.typography.h5.fontSize).toBe('0.875rem')
expect(theme.typography.h5.fontWeight).toBe(600)
expect(theme.typography.h5.fontWeight).toBe(500)
})
it('should set h6 to 0.75rem / 500 weight', () => {
@@ -41,12 +41,12 @@ export function createAgentflowTheme(isDarkMode: boolean): Theme {
}
},
typography: {
h4: { fontSize: '1rem', fontWeight: 600 },
h5: { fontSize: '0.875rem', fontWeight: 600 },
h6: { fontSize: '0.75rem', fontWeight: 500 },
subtitle1: { fontSize: '0.875rem', fontWeight: 500 },
body1: { fontSize: '0.875rem', fontWeight: 400 },
body2: { fontSize: '0.75rem', fontWeight: 400 }
h4: { fontSize: tokens.typography.fontSize.lg, fontWeight: tokens.typography.fontWeight.semibold },
h5: { fontSize: tokens.typography.fontSize.md, fontWeight: tokens.typography.fontWeight.medium },
h6: { fontSize: tokens.typography.fontSize.sm, fontWeight: tokens.typography.fontWeight.medium },
subtitle1: { fontSize: tokens.typography.fontSize.md, fontWeight: tokens.typography.fontWeight.medium },
body1: { fontSize: tokens.typography.fontSize.md, fontWeight: tokens.typography.fontWeight.regular },
body2: { fontSize: tokens.typography.fontSize.sm, fontWeight: tokens.typography.fontWeight.regular }
},
components: {
MuiPaper: {
@@ -62,7 +62,39 @@ export function createAgentflowTheme(isDarkMode: boolean): Theme {
MuiOutlinedInput: {
styleOverrides: {
root: {
backgroundColor: tokens.colors.background.input[mode]
backgroundColor: tokens.colors.background.input[mode],
'& .MuiOutlinedInput-notchedOutline': {
borderColor: tokens.colors.border.input[mode]
},
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: tokens.colors.border.hover[mode]
}
},
input: {
fontWeight: tokens.typography.fontWeight.medium
}
}
},
MuiListItemButton: {
styleOverrides: {
root: {
'&.Mui-selected': {
color: isDarkMode ? '#fff' : tokens.colors.palette.secondary.dark,
backgroundColor: isDarkMode
? tokens.colors.background.listItemSelected.dark
: tokens.colors.palette.secondary.light,
'&:hover': {
backgroundColor: isDarkMode
? tokens.colors.background.listItemSelected.dark
: tokens.colors.palette.secondary.light
}
},
'&:hover': {
color: isDarkMode ? '#fff' : tokens.colors.palette.secondary.dark,
backgroundColor: isDarkMode
? tokens.colors.background.listItemSelected.dark
: tokens.colors.palette.secondary.light
}
}
}
},
+21 -3
View File
@@ -28,6 +28,7 @@ const baseColors = {
gray800: '#333',
// Dark mode grays
lightBorderDefault: '#21212125',
darkBorderDefault: 'rgba(255, 255, 255, 0.145)',
darkGray100: '#1a1a1a',
darkGray200: '#1a1a2e',
@@ -35,6 +36,7 @@ const baseColors = {
darkGray350: '#23262c',
darkGray400: '#2d2d2d',
darkGray450: '#32353b',
darkGray460: '#454c59',
darkGray500: '#404040',
darkBlueHover: '#233345',
darkGray600: '#525252',
@@ -125,14 +127,15 @@ export const tokens = {
card: { light: baseColors.white, dark: baseColors.darkGray350 },
cardHover: { light: baseColors.gray75, dark: baseColors.darkGray500 },
header: { light: baseColors.white, dark: baseColors.darkGray400 },
input: { light: baseColors.white, dark: baseColors.darkGray450 },
optionHover: { light: '', dark: baseColors.darkBlueHover }
input: { light: baseColors.gray50, dark: baseColors.darkGray450 },
optionHover: { light: '', dark: baseColors.darkBlueHover },
listItemSelected: { light: '', dark: baseColors.darkGray460 }
},
border: {
default: { light: baseColors.gray300, dark: baseColors.darkBorderDefault },
hover: { light: baseColors.gray400, dark: baseColors.darkGray600 },
input: { light: baseColors.gray400, dark: baseColors.darkGray750 },
input: { light: baseColors.lightBorderDefault, dark: baseColors.darkGray750 },
validation: baseColors.nodeCondition
},
@@ -241,6 +244,21 @@ export const tokens = {
// Typography
typography: {
// Font size scale
fontSize: {
xs: '0.625rem', // 10px — badge, compact caption
sm: '0.75rem', // 12px — secondary label, helper text
md: '0.875rem', // 14px — body, input, primary label
lg: '1rem' // 16px — section heading
},
// Font weight scale
fontWeight: {
regular: 400,
medium: 500,
semibold: 600
},
/** Matches MUI OutlinedInput's default line-height (1.4375em) so the
* RichTextEditor aligns with standard TextField height at the same row count. */
rowHeightRem: 1.4375,
@@ -90,6 +90,7 @@ export interface InputParam {
rows?: number
description?: string
acceptVariable?: boolean
acceptNodeOutputAsVariable?: boolean
additionalParams?: boolean
show?: Record<string, unknown>
hide?: Record<string, unknown>
@@ -175,7 +175,7 @@ function ValidationFeedbackComponent({ nodes, edges, availableNodes, setNodes }:
}}
>
<Box sx={{ p: 2 }}>
<Typography variant='h6' sx={{ mt: 1, mb: 2, fontWeight: 600 }}>
<Typography variant='h6' sx={{ mt: 1, mb: 2, fontWeight: tokens.typography.fontWeight.semibold }}>
Checklist ({results.reduce((sum, r) => sum + r.issues.length, 0)})
</Typography>
@@ -195,7 +195,9 @@ function ValidationFeedbackComponent({ nodes, edges, availableNodes, setNodes }:
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
{getNodeIcon(item)}
<Typography sx={{ fontWeight: 500 }}>{item.label || item.name}</Typography>
<Typography sx={{ fontWeight: tokens.typography.fontWeight.medium }}>
{item.label || item.name}
</Typography>
</Box>
{item.issues.map((issue, issueIndex) => (
@@ -235,7 +237,11 @@ function ValidationFeedbackComponent({ nodes, edges, availableNodes, setNodes }:
alt='Illustration of a checklist with no items, indicating no issues found'
/>
{hasValidated ? (
<Typography variant='body2' color='success.main' sx={{ mt: 2, fontWeight: 500 }}>
<Typography
variant='body2'
color='success.main'
sx={{ mt: 2, fontWeight: tokens.typography.fontWeight.medium }}
>
No issues found in your flow!
</Typography>
) : (
@@ -19,6 +19,7 @@ import {
import { useTheme } from '@mui/material/styles'
import { IconSparkles } from '@tabler/icons-react'
import { tokens } from '@/core/theme/tokens'
import type { FlowData } from '@/core/types'
import { useApiContext, useConfigContext } from '@/infrastructure/store'
@@ -161,7 +162,7 @@ function GenerateFlowDialogComponent({ open, onClose, onGenerated }: GenerateFlo
onClose={loading ? undefined : onClose}
aria-labelledby='generate-flow-dialog-title'
>
<DialogTitle sx={{ fontSize: '1rem' }} id='generate-flow-dialog-title'>
<DialogTitle sx={{ fontSize: tokens.typography.fontSize.lg }} id='generate-flow-dialog-title'>
What would you like to build?
</DialogTitle>
<DialogContent>
@@ -24,6 +24,7 @@ import { JsonInput } from '@/atoms/JsonInput'
import { SwitchInput } from '@/atoms/SwitchInput'
import { TooltipWithParser } from '@/atoms/TooltipWithParser'
import { getDefaultValueForType } from '@/core/primitives'
import { tokens } from '@/core/theme/tokens'
import type { ComponentCredentialSchema, CredentialSchemaInput } from '@/core/types'
import { useApiContext } from '@/infrastructure/store/ApiContext'
@@ -184,7 +185,7 @@ export function CreateCredentialDialog({ open, credentialNames, onClose, onCreat
return (
<Dialog open={open} onClose={handleClose} fullWidth maxWidth={showSelection ? 'md' : 'sm'}>
<DialogTitle sx={{ fontSize: '1rem', p: 3, pb: 0 }}>
<DialogTitle sx={{ fontSize: tokens.typography.fontSize.lg, p: 3, pb: 0 }}>
{selectedSchema ? (
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<div style={{ marginRight: 10 }}>
@@ -236,7 +237,7 @@ export function CreateCredentialDialog({ open, credentialNames, onClose, onCreat
)}
<Box sx={{ p: 2 }}>
<Typography variant='overline'>
<Typography>
Credential Name
<span style={{ color: theme.palette.error.main }}>&nbsp;*</span>
</Typography>
@@ -65,7 +65,7 @@ describe('useAvailableVariables', () => {
const nodeOutputs = result.current.filter((i) => i.category === 'Node Outputs')
expect(nodeOutputs).toHaveLength(1)
expect(nodeOutputs[0].value).toBe('{{llm_0}}')
expect(nodeOutputs[0].label).toBe('llmAgentflow')
expect(nodeOutputs[0].label).toBe('llm_0')
})
it('excludes startAgentflow from node outputs', () => {
@@ -78,7 +78,6 @@ export function useAvailableVariables(nodeId: string): VariableItem[] {
(node.data.inputs?.chainName as string) ??
(node.data.inputs?.functionName as string) ??
(node.data.inputs?.variableName as string) ??
node.data.label ??
node.data.id
const agentflowIcon = getAgentflowIcon(node.data.name)