Skip to content

Commit e2351c5

Browse files
authored
Feat/agentflow add support for Variable System & Syntax Support (#6067)
* Initial flow * Add tiptap - mention dependency similar to v2 * Add functionalities similiar to V2 * update to tiptap to 3.20.4 * Fix lint error * Added missiing test cases for new files * Styling changes * InputMessage should be visible on initial load * Fix gemini comment * Remove outdated SelectVariable * Add icon support similar to v2 * Fix gemini comments * Remove input folder and utils folder under atoms * Fix test error * Fix initlalDefualts not being set * Fix after merge with main * Fixes for review comments * Fix review comments * New test cases added for fixes * Remove additional space
1 parent faa571c commit e2351c5

33 files changed

Lines changed: 1753 additions & 539 deletions

packages/agentflow/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const baseConfig = {
2020
// so we redirect to lightweight CJS stubs under src/__mocks__/.
2121
'^@tiptap/(.+)$': '<rootDir>/src/__mocks__/@tiptap/$1.ts',
2222
'^lowlight$': '<rootDir>/src/__mocks__/lowlight.ts',
23+
'^tippy\\.js$': '<rootDir>/src/__mocks__/tippy.js.ts',
2324
// Bypass React.lazy wrappers — resolve Foo.lazy → Foo so tests render synchronously
2425
'(.*)\\.lazy$': '$1'
2526
}

packages/agentflow/package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,13 @@
7272
"@codemirror/lang-json": "^6.0.0",
7373
"@codemirror/lang-python": "^6.1.0",
7474
"@tabler/icons-react": "^3.7.0",
75-
"@tiptap/extension-code-block-lowlight": "^3.4.3",
76-
"@tiptap/extension-placeholder": "^2.11.5",
77-
"@tiptap/react": "^2.11.5",
78-
"@tiptap/starter-kit": "^2.11.5",
75+
"@tiptap/core": "^3.20.4",
76+
"@tiptap/extension-code-block-lowlight": "^3.20.4",
77+
"@tiptap/extension-mention": "^3.20.4",
78+
"@tiptap/extension-placeholder": "^3.20.4",
79+
"@tiptap/pm": "^3.20.4",
80+
"@tiptap/react": "^3.20.4",
81+
"@tiptap/starter-kit": "^3.20.4",
7982
"@uiw/codemirror-theme-sublime": "^4.21.0",
8083
"@uiw/codemirror-theme-vscode": "^4.21.0",
8184
"@uiw/react-codemirror": "^4.21.0",
@@ -85,6 +88,7 @@
8588
"html-react-parser": "^3.0.16",
8689
"lodash": "^4.17.21",
8790
"lowlight": "^3.3.0",
91+
"tippy.js": "^6.3.7",
8892
"uuid": "^10.0.0"
8993
},
9094
"devDependencies": {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const mergeAttributes = jest.fn((...attrs: Record<string, unknown>[]) => Object.assign({}, ...attrs))
2+
3+
export class PasteRule {
4+
find: unknown
5+
handler: unknown
6+
constructor(config: Record<string, unknown>) {
7+
this.find = config.find
8+
this.handler = config.handler
9+
}
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const Mention = {
2+
extend: jest.fn(() => ({
3+
configure: jest.fn(() => 'CustomMention')
4+
})),
5+
configure: jest.fn(() => 'Mention')
6+
}
7+
export default Mention
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const Markdown = { configure: jest.fn(() => 'Markdown') }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const model = {}
2+
export const state = {}
3+
export const view = {}
4+
export const transform = {}

packages/agentflow/src/__mocks__/@tiptap/react.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createElement, forwardRef } from 'react'
22

33
export const useEditor = (config?: Record<string, unknown>) => ({
44
getHTML: () => (config?.content as string) ?? '<p></p>',
5+
getMarkdown: () => (config?.content as string) ?? '',
56
setEditable: jest.fn(),
67
commands: { focus: jest.fn(), setContent: jest.fn() },
78
_onUpdate: config?.onUpdate
@@ -11,3 +12,10 @@ export const EditorContent = forwardRef<HTMLDivElement, { editor?: unknown; [k:
1112
createElement('div', { ref, 'data-testid': 'tiptap-editor-content', 'data-has-editor': !!editor, ...rest })
1213
)
1314
EditorContent.displayName = 'EditorContent'
15+
16+
export const ReactRenderer = jest.fn().mockImplementation(() => ({
17+
element: document.createElement('div'),
18+
ref: null,
19+
updateProps: jest.fn(),
20+
destroy: jest.fn()
21+
}))
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const tippy = jest.fn(() => [
2+
{
3+
setProps: jest.fn(),
4+
destroy: jest.fn(),
5+
hide: jest.fn(),
6+
show: jest.fn()
7+
}
8+
])
9+
export default tippy

packages/agentflow/src/atoms/JsonInput.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { FormControl, Popover } from '@mui/material'
44
import { useTheme } from '@mui/material/styles'
55
import ReactJson from 'flowise-react-json-view'
66

7-
import type { VariableItem } from './SelectVariable'
8-
import { SelectVariable } from './SelectVariable'
7+
import type { VariableItem } from './VariablePicker'
8+
import { VariablePicker } from './VariablePicker'
99

1010
export interface JsonInputProps {
1111
value: string
@@ -153,7 +153,7 @@ export function JsonInput({ value, onChange, disabled = false, variableItems }:
153153
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
154154
slotProps={{ paper: { sx: { width: 320, maxHeight: 400 } } }}
155155
>
156-
<SelectVariable
156+
<VariablePicker
157157
items={variableItems!}
158158
onSelect={(val) => {
159159
setNewVal(val)

packages/agentflow/src/atoms/NodeInputHandler.test.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ jest.mock('./RichTextEditor.lazy', () => ({
3636
)
3737
}))
3838

39+
jest.mock('./VariableInput', () => ({
40+
VariableInput: ({ suggestionItems }: { suggestionItems?: { id: string }[] }) => (
41+
<div data-testid='variable-input' data-suggestion-ids={JSON.stringify(suggestionItems?.map((i) => i.id))} />
42+
)
43+
}))
44+
3945
jest.mock('@tabler/icons-react', () => ({
4046
IconArrowsMaximize: () => <span data-testid='icon-expand' />,
4147
IconInfoCircle: () => <span data-testid='icon-info-circle' />,
@@ -328,8 +334,8 @@ jest.mock('./JsonInput', () => ({
328334
)
329335
}))
330336

331-
jest.mock('./SelectVariable', () => ({
332-
SelectVariable: ({ items, onSelect }: { items: Array<{ value: string }>; onSelect: (v: string) => void }) => (
337+
jest.mock('./VariablePicker', () => ({
338+
VariablePicker: ({ items, onSelect }: { items: Array<{ value: string }>; onSelect: (v: string) => void }) => (
333339
<div data-testid='select-variable'>
334340
{items.map((item, i) => (
335341
<button key={i} data-testid={`var-${item.value}`} onClick={() => onSelect(item.value)}>
@@ -503,6 +509,28 @@ describe('NodeInputHandler – variable popover', () => {
503509

504510
expect(screen.queryByTestId('icon-variable')).toBeNull()
505511
})
512+
513+
it('deduplicates suggestionItem ids when variableItems share the same value', () => {
514+
// Two flow-state entries with the same key produce the same base id.
515+
// The first should keep its id; subsequent duplicates get a __N suffix.
516+
const variableItems = [
517+
{ label: '$flow.state.myVar', value: '$flow.state.myVar', category: 'Flow State' },
518+
{ label: '$flow.state.myVar', value: '$flow.state.myVar', category: 'Flow State' },
519+
{ label: '$flow.state.other', value: '$flow.state.other', category: 'Flow State' }
520+
]
521+
render(
522+
<NodeInputHandler
523+
inputParam={makeParam({ type: 'string', acceptVariable: true })}
524+
data={baseNodeData}
525+
isAdditionalParams
526+
onDataChange={mockOnDataChange}
527+
variableItems={variableItems}
528+
/>
529+
)
530+
531+
const ids = JSON.parse(screen.getByTestId('variable-input').getAttribute('data-suggestion-ids')!)
532+
expect(ids).toEqual(['$flow.state.myVar', '$flow.state.myVar__1', '$flow.state.other'])
533+
})
506534
})
507535

508536
describe('NodeInputHandler – credential type rendering', () => {

0 commit comments

Comments
 (0)