mirror of
https://github.com/langgenius/dify.git
synced 2026-06-07 16:32:01 +08:00
fix(explore): render human input preview handles (#37086)
This commit is contained in:
parent
a1ad4be61e
commit
9da4d167fa
@ -1,4 +1,5 @@
|
|||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
|
import { UserActionButtonType } from '@/app/components/workflow/nodes/human-input/types'
|
||||||
import { BlockEnum } from '@/app/components/workflow/types'
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
import CustomNode from '../index'
|
import CustomNode from '../index'
|
||||||
|
|
||||||
@ -41,4 +42,39 @@ describe('workflow preview custom node', () => {
|
|||||||
expect(getByText('Classifier node')).toBeInTheDocument()
|
expect(getByText('Classifier node')).toBeInTheDocument()
|
||||||
expect(container.querySelector('[data-handleid="class-a"]')).toBeInTheDocument()
|
expect(container.querySelector('[data-handleid="class-a"]')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders human input output handles from the mapped node component', () => {
|
||||||
|
const props: React.ComponentProps<typeof CustomNode> = {
|
||||||
|
id: 'human-input-1',
|
||||||
|
type: 'custom-node',
|
||||||
|
selected: false,
|
||||||
|
zIndex: 1,
|
||||||
|
isConnectable: true,
|
||||||
|
dragging: false,
|
||||||
|
xPos: 0,
|
||||||
|
yPos: 0,
|
||||||
|
dragHandle: undefined,
|
||||||
|
data: {
|
||||||
|
type: BlockEnum.HumanInput,
|
||||||
|
title: 'Human Input',
|
||||||
|
desc: '',
|
||||||
|
delivery_methods: [],
|
||||||
|
form_content: '',
|
||||||
|
inputs: [],
|
||||||
|
user_actions: [
|
||||||
|
{ id: 'approve', title: 'Approve', button_style: UserActionButtonType.Primary },
|
||||||
|
],
|
||||||
|
timeout: 1,
|
||||||
|
timeout_unit: 'hour',
|
||||||
|
} as never,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { container, getByText } = render(
|
||||||
|
<CustomNode {...props} />,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(getByText('Human Input')).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('[data-handleid="approve"]')).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('[data-handleid="__timeout"]')).toBeInTheDocument()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { BlockEnum } from '@/app/components/workflow/types'
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
|
import HumanInputNode from './human-input/node'
|
||||||
import IfElseNode from './if-else/node'
|
import IfElseNode from './if-else/node'
|
||||||
import IterationNode from './iteration/node'
|
import IterationNode from './iteration/node'
|
||||||
import LoopNode from './loop/node'
|
import LoopNode from './loop/node'
|
||||||
import QuestionClassifierNode from './question-classifier/node'
|
import QuestionClassifierNode from './question-classifier/node'
|
||||||
|
|
||||||
// todo: add human-input node support
|
|
||||||
export const NodeComponentMap: Record<string, any> = {
|
export const NodeComponentMap: Record<string, any> = {
|
||||||
[BlockEnum.QuestionClassifier]: QuestionClassifierNode,
|
[BlockEnum.QuestionClassifier]: QuestionClassifierNode,
|
||||||
[BlockEnum.IfElse]: IfElseNode,
|
[BlockEnum.IfElse]: IfElseNode,
|
||||||
|
[BlockEnum.HumanInput]: HumanInputNode,
|
||||||
[BlockEnum.Iteration]: IterationNode,
|
[BlockEnum.Iteration]: IterationNode,
|
||||||
[BlockEnum.Loop]: LoopNode,
|
[BlockEnum.Loop]: LoopNode,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { render } from '@testing-library/react'
|
||||||
|
import { UserActionButtonType } from '@/app/components/workflow/nodes/human-input/types'
|
||||||
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
|
import Node from '../node'
|
||||||
|
|
||||||
|
vi.mock('reactflow', () => ({
|
||||||
|
Handle: (props: { id: string, type: string, className?: string }) => (
|
||||||
|
<div data-testid="handle" data-handleid={props.id} data-type={props.type} className={props.className} />
|
||||||
|
),
|
||||||
|
Position: {
|
||||||
|
Right: 'right',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('workflow preview human input node', () => {
|
||||||
|
it('renders one output handle per user action and timeout', () => {
|
||||||
|
const props: React.ComponentProps<typeof Node> = {
|
||||||
|
id: 'human-input-1',
|
||||||
|
type: 'human-input-node',
|
||||||
|
selected: false,
|
||||||
|
zIndex: 1,
|
||||||
|
isConnectable: true,
|
||||||
|
dragging: false,
|
||||||
|
xPos: 0,
|
||||||
|
yPos: 0,
|
||||||
|
dragHandle: undefined,
|
||||||
|
data: {
|
||||||
|
type: BlockEnum.HumanInput,
|
||||||
|
title: 'Human Input',
|
||||||
|
desc: '',
|
||||||
|
delivery_methods: [],
|
||||||
|
form_content: '',
|
||||||
|
inputs: [],
|
||||||
|
user_actions: [
|
||||||
|
{ id: 'approve', title: 'Approve', button_style: UserActionButtonType.Primary },
|
||||||
|
{ id: 'regenerate', title: 'Regenerate', button_style: UserActionButtonType.Default },
|
||||||
|
],
|
||||||
|
timeout: 1,
|
||||||
|
timeout_unit: 'hour',
|
||||||
|
} as never,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { container, getByText } = render(
|
||||||
|
<Node {...props} />,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(getByText('approve')).toBeInTheDocument()
|
||||||
|
expect(getByText('regenerate')).toBeInTheDocument()
|
||||||
|
expect(getByText('Timeout')).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('[data-handleid="approve"]')).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('[data-handleid="regenerate"]')).toBeInTheDocument()
|
||||||
|
expect(container.querySelector('[data-handleid="__timeout"]')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import type { NodeProps } from 'reactflow'
|
||||||
|
import type { HumanInputNodeType } from '@/app/components/workflow/nodes/human-input/types'
|
||||||
|
import { memo } from 'react'
|
||||||
|
import { NodeSourceHandle } from '../../node-handle'
|
||||||
|
|
||||||
|
function HumanInputNode(props: NodeProps<HumanInputNodeType>) {
|
||||||
|
const { data } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-0.5 px-3 py-1">
|
||||||
|
{data.user_actions.map(userAction => (
|
||||||
|
<div key={userAction.id} className="relative flex h-6 flex-row-reverse items-center px-1">
|
||||||
|
<span className="truncate system-xs-semibold-uppercase text-text-secondary">{userAction.id}</span>
|
||||||
|
<NodeSourceHandle
|
||||||
|
{...props}
|
||||||
|
handleId={userAction.id}
|
||||||
|
handleClassName="top-1/2! -right-[21px]! -translate-y-1/2!"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="relative flex h-6 flex-row-reverse items-center px-1">
|
||||||
|
<span className="truncate system-xs-semibold-uppercase text-text-secondary">Timeout</span>
|
||||||
|
<NodeSourceHandle
|
||||||
|
{...props}
|
||||||
|
handleId="__timeout"
|
||||||
|
handleClassName="top-1/2! -right-[21px]! -translate-y-1/2!"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(HumanInputNode)
|
||||||
Loading…
Reference in New Issue
Block a user