mirror of https://github.com/langgenius/dify.git
feat: can insert hitl node by /
This commit is contained in:
parent
6b11973151
commit
71a511a470
|
|
@ -4,6 +4,7 @@ import type { FC } from 'react'
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import type {
|
||||
EditorState,
|
||||
LexicalCommand,
|
||||
} from 'lexical'
|
||||
import {
|
||||
$getRoot,
|
||||
|
|
@ -119,7 +120,7 @@ export type PromptEditorProps = {
|
|||
errorMessageBlock?: ErrorMessageBlockType
|
||||
lastRunBlock?: LastRunBlockType
|
||||
isSupportFileVar?: boolean
|
||||
shortcutPopups?: Array<{ hotkey: Hotkey; Popup: React.ComponentType<{ onClose: () => void }> }>
|
||||
shortcutPopups?: Array<{ hotkey: Hotkey; Popup: React.ComponentType<{ onClose: () => void, onInsert: (command: LexicalCommand<unknown>, params: any[]) => void }> }>
|
||||
}
|
||||
|
||||
const PromptEditor: FC<PromptEditorProps> = ({
|
||||
|
|
@ -229,7 +230,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
|||
/>
|
||||
{shortcutPopups?.map(({ hotkey, Popup }, idx) => (
|
||||
<ShortcutsPopupPlugin key={idx} hotkey={hotkey} >
|
||||
{closePortal => <Popup onClose={closePortal} />}
|
||||
{(closePortal, onInsert) => <Popup onClose={closePortal} onInsert={onInsert} />}
|
||||
</ShortcutsPopupPlugin>
|
||||
))}
|
||||
<ComponentPickerBlock
|
||||
|
|
|
|||
|
|
@ -3,13 +3,20 @@ import {
|
|||
useEffect,
|
||||
} from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { QueryBlockType } from '../../types'
|
||||
import type {
|
||||
HITLNodeProps,
|
||||
} from './node'
|
||||
import {
|
||||
$createHITLInputNode,
|
||||
HITLInputNode,
|
||||
} from './node'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
|
||||
export const INSERT_HITL_INPUT_BLOCK_COMMAND = createCommand('INSERT_HITL_INPUT_BLOCK_COMMAND')
|
||||
export const DELETE_HITL_INPUT_BLOCK_COMMAND = createCommand('DELETE_HITL_INPUT_BLOCK_COMMAND')
|
||||
|
|
@ -27,6 +34,49 @@ const HITLInputBlock = memo(({
|
|||
useEffect(() => {
|
||||
if (!editor.hasNodes([HITLInputNode]))
|
||||
throw new Error('HITLInputBlockPlugin: HITLInputBlock not registered on editor')
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_HITL_INPUT_BLOCK_COMMAND,
|
||||
(nodeProps: HITLNodeProps) => {
|
||||
const {
|
||||
variableName,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
} = nodeProps
|
||||
const currentHITLNode = $createHITLInputNode(
|
||||
variableName,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
)
|
||||
|
||||
$insertNodes([currentHITLNode])
|
||||
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_HITL_INPUT_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete)
|
||||
onDelete()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
)
|
||||
}, [editor, onInsert, onDelete])
|
||||
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -8,16 +8,22 @@ import { getKeyboardKeyNameBySystem } from '@/app/components/workflow/utils'
|
|||
import type { FormInputItem, FormInputItemPlaceholder } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import PrePopulate from './pre-populate'
|
||||
import produce from 'immer'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.humanInput.insertInputField'
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
isEdit: boolean
|
||||
payload: FormInputItem
|
||||
payload?: FormInputItem
|
||||
onChange: (newPayload: FormInputItem) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
const defaultPayload: FormInputItem = {
|
||||
type: InputVarType.paragraph,
|
||||
output_variable_name: '',
|
||||
placeholder: { type: 'const', selector: [], value: '' },
|
||||
}
|
||||
const InputField: React.FC<Props> = ({
|
||||
nodeId,
|
||||
isEdit,
|
||||
|
|
@ -26,7 +32,7 @@ const InputField: React.FC<Props> = ({
|
|||
onCancel,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [tempPayload, setTempPayload] = useState(payload)
|
||||
const [tempPayload, setTempPayload] = useState(payload || defaultPayload)
|
||||
const handleSave = useCallback(() => {
|
||||
onChange(tempPayload)
|
||||
}, [tempPayload])
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { DecoratorNode } from 'lexical'
|
|||
import HILTInputBlockComponent from './component'
|
||||
import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types'
|
||||
|
||||
export type SerializedNode = SerializedLexicalNode & {
|
||||
export type HITLNodeProps = {
|
||||
variableName: string
|
||||
nodeId: string
|
||||
nodeTitle: string
|
||||
|
|
@ -13,6 +13,8 @@ export type SerializedNode = SerializedLexicalNode & {
|
|||
onFormInputItemRemove: (varName: string) => void
|
||||
}
|
||||
|
||||
export type SerializedNode = SerializedLexicalNode & HITLNodeProps
|
||||
|
||||
export class HITLInputNode extends DecoratorNode<React.JSX.Element> {
|
||||
__variableName: string
|
||||
__nodeId: string
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
useState,
|
||||
} from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import type { LexicalCommand } from 'lexical'
|
||||
import {
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
|
|
@ -24,7 +25,7 @@ export type Hotkey = string | string[] | string[][] | ((e: KeyboardEvent) => boo
|
|||
|
||||
type ShortcutPopupPluginProps = {
|
||||
hotkey?: Hotkey
|
||||
children?: React.ReactNode | ((close: () => void) => React.ReactNode)
|
||||
children?: React.ReactNode | ((close: () => void, onInsert: (command: LexicalCommand<unknown>, params: any[]) => void) => React.ReactNode)
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
container?: Element | null
|
||||
|
|
@ -261,6 +262,11 @@ export default function ShortcutsPopupPlugin({
|
|||
return () => document.removeEventListener('mousedown', onMouseDown, true)
|
||||
}, [open, closePortal])
|
||||
|
||||
const handleInsert = useCallback((command: LexicalCommand<unknown>, params: any) => {
|
||||
editor.dispatchCommand(command, params)
|
||||
closePortal()
|
||||
}, [editor, closePortal])
|
||||
|
||||
if (!open || !containerEl)
|
||||
return null
|
||||
|
||||
|
|
@ -274,7 +280,7 @@ export default function ShortcutsPopupPlugin({
|
|||
)}
|
||||
style={{ top: `${position.top}px`, left: `${position.left}px`, ...style }}
|
||||
>
|
||||
{typeof children === 'function' ? children(closePortal) : (children ?? SHORTCUTS_EMPTY_CONTENT)}
|
||||
{typeof children === 'function' ? children(closePortal, handleInsert) : (children ?? SHORTCUTS_EMPTY_CONTENT)}
|
||||
</div>,
|
||||
containerEl,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
'use client'
|
||||
import InputField from '@/app/components/base/prompt-editor/plugins/hitl-input-block/input-field'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import type { FormInputItem } from '../types'
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
onSave: (newPayload: FormInputItem) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
const AddInputField: FC<Props> = ({
|
||||
nodeId,
|
||||
onSave,
|
||||
onCancel,
|
||||
}) => {
|
||||
return (
|
||||
<InputField
|
||||
nodeId={nodeId}
|
||||
isEdit={false}
|
||||
onChange={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default React.memo(AddInputField)
|
||||
|
|
@ -7,28 +7,31 @@ import { BlockEnum } from '../../../types'
|
|||
import { useWorkflowVariableType } from '../../../hooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { FormInputItem } from '../types'
|
||||
import AddInputField from './add-input-field'
|
||||
import { INSERT_HITL_INPUT_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/hitl-input-block'
|
||||
import type { LexicalCommand } from 'lexical'
|
||||
|
||||
type Props = {
|
||||
nodeId: string
|
||||
nodeTitle: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
formInputs: FormInputItem[]
|
||||
onFormInputsChange: (payload: FormInputItem[]) => void
|
||||
onFormInputItemRename: (payload: FormInputItem, oldName: string) => void
|
||||
onFormInputItemRemove: (varName: string) => void
|
||||
nodeTitle: string
|
||||
editorKey: number
|
||||
}
|
||||
|
||||
const FormContent: FC<Props> = ({
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
value,
|
||||
onChange,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
nodeTitle,
|
||||
editorKey,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
|
@ -43,6 +46,21 @@ const FormContent: FC<Props> = ({
|
|||
|
||||
const getVarType = useWorkflowVariableType()
|
||||
|
||||
const handleInsertHITLNode = (onInsert: (command: LexicalCommand<unknown>, params: any) => void) => {
|
||||
return (payload: FormInputItem) => {
|
||||
// todo insert into form inputs
|
||||
onInsert(INSERT_HITL_INPUT_BLOCK_COMMAND, {
|
||||
variableName: payload.output_variable_name,
|
||||
nodeId,
|
||||
nodeTitle,
|
||||
formInputs,
|
||||
onFormInputsChange,
|
||||
onFormInputItemRename,
|
||||
onFormInputItemRemove,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PromptEditor
|
||||
|
|
@ -81,6 +99,16 @@ const FormContent: FC<Props> = ({
|
|||
}, {}),
|
||||
}}
|
||||
editable
|
||||
shortcutPopups={[{
|
||||
hotkey: ['mod', '/'],
|
||||
Popup: ({ onClose, onInsert }) => (
|
||||
<AddInputField
|
||||
nodeId={nodeId}
|
||||
onSave={handleInsertHITLNode(onInsert!)}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
),
|
||||
}]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue