Skip to content

Commit a4f9fa6

Browse files
committed
Refactor input default value handling and consolidate utility functions
1 parent 991c71a commit a4f9fa6

10 files changed

Lines changed: 91 additions & 81 deletions

File tree

packages/agentflow/src/atoms/ArrayInput.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useTheme } from '@mui/material/styles'
55
import { IconPlus, IconTrash } from '@tabler/icons-react'
66

77
import type { InputParam, NodeData } from '@/core/types'
8+
import { getDefaultValueForType } from '@/core/utils/inputDefaults'
89

910
import { type AsyncInputProps, type ConfigInputComponentProps, NodeInputHandler } from './NodeInputHandler'
1011
import { useStableKeys } from './useStableKeys'
@@ -75,23 +76,7 @@ export function ArrayInput({
7576

7677
if (inputParam.array) {
7778
for (const field of inputParam.array) {
78-
if (field.default !== undefined) {
79-
newItem[field.name] = field.default
80-
} else {
81-
switch (field.type) {
82-
case 'number':
83-
newItem[field.name] = 0
84-
break
85-
case 'boolean':
86-
newItem[field.name] = false
87-
break
88-
case 'array':
89-
newItem[field.name] = []
90-
break
91-
default:
92-
newItem[field.name] = ''
93-
}
94-
}
79+
newItem[field.name] = getDefaultValueForType(field)
9580
}
9681
}
9782

packages/agentflow/src/atoms/ConditionBuilder.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useTheme } from '@mui/material/styles'
55
import { IconPlus, IconTrash } from '@tabler/icons-react'
66

77
import type { InputParam, NodeData } from '@/core/types'
8+
import { getDefaultValueForType } from '@/core/utils/inputDefaults'
89

910
import { NodeInputHandler } from './NodeInputHandler'
1011
import { useStableKeys } from './useStableKeys'
@@ -58,20 +59,7 @@ export function ConditionBuilder({
5859
const newItem: Record<string, unknown> = {}
5960
if (inputParam.array) {
6061
for (const field of inputParam.array) {
61-
if (field.default != null) {
62-
newItem[field.name] = field.default
63-
} else {
64-
switch (field.type) {
65-
case 'number':
66-
newItem[field.name] = 0
67-
break
68-
case 'boolean':
69-
newItem[field.name] = false
70-
break
71-
default:
72-
newItem[field.name] = ''
73-
}
74-
}
62+
newItem[field.name] = getDefaultValueForType(field)
7563
}
7664
}
7765
onDataChange?.({ inputParam, newValue: [...arrayItems, newItem] })

packages/agentflow/src/core/utils/credentialDefaults.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

packages/agentflow/src/core/utils/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ export { evaluateFieldVisibility, evaluateParamVisibility, stripHiddenFieldValue
1010
// Dynamic output anchor utilities
1111
export { buildDynamicOutputAnchors, parseOutputHandleIndex } from './dynamicOutputAnchors'
1212

13-
// Credential utilities
14-
export { getDefaultValueForType } from './credentialDefaults'
13+
// Input default value utilities
14+
export { getDefaultValueForType } from './inputDefaults'

packages/agentflow/src/core/utils/credentialDefaults.test.ts renamed to packages/agentflow/src/core/utils/inputDefaults.test.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import type { CredentialSchemaInput } from '@/core/types'
1+
import { getDefaultValueForType } from './inputDefaults'
22

3-
import { getDefaultValueForType } from './credentialDefaults'
4-
5-
const makeInput = (overrides: Partial<CredentialSchemaInput> = {}): CredentialSchemaInput => ({
6-
label: 'Test',
7-
name: 'test',
8-
type: 'string',
3+
const makeInput = (overrides: Record<string, unknown> = {}) => ({
4+
type: 'string' as string,
95
...overrides
106
})
117

@@ -32,15 +28,26 @@ describe('getDefaultValueForType', () => {
3228
expect(getDefaultValueForType(makeInput({ type: 'json' }))).toBe('{}')
3329
})
3430

35-
it('returns first option name for options with options present', () => {
36-
const input = makeInput({
37-
type: 'options',
38-
options: [
39-
{ label: 'First', name: 'first' },
40-
{ label: 'Second', name: 'second' }
41-
]
42-
})
43-
expect(getDefaultValueForType(input)).toBe('first')
31+
it('returns [] for array', () => {
32+
expect(getDefaultValueForType(makeInput({ type: 'array' }))).toEqual([])
33+
})
34+
35+
it('returns first option name for object options', () => {
36+
expect(
37+
getDefaultValueForType(
38+
makeInput({
39+
type: 'options',
40+
options: [
41+
{ label: 'First', name: 'first' },
42+
{ label: 'Second', name: 'second' }
43+
]
44+
})
45+
)
46+
).toBe('first')
47+
})
48+
49+
it('returns first option value for string options', () => {
50+
expect(getDefaultValueForType(makeInput({ type: 'options', options: ['alpha', 'beta'] }))).toBe('alpha')
4451
})
4552

4653
it("returns '' for options with no options", () => {
@@ -57,6 +64,17 @@ describe('getDefaultValueForType', () => {
5764
})
5865

5966
it("returns '' for unknown type", () => {
60-
expect(getDefaultValueForType(makeInput({ type: 'unknown' as CredentialSchemaInput['type'] }))).toBe('')
67+
expect(getDefaultValueForType(makeInput({ type: 'somethingElse' }))).toBe('')
68+
})
69+
70+
it('works with InputParam-shaped objects', () => {
71+
// InputParam has id, name, label, type, etc.
72+
const inputParam = { id: 'p1', name: 'field', label: 'Field', type: 'boolean' }
73+
expect(getDefaultValueForType(inputParam)).toBe(false)
74+
})
75+
76+
it('works with CredentialSchemaInput-shaped objects', () => {
77+
const credInput = { label: 'API Key', name: 'apiKey', type: 'password' }
78+
expect(getDefaultValueForType(credInput)).toBe('')
6179
})
6280
})
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Minimal shape accepted by getDefaultValueForType.
3+
* Both InputParam and CredentialSchemaInput satisfy this interface.
4+
*/
5+
interface TypedInput {
6+
type: string
7+
default?: unknown
8+
options?: Array<{ name: string } | string>
9+
}
10+
11+
/**
12+
* Returns the appropriate default value for an input based on its type.
13+
* If the input already defines a `default`, that value is returned as-is.
14+
*
15+
* Works with any input shape that has `type`, optional `default`, and optional `options`
16+
* (e.g. InputParam, CredentialSchemaInput).
17+
*/
18+
export function getDefaultValueForType(input: TypedInput): unknown {
19+
if (input.default !== undefined) return input.default
20+
21+
switch (input.type) {
22+
case 'boolean':
23+
return false
24+
case 'number':
25+
return 0
26+
case 'json':
27+
return '{}'
28+
case 'array':
29+
return []
30+
case 'options': {
31+
const first = input.options?.[0]
32+
if (!first) return ''
33+
return typeof first === 'string' ? first : first.name
34+
}
35+
case 'string':
36+
case 'password':
37+
default:
38+
return ''
39+
}
40+
}

packages/agentflow/src/core/utils/nodeFactory.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { FlowNode, NodeData, OutputAnchor } from '../types'
22

33
import { buildDynamicOutputAnchors } from './dynamicOutputAnchors'
4+
import { getDefaultValueForType } from './inputDefaults'
45

56
/**
67
* Map from NodeData.type to the ReactFlow node type key.
@@ -55,11 +56,13 @@ export function getUniqueNodeLabel(nodeData: NodeData, nodes: FlowNode[]): strin
5556
* Initialize default values for node parameters.
5657
* Falls back to '' for params without a default — needed by show/hide condition evaluation.
5758
*/
58-
function initializeDefaultNodeData(nodeParams: Array<{ name: string; default?: unknown }>): Record<string, unknown> {
59+
function initializeDefaultNodeData(
60+
nodeParams: Array<{ name: string; type: string; default?: unknown; options?: Array<{ name: string } | string> }>
61+
): Record<string, unknown> {
5962
const initialValues: Record<string, unknown> = {}
6063

6164
for (const input of nodeParams) {
62-
initialValues[input.name] = input.default ?? ''
65+
initialValues[input.name] = getDefaultValueForType(input)
6366
}
6467

6568
return initialValues

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { IconSettings } from '@tabler/icons-react'
77

88
import { type AsyncInputProps, NodeInputHandler } from '@/atoms'
99
import type { InputParam, NodeData } from '@/core/types'
10-
import { evaluateFieldVisibility, initNode } from '@/core/utils'
10+
import { evaluateFieldVisibility, getDefaultValueForType, initNode } from '@/core/utils'
1111
import { useApiContext } from '@/infrastructure/store'
1212

1313
// ─── Props ────────────────────────────────────────────────────────────────────
@@ -33,7 +33,7 @@ export interface ConfigInputProps {
3333
function initializeDefaults(params: InputParam[]): Record<string, unknown> {
3434
const defaults: Record<string, unknown> = {}
3535
for (const p of params) {
36-
defaults[p.name] = p.default ?? ''
36+
defaults[p.name] = getDefaultValueForType(p)
3737
}
3838
return defaults
3939
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { JsonInput } from '@/atoms/JsonInput'
2121
import { SwitchInput } from '@/atoms/SwitchInput'
2222
import { TooltipWithParser } from '@/atoms/TooltipWithParser'
2323
import type { ComponentCredentialSchema, CredentialSchemaInput } from '@/core/types'
24-
import { getDefaultValueForType } from '@/core/utils/credentialDefaults'
24+
import { getDefaultValueForType } from '@/core/utils/inputDefaults'
2525
import { useApiContext } from '@/infrastructure/store/ApiContext'
2626

2727
export interface CreateCredentialDialogProps {

packages/agentflow/src/infrastructure/store/AgentflowContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
InputParam,
1515
NodeData
1616
} from '@/core/types'
17-
import { getUniqueNodeId } from '@/core/utils'
17+
import { getDefaultValueForType, getUniqueNodeId } from '@/core/utils'
1818

1919
import { agentflowReducer, initialState, normalizeNodes } from './agentflowReducer'
2020

@@ -216,7 +216,7 @@ export function AgentflowStateProvider({ children, initialFlow }: AgentflowState
216216
if (isConnectionString(value)) {
217217
// Reset string connections to parameter default
218218
const inputParam = newNode.data.inputs?.find((p) => p.name === inputName)
219-
newNode.data.inputValues[inputName] = inputParam?.default ?? ''
219+
newNode.data.inputValues[inputName] = inputParam ? getDefaultValueForType(inputParam) : ''
220220
} else if (Array.isArray(value)) {
221221
// Filter out connection strings from arrays
222222
newNode.data.inputValues[inputName] = value.filter((item) => !isConnectionString(item))

0 commit comments

Comments
 (0)