mirror of https://github.com/langgenius/dify.git
feat: Implement input field management panel
This commit is contained in:
parent
ec501cf664
commit
44d569a7c1
|
|
@ -1,7 +1,7 @@
|
|||
import type { ZodSchema, ZodString } from 'zod'
|
||||
import { z } from 'zod'
|
||||
import { type InputFieldConfiguration, InputFieldType } from './types'
|
||||
import { SupportedFileTypes, TransferMethod } from '@/app/components/rag-pipeline/components/input-field/editor/form/schema'
|
||||
import { SupportedFileTypes, TransferMethod } from '@/app/components/rag-pipeline/components/panel/input-field/editor/form/schema'
|
||||
|
||||
export const generateZodSchema = (fields: InputFieldConfiguration[]) => {
|
||||
const shape: Record<string, ZodSchema> = {}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
import { useCallback } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type DialogWrapperProps = {
|
||||
dialogClassName?: string
|
||||
className?: string
|
||||
panelWrapperClassName?: string
|
||||
outerWrapperClassName?: string
|
||||
children: ReactNode
|
||||
show: boolean
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
const DialogWrapper = ({
|
||||
dialogClassName,
|
||||
className,
|
||||
panelWrapperClassName,
|
||||
outerWrapperClassName,
|
||||
children,
|
||||
show,
|
||||
onClose,
|
||||
}: DialogWrapperProps) => {
|
||||
const close = useCallback(() => onClose?.(), [onClose])
|
||||
return (
|
||||
<Transition appear show={show}>
|
||||
<Dialog
|
||||
as='div'
|
||||
className={cn('relative z-40', dialogClassName)}
|
||||
onClose={close}
|
||||
>
|
||||
<TransitionChild>
|
||||
<div
|
||||
className={cn(
|
||||
'fixed inset-0 bg-black/25',
|
||||
'data-[closed]:opacity-0',
|
||||
'data-[enter]:opacity-100 data-[enter]:duration-300 data-[enter]:ease-out',
|
||||
'data-[leave]:opacity-0 data-[leave]:duration-200 data-[leave]:ease-in',
|
||||
)}
|
||||
/>
|
||||
</TransitionChild>
|
||||
|
||||
<div className={cn('fixed inset-0', outerWrapperClassName)}>
|
||||
<div className={cn('flex min-h-full flex-col items-end justify-center pb-1 pt-[116px]', panelWrapperClassName)}>
|
||||
<TransitionChild>
|
||||
<DialogPanel
|
||||
className={cn(
|
||||
'relative flex w-[420px] flex-col overflow-hidden border-components-panel-border bg-components-panel-bg-alt p-0 shadow-xl shadow-shadow-shadow-5 transition-all',
|
||||
'data-[closed]:scale-95 data-[closed]:opacity-0',
|
||||
'data-[enter]:scale-100 data-[enter]:opacity-100 data-[enter]:duration-300 data-[enter]:ease-out',
|
||||
'data-[leave]:scale-95 data-[leave]:opacity-0 data-[leave]:duration-200 data-[leave]:ease-in',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition >
|
||||
)
|
||||
}
|
||||
|
||||
export default DialogWrapper
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { RiCloseLine, RiEyeLine } from '@remixicon/react'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import DialogWrapper from './dialog-wrapper'
|
||||
import FieldList from './field-list'
|
||||
import FooterTip from './footer-tip'
|
||||
import GlobalInputs from './label-right-content/global-inputs'
|
||||
import Datasource from './label-right-content/datasource'
|
||||
import { useNodes } from 'reactflow'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks'
|
||||
import type { InputVar, RAGPipelineVariables } from '@/models/pipeline'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import cn from '@/utils/classnames'
|
||||
import PreviewPanel from './preview'
|
||||
|
||||
type InputFieldDialogProps = {
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
const InputFieldDialog = ({
|
||||
readonly = false,
|
||||
}: InputFieldDialogProps) => {
|
||||
const { t } = useTranslation()
|
||||
const nodes = useNodes<DataSourceNodeType>()
|
||||
const showInputFieldDialog = useStore(state => state.showInputFieldDialog)
|
||||
const setShowInputFieldDialog = useStore(state => state.setShowInputFieldDialog)
|
||||
const ragPipelineVariables = useStore(state => state.ragPipelineVariables)
|
||||
const setRagPipelineVariables = useStore(state => state.setRagPipelineVariables)
|
||||
const [previewPanelOpen, setPreviewPanelOpen] = useState(false)
|
||||
|
||||
const getInputFieldsMap = () => {
|
||||
const inputFieldsMap: Record<string, InputVar[]> = {}
|
||||
ragPipelineVariables?.forEach((variable) => {
|
||||
const { belong_to_node_id: nodeId, ...varConfig } = variable
|
||||
if (inputFieldsMap[nodeId])
|
||||
inputFieldsMap[nodeId].push(varConfig)
|
||||
else
|
||||
inputFieldsMap[nodeId] = [varConfig]
|
||||
})
|
||||
return inputFieldsMap
|
||||
}
|
||||
const inputFieldsMap = useRef(getInputFieldsMap())
|
||||
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const datasourceNodeDataMap = useMemo(() => {
|
||||
const datasourceNodeDataMap: Record<string, DataSourceNodeType> = {}
|
||||
const datasourceNodes: Node<DataSourceNodeType>[] = nodes.filter(node => node.data.type === BlockEnum.DataSource)
|
||||
datasourceNodes.forEach((node) => {
|
||||
const { id, data } = node
|
||||
datasourceNodeDataMap[id] = data
|
||||
})
|
||||
return datasourceNodeDataMap
|
||||
}, [nodes])
|
||||
|
||||
const updateInputFields = useCallback(async (key: string, value: InputVar[]) => {
|
||||
inputFieldsMap.current[key] = value
|
||||
const datasourceNodeInputFields: RAGPipelineVariables = []
|
||||
const globalInputFields: RAGPipelineVariables = []
|
||||
Object.keys(inputFieldsMap.current).forEach((key) => {
|
||||
const inputFields = inputFieldsMap.current[key]
|
||||
inputFields.forEach((inputField) => {
|
||||
if (key === 'shared') {
|
||||
globalInputFields.push({
|
||||
...inputField,
|
||||
belong_to_node_id: key,
|
||||
})
|
||||
}
|
||||
else {
|
||||
datasourceNodeInputFields.push({
|
||||
...inputField,
|
||||
belong_to_node_id: key,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
// Datasource node input fields come first, then global input fields
|
||||
const newRagPipelineVariables = [...datasourceNodeInputFields, ...globalInputFields]
|
||||
setRagPipelineVariables?.(newRagPipelineVariables)
|
||||
handleSyncWorkflowDraft()
|
||||
}, [setRagPipelineVariables, handleSyncWorkflowDraft])
|
||||
|
||||
const closePanel = useCallback(() => {
|
||||
setShowInputFieldDialog?.(false)
|
||||
}, [setShowInputFieldDialog])
|
||||
|
||||
const togglePreviewPanel = useCallback(() => {
|
||||
setPreviewPanelOpen(prev => !prev)
|
||||
}, [])
|
||||
|
||||
const allVariableNames = useMemo(() => {
|
||||
return ragPipelineVariables?.map(variable => variable.variable) || []
|
||||
}, [ragPipelineVariables])
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogWrapper
|
||||
show={!!showInputFieldDialog}
|
||||
onClose={closePanel}
|
||||
outerWrapperClassName='overflow-y-auto'
|
||||
panelWrapperClassName='justify-start'
|
||||
className='grow rounded-l-2xl border-y-[0.5px] border-l-[0.5px]'
|
||||
>
|
||||
<div className='flex shrink-0 items-center p-4 pb-0'>
|
||||
<div className='system-xl-semibold grow'>
|
||||
{t('datasetPipeline.inputFieldPanel.title')}
|
||||
</div>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
size='small'
|
||||
className={cn(
|
||||
'shrink-0 gap-x-px px-1.5',
|
||||
previewPanelOpen && 'bg-state-accent-active text-text-accent',
|
||||
)}
|
||||
onClick={togglePreviewPanel}
|
||||
>
|
||||
<RiEyeLine className='size-3.5' />
|
||||
<span className='px-[3px]'>{t('datasetPipeline.operations.preview')}</span>
|
||||
</Button>
|
||||
<Divider type='vertical' className='mx-1 h-3' />
|
||||
<button
|
||||
type='button'
|
||||
className='flex size-6 shrink-0 items-center justify-center p-0.5'
|
||||
onClick={closePanel}
|
||||
>
|
||||
<RiCloseLine className='size-4 text-text-tertiary' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='system-sm-regular shrink-0 px-4 pb-2 pt-1 text-text-tertiary'>
|
||||
{t('datasetPipeline.inputFieldPanel.description')}
|
||||
</div>
|
||||
<div className='flex grow flex-col overflow-hidden'>
|
||||
{/* Unique Inputs for Each Entrance */}
|
||||
<div className='flex h-6 items-center gap-x-0.5 px-4 pt-2'>
|
||||
<span className='system-sm-semibold-uppercase text-text-secondary'>
|
||||
{t('datasetPipeline.inputFieldPanel.uniqueInputs.title')}
|
||||
</span>
|
||||
<Tooltip
|
||||
popupContent={t('datasetPipeline.inputFieldPanel.uniqueInputs.tooltip')}
|
||||
popupClassName='max-w-[240px]'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1 py-1'>
|
||||
{
|
||||
Object.keys(datasourceNodeDataMap).map((key) => {
|
||||
const inputFields = inputFieldsMap.current[key] || []
|
||||
return (
|
||||
<FieldList
|
||||
key={key}
|
||||
nodeId={key}
|
||||
LabelRightContent={<Datasource nodeData={datasourceNodeDataMap[key]} />}
|
||||
inputFields={inputFields}
|
||||
readonly={readonly}
|
||||
labelClassName='pt-1 pb-1'
|
||||
handleInputFieldsChange={updateInputFields}
|
||||
allVariableNames={allVariableNames}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
{/* Global Inputs */}
|
||||
<FieldList
|
||||
nodeId='shared'
|
||||
LabelRightContent={<GlobalInputs />}
|
||||
inputFields={inputFieldsMap.current.shared || []}
|
||||
readonly={readonly}
|
||||
labelClassName='pt-2 pb-1'
|
||||
handleInputFieldsChange={updateInputFields}
|
||||
allVariableNames={allVariableNames}
|
||||
/>
|
||||
</div>
|
||||
<FooterTip />
|
||||
</DialogWrapper>
|
||||
{previewPanelOpen && (
|
||||
<PreviewPanel
|
||||
show={previewPanelOpen}
|
||||
onClose={togglePreviewPanel}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(InputFieldDialog)
|
||||
|
|
@ -7,22 +7,38 @@ import Panel from '@/app/components/workflow/panel'
|
|||
import { useStore } from '@/app/components/workflow/store'
|
||||
import Record from '@/app/components/workflow/panel/record'
|
||||
import TestRunPanel from './test-run'
|
||||
import InputFieldPanel from './input-field'
|
||||
import PreviewPanel from './input-field/preview'
|
||||
import InputFieldEditorPanel from './input-field/editor'
|
||||
|
||||
const RagPipelinePanelOnRight = () => {
|
||||
const historyWorkflowData = useStore(s => s.historyWorkflowData)
|
||||
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||
return (
|
||||
<>
|
||||
{
|
||||
historyWorkflowData && (
|
||||
<Record />
|
||||
)
|
||||
}
|
||||
{historyWorkflowData && <Record />}
|
||||
{showDebugAndPreviewPanel && <TestRunPanel />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const RagPipelinePanelOnLeft = () => {
|
||||
const showInputFieldPanel = useStore(s => s.showInputFieldPanel)
|
||||
const showInputFieldPreviewPanel = useStore(s => s.showInputFieldPreviewPanel)
|
||||
const inputFieldEditPanelProps = useStore(s => s.inputFieldEditPanelProps)
|
||||
return (
|
||||
<>
|
||||
{showInputFieldPreviewPanel && <PreviewPanel />}
|
||||
{inputFieldEditPanelProps && (
|
||||
<InputFieldEditorPanel
|
||||
{...inputFieldEditPanelProps}
|
||||
/>
|
||||
)}
|
||||
{showInputFieldPanel && <InputFieldPanel />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const RagPipelinePanel = () => {
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const versionHistoryPanelProps = useMemo(() => {
|
||||
|
|
@ -37,7 +53,7 @@ const RagPipelinePanel = () => {
|
|||
const panelProps: PanelProps = useMemo(() => {
|
||||
return {
|
||||
components: {
|
||||
left: null,
|
||||
left: <RagPipelinePanelOnLeft />,
|
||||
right: <RagPipelinePanelOnRight />,
|
||||
},
|
||||
versionHistoryPanelProps,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { RiCloseLine } from '@remixicon/react'
|
||||
import DialogWrapper from '../dialog-wrapper'
|
||||
import InputFieldForm from './form'
|
||||
import { convertFormDataToINputField, convertToInputFieldFormData } from './utils'
|
||||
import { useCallback } from 'react'
|
||||
|
|
@ -8,15 +7,13 @@ import type { InputVar } from '@/models/pipeline'
|
|||
import type { FormData } from './form/types'
|
||||
import type { MoreInfo } from '@/app/components/workflow/types'
|
||||
|
||||
type InputFieldEditorProps = {
|
||||
show: boolean
|
||||
export type InputFieldEditorProps = {
|
||||
onClose: () => void
|
||||
onSubmit: (data: InputVar, moreInfo?: MoreInfo) => void
|
||||
initialData?: InputVar
|
||||
}
|
||||
|
||||
const InputFieldEditor = ({
|
||||
show,
|
||||
const InputFieldEditorPanel = ({
|
||||
onClose,
|
||||
onSubmit,
|
||||
initialData,
|
||||
|
|
@ -30,13 +27,7 @@ const InputFieldEditor = ({
|
|||
}, [onSubmit])
|
||||
|
||||
return (
|
||||
<DialogWrapper
|
||||
show={show}
|
||||
onClose={onClose}
|
||||
outerWrapperClassName='overflow-y-auto'
|
||||
panelWrapperClassName='pr-[424px] justify-start'
|
||||
className='w-[400px] rounded-2xl border-[0.5px] bg-components-panel-bg shadow-shadow-shadow-9'
|
||||
>
|
||||
<div className='relative mr-1 flex h-fit max-h-full w-[400px] flex-col overflow-y-auto rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-2xl shadow-shadow-shadow-9'>
|
||||
<div className='system-xl-semibold flex items-center pb-1 pl-4 pr-11 pt-3.5 text-text-primary'>
|
||||
{initialData ? t('datasetPipeline.inputFieldPanel.editInputField') : t('datasetPipeline.inputFieldPanel.addInputField')}
|
||||
</div>
|
||||
|
|
@ -53,8 +44,8 @@ const InputFieldEditor = ({
|
|||
onCancel={onClose}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
</DialogWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InputFieldEditor
|
||||
export default InputFieldEditorPanel
|
||||
|
|
@ -10,8 +10,9 @@ import type { MoreInfo, ValueSelector } from '@/app/components/workflow/types'
|
|||
import { ChangeType } from '@/app/components/workflow/types'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { usePipeline } from '../../../hooks/use-pipeline'
|
||||
import { usePipeline } from '../../../../hooks/use-pipeline'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
|
||||
const VARIABLE_PREFIX = 'rag'
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ export const useFieldList = ({
|
|||
allVariableNames,
|
||||
}: useFieldListProps) => {
|
||||
const { t } = useTranslation()
|
||||
const setInputFieldEditPanelProps = useStore(s => s.setInputFieldEditPanelProps)
|
||||
const [inputFields, setInputFields] = useState<InputVar[]>(initialInputFields)
|
||||
const inputFieldsRef = useRef<InputVar[]>(inputFields)
|
||||
const [removedVar, setRemovedVar] = useState<ValueSelector>([])
|
||||
|
|
@ -55,20 +57,12 @@ export const useFieldList = ({
|
|||
handleInputFieldsChange(newInputFields)
|
||||
}, [handleInputFieldsChange])
|
||||
|
||||
const [editingField, setEditingField] = useState<InputVar | undefined>()
|
||||
const [showInputFieldEditor, setShowInputFieldEditor] = useState(false)
|
||||
const editingFieldIndex = useRef<number>(-1)
|
||||
const handleOpenInputFieldEditor = useCallback((id?: string) => {
|
||||
const index = inputFieldsRef.current.findIndex(field => field.variable === id)
|
||||
editingFieldIndex.current = index
|
||||
setEditingField(inputFieldsRef.current[index])
|
||||
setShowInputFieldEditor(true)
|
||||
}, [])
|
||||
|
||||
const handleCloseInputFieldEditor = useCallback(() => {
|
||||
setShowInputFieldEditor(false)
|
||||
setInputFieldEditPanelProps?.(null)
|
||||
editingFieldIndex.current = -1
|
||||
setEditingField(undefined)
|
||||
}, [])
|
||||
}, [setInputFieldEditPanelProps])
|
||||
|
||||
const handleRemoveField = useCallback((index: number) => {
|
||||
const itemToRemove = inputFieldsRef.current[index]
|
||||
|
|
@ -92,7 +86,7 @@ export const useFieldList = ({
|
|||
|
||||
const handleSubmitField = useCallback((data: InputVar, moreInfo?: MoreInfo) => {
|
||||
const isDuplicate = allVariableNames.some(name =>
|
||||
name === data.variable && name !== editingField?.variable)
|
||||
name === data.variable && name !== inputFieldsRef.current[editingFieldIndex.current]?.variable)
|
||||
if (isDuplicate) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
|
|
@ -113,18 +107,23 @@ export const useFieldList = ({
|
|||
if (moreInfo?.type === ChangeType.changeVarName)
|
||||
handleInputVarRename(nodeId, [VARIABLE_PREFIX, nodeId, moreInfo.payload?.beforeKey || ''], [VARIABLE_PREFIX, nodeId, moreInfo.payload?.afterKey || ''])
|
||||
handleCloseInputFieldEditor()
|
||||
}, [allVariableNames, editingField?.variable, handleCloseInputFieldEditor, handleInputFieldsChange, handleInputVarRename, nodeId, t])
|
||||
}, [allVariableNames, handleCloseInputFieldEditor, handleInputFieldsChange, handleInputVarRename, nodeId, t])
|
||||
|
||||
const handleOpenInputFieldEditor = useCallback((id?: string) => {
|
||||
const index = inputFieldsRef.current.findIndex(field => field.variable === id)
|
||||
editingFieldIndex.current = index
|
||||
setInputFieldEditPanelProps?.({
|
||||
onClose: handleCloseInputFieldEditor,
|
||||
onSubmit: handleSubmitField,
|
||||
initialData: inputFieldsRef.current[index],
|
||||
})
|
||||
}, [])
|
||||
|
||||
return {
|
||||
inputFields,
|
||||
handleInputFieldsChange,
|
||||
handleListSortChange,
|
||||
handleRemoveField,
|
||||
handleSubmitField,
|
||||
editingField,
|
||||
showInputFieldEditor,
|
||||
handleOpenInputFieldEditor,
|
||||
handleCloseInputFieldEditor,
|
||||
isShowRemoveVarConfirm,
|
||||
hideRemoveVarConfirm,
|
||||
onRemoveVarConfirm,
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useCallback } from 'react'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import InputFieldEditor from '../editor'
|
||||
import type { InputVar } from '@/models/pipeline'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { useFieldList } from './hooks'
|
||||
|
|
@ -33,13 +32,9 @@ const FieldList = ({
|
|||
|
||||
const {
|
||||
inputFields,
|
||||
handleSubmitField,
|
||||
handleListSortChange,
|
||||
handleRemoveField,
|
||||
handleCloseInputFieldEditor,
|
||||
handleOpenInputFieldEditor,
|
||||
showInputFieldEditor,
|
||||
editingField,
|
||||
isShowRemoveVarConfirm,
|
||||
hideRemoveVarConfirm,
|
||||
onRemoveVarConfirm,
|
||||
|
|
@ -59,6 +54,7 @@ const FieldList = ({
|
|||
<ActionButton
|
||||
onClick={() => handleOpenInputFieldEditor()}
|
||||
disabled={readonly}
|
||||
className={cn(readonly && 'cursor-not-allowed')}
|
||||
>
|
||||
<RiAddLine className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
|
|
@ -71,14 +67,6 @@ const FieldList = ({
|
|||
onListSortChange={handleListSortChange}
|
||||
readonly={readonly}
|
||||
/>
|
||||
{showInputFieldEditor && (
|
||||
<InputFieldEditor
|
||||
show={showInputFieldEditor}
|
||||
initialData={editingField}
|
||||
onSubmit={handleSubmitField}
|
||||
onClose={handleCloseInputFieldEditor}
|
||||
/>
|
||||
)}
|
||||
<RemoveEffectVarConfirm
|
||||
isShow={isShowRemoveVarConfirm}
|
||||
onCancel={hideRemoveVarConfirm}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { RiCloseLine, RiEyeLine } from '@remixicon/react'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import FieldList from './field-list'
|
||||
import FooterTip from './footer-tip'
|
||||
import GlobalInputs from './label-right-content/global-inputs'
|
||||
import Datasource from './label-right-content/datasource'
|
||||
import { useNodes } from 'reactflow'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks'
|
||||
import type { InputVar, RAGPipelineVariables } from '@/models/pipeline'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const InputFieldPanel = () => {
|
||||
const { t } = useTranslation()
|
||||
const nodes = useNodes<DataSourceNodeType>()
|
||||
const setShowInputFieldPanel = useStore(state => state.setShowInputFieldPanel)
|
||||
const ragPipelineVariables = useStore(state => state.ragPipelineVariables)
|
||||
const setRagPipelineVariables = useStore(state => state.setRagPipelineVariables)
|
||||
const showInputFieldPreviewPanel = useStore(state => state.showInputFieldPreviewPanel)
|
||||
const setShowInputFieldPreviewPanel = useStore(state => state.setShowInputFieldPreviewPanel)
|
||||
const inputFieldEditPanelProps = useStore(state => state.inputFieldEditPanelProps)
|
||||
|
||||
const getInputFieldsMap = () => {
|
||||
const inputFieldsMap: Record<string, InputVar[]> = {}
|
||||
ragPipelineVariables?.forEach((variable) => {
|
||||
const { belong_to_node_id: nodeId, ...varConfig } = variable
|
||||
if (inputFieldsMap[nodeId])
|
||||
inputFieldsMap[nodeId].push(varConfig)
|
||||
else
|
||||
inputFieldsMap[nodeId] = [varConfig]
|
||||
})
|
||||
return inputFieldsMap
|
||||
}
|
||||
const inputFieldsMap = useRef(getInputFieldsMap())
|
||||
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
|
||||
const datasourceNodeDataMap = useMemo(() => {
|
||||
const datasourceNodeDataMap: Record<string, DataSourceNodeType> = {}
|
||||
const datasourceNodes: Node<DataSourceNodeType>[] = nodes.filter(node => node.data.type === BlockEnum.DataSource)
|
||||
datasourceNodes.forEach((node) => {
|
||||
const { id, data } = node
|
||||
datasourceNodeDataMap[id] = data
|
||||
})
|
||||
return datasourceNodeDataMap
|
||||
}, [nodes])
|
||||
|
||||
const updateInputFields = useCallback(async (key: string, value: InputVar[]) => {
|
||||
inputFieldsMap.current[key] = value
|
||||
const datasourceNodeInputFields: RAGPipelineVariables = []
|
||||
const globalInputFields: RAGPipelineVariables = []
|
||||
Object.keys(inputFieldsMap.current).forEach((key) => {
|
||||
const inputFields = inputFieldsMap.current[key]
|
||||
inputFields.forEach((inputField) => {
|
||||
if (key === 'shared') {
|
||||
globalInputFields.push({
|
||||
...inputField,
|
||||
belong_to_node_id: key,
|
||||
})
|
||||
}
|
||||
else {
|
||||
datasourceNodeInputFields.push({
|
||||
...inputField,
|
||||
belong_to_node_id: key,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
// Datasource node input fields come first, then global input fields
|
||||
const newRagPipelineVariables = [...datasourceNodeInputFields, ...globalInputFields]
|
||||
setRagPipelineVariables?.(newRagPipelineVariables)
|
||||
handleSyncWorkflowDraft()
|
||||
}, [setRagPipelineVariables, handleSyncWorkflowDraft])
|
||||
|
||||
const closePanel = useCallback(() => {
|
||||
setShowInputFieldPanel?.(false)
|
||||
}, [setShowInputFieldPanel])
|
||||
|
||||
const togglePreviewPanel = useCallback(() => {
|
||||
setShowInputFieldPreviewPanel?.(true)
|
||||
}, [setShowInputFieldPreviewPanel])
|
||||
|
||||
const allVariableNames = useMemo(() => {
|
||||
return ragPipelineVariables?.map(variable => variable.variable) || []
|
||||
}, [ragPipelineVariables])
|
||||
|
||||
return (
|
||||
<div className='mr-1 flex h-full w-[400px] flex-col rounded-2xl border-y-[0.5px] border-l-[0.5px] border-components-panel-border bg-components-panel-bg-alt shadow-xl shadow-shadow-shadow-5'>
|
||||
<div className='flex shrink-0 items-center p-4 pb-0'>
|
||||
<div className='system-xl-semibold grow'>
|
||||
{t('datasetPipeline.inputFieldPanel.title')}
|
||||
</div>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
size='small'
|
||||
className={cn(
|
||||
'shrink-0 gap-x-px px-1.5',
|
||||
showInputFieldPreviewPanel && 'bg-state-accent-active text-text-accent',
|
||||
)}
|
||||
onClick={togglePreviewPanel}
|
||||
>
|
||||
<RiEyeLine className='size-3.5' />
|
||||
<span className='px-[3px]'>{t('datasetPipeline.operations.preview')}</span>
|
||||
</Button>
|
||||
<Divider type='vertical' className='mx-1 h-3' />
|
||||
<button
|
||||
type='button'
|
||||
className='flex size-6 shrink-0 items-center justify-center p-0.5'
|
||||
onClick={closePanel}
|
||||
>
|
||||
<RiCloseLine className='size-4 text-text-tertiary' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='system-sm-regular shrink-0 px-4 pb-2 pt-1 text-text-tertiary'>
|
||||
{t('datasetPipeline.inputFieldPanel.description')}
|
||||
</div>
|
||||
<div className='flex grow flex-col overflow-y-auto'>
|
||||
{/* Unique Inputs for Each Entrance */}
|
||||
<div className='flex h-6 items-center gap-x-0.5 px-4 pt-2'>
|
||||
<span className='system-sm-semibold-uppercase text-text-secondary'>
|
||||
{t('datasetPipeline.inputFieldPanel.uniqueInputs.title')}
|
||||
</span>
|
||||
<Tooltip
|
||||
popupContent={t('datasetPipeline.inputFieldPanel.uniqueInputs.tooltip')}
|
||||
popupClassName='max-w-[240px]'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-y-1 py-1'>
|
||||
{
|
||||
Object.keys(datasourceNodeDataMap).map((key) => {
|
||||
const inputFields = inputFieldsMap.current[key] || []
|
||||
return (
|
||||
<FieldList
|
||||
key={key}
|
||||
nodeId={key}
|
||||
LabelRightContent={<Datasource nodeData={datasourceNodeDataMap[key]} />}
|
||||
inputFields={inputFields}
|
||||
readonly={showInputFieldPreviewPanel || !!inputFieldEditPanelProps}
|
||||
labelClassName='pt-1 pb-1'
|
||||
handleInputFieldsChange={updateInputFields}
|
||||
allVariableNames={allVariableNames}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
{/* Global Inputs */}
|
||||
<FieldList
|
||||
nodeId='shared'
|
||||
LabelRightContent={<GlobalInputs />}
|
||||
inputFields={inputFieldsMap.current.shared || []}
|
||||
readonly={showInputFieldPreviewPanel || !!inputFieldEditPanelProps}
|
||||
labelClassName='pt-2 pb-1'
|
||||
handleInputFieldsChange={updateInputFields}
|
||||
allVariableNames={allVariableNames}
|
||||
/>
|
||||
</div>
|
||||
<FooterTip />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(InputFieldPanel)
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import DataSourceOptions from '../../panel/test-run/data-source-options'
|
||||
import DataSourceOptions from '../../test-run/data-source-options'
|
||||
import Form from './form'
|
||||
import type { Datasource } from '../../panel/test-run/types'
|
||||
import type { Datasource } from '../../test-run/types'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { useDraftPipelinePreProcessingParams } from '@/service/use-pipeline'
|
||||
|
||||
|
|
@ -1,33 +1,24 @@
|
|||
import { useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import DialogWrapper from '../dialog-wrapper'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import DataSource from './data-source'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import ProcessDocuments from './process-documents'
|
||||
import type { Datasource } from '../../panel/test-run/types'
|
||||
import type { Datasource } from '../../test-run/types'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
|
||||
type PreviewPanelProps = {
|
||||
show: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const PreviewPanel = ({
|
||||
show,
|
||||
onClose,
|
||||
}: PreviewPanelProps) => {
|
||||
const PreviewPanel = () => {
|
||||
const { t } = useTranslation()
|
||||
const [datasource, setDatasource] = useState<Datasource>()
|
||||
const setShowInputFieldPreviewPanel = useStore(state => state.setShowInputFieldPreviewPanel)
|
||||
|
||||
const handleClosePreviewPanel = useCallback(() => {
|
||||
setShowInputFieldPreviewPanel(false)
|
||||
}, [setShowInputFieldPreviewPanel])
|
||||
|
||||
return (
|
||||
<DialogWrapper
|
||||
show={show}
|
||||
onClose={onClose}
|
||||
outerWrapperClassName='overflow-y-auto'
|
||||
panelWrapperClassName='pr-[424px] justify-start'
|
||||
className='w-[480px] grow rounded-2xl border-[0.5px] bg-components-panel-bg'
|
||||
>
|
||||
<div className='mr-1 flex h-full w-[480px] flex-col overflow-y-auto rounded-2xl border-y-[0.5px] border-l-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl shadow-shadow-shadow-5'>
|
||||
<div className='flex items-center gap-x-2 px-4 pt-1'>
|
||||
<div className='grow py-1'>
|
||||
<Badge className='border-text-accent-secondary bg-components-badge-bg-dimm text-text-accent-secondary'>
|
||||
|
|
@ -37,7 +28,7 @@ const PreviewPanel = ({
|
|||
<button
|
||||
type='button'
|
||||
className='flex size-6 shrink-0 items-center justify-center'
|
||||
onClick={onClose}
|
||||
onClick={handleClosePreviewPanel}
|
||||
>
|
||||
<RiCloseLine className='size-4 text-text-tertiary' />
|
||||
</button>
|
||||
|
|
@ -52,7 +43,7 @@ const PreviewPanel = ({
|
|||
</div>
|
||||
{/* Process documents form Preview */}
|
||||
<ProcessDocuments dataSourceNodeId={datasource?.nodeId || ''} />
|
||||
</DialogWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -3,7 +3,6 @@ import {
|
|||
useState,
|
||||
} from 'react'
|
||||
import { useStore } from '../../workflow/store'
|
||||
import InputField from './input-field'
|
||||
import PluginDependency from '../../workflow/plugin-dependency'
|
||||
import RagPipelinePanel from './panel'
|
||||
import RagPipelineHeader from './rag-pipeline-header'
|
||||
|
|
@ -21,7 +20,6 @@ import PublishToast from './publish-toast'
|
|||
const RagPipelineChildren = () => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [secretEnvList, setSecretEnvList] = useState<EnvironmentVariable[]>([])
|
||||
const showInputFieldDialog = useStore(state => state.showInputFieldDialog)
|
||||
const showImportDSLModal = useStore(s => s.showImportDSLModal)
|
||||
const setShowImportDSLModal = useStore(s => s.setShowImportDSLModal)
|
||||
const {
|
||||
|
|
@ -60,9 +58,6 @@ const RagPipelineChildren = () => {
|
|||
}
|
||||
<RagPipelineHeader />
|
||||
<RagPipelinePanel />
|
||||
{
|
||||
showInputFieldDialog && (<InputField />)
|
||||
}
|
||||
<PublishToast />
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import { useTranslation } from 'react-i18next'
|
|||
|
||||
const InputFieldButton = () => {
|
||||
const { t } = useTranslation()
|
||||
const setShowInputFieldDialog = useStore(state => state.setShowInputFieldDialog)
|
||||
const setShowInputFieldPanel = useStore(state => state.setShowInputFieldPanel)
|
||||
const handleClick = useCallback(() => {
|
||||
setShowInputFieldDialog?.(true)
|
||||
}, [setShowInputFieldDialog])
|
||||
setShowInputFieldPanel?.(true)
|
||||
}, [setShowInputFieldPanel])
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -6,13 +6,18 @@ import type {
|
|||
import type { DataSourceItem } from '@/app/components/workflow/block-selector/types'
|
||||
import { transformDataSourceToTool } from '@/app/components/workflow/block-selector/utils'
|
||||
import type { IconInfo } from '@/models/datasets'
|
||||
import type { InputFieldEditorProps } from '../components/panel/input-field/editor'
|
||||
|
||||
export type RagPipelineSliceShape = {
|
||||
pipelineId: string
|
||||
knowledgeName: string
|
||||
knowledgeIcon?: IconInfo
|
||||
showInputFieldDialog: boolean
|
||||
setShowInputFieldDialog: (showInputFieldPanel: boolean) => void
|
||||
showInputFieldPanel: boolean
|
||||
setShowInputFieldPanel: (showInputFieldPanel: boolean) => void
|
||||
showInputFieldPreviewPanel: boolean
|
||||
setShowInputFieldPreviewPanel: (showInputFieldPreviewPanel: boolean) => void
|
||||
inputFieldEditPanelProps: InputFieldEditorProps | null
|
||||
setInputFieldEditPanelProps: (showInputFieldEditPanel: InputFieldEditorProps | null) => void
|
||||
nodesDefaultConfigs: Record<string, any>
|
||||
setNodesDefaultConfigs: (nodesDefaultConfigs: Record<string, any>) => void
|
||||
ragPipelineVariables: RAGPipelineVariables
|
||||
|
|
@ -25,8 +30,12 @@ export type CreateRagPipelineSliceSlice = StateCreator<RagPipelineSliceShape>
|
|||
export const createRagPipelineSliceSlice: StateCreator<RagPipelineSliceShape> = set => ({
|
||||
pipelineId: '',
|
||||
knowledgeName: '',
|
||||
showInputFieldDialog: false,
|
||||
setShowInputFieldDialog: showInputFieldDialog => set(() => ({ showInputFieldDialog })),
|
||||
showInputFieldPanel: false,
|
||||
setShowInputFieldPanel: showInputFieldPanel => set(() => ({ showInputFieldPanel })),
|
||||
showInputFieldPreviewPanel: false,
|
||||
setShowInputFieldPreviewPanel: showInputFieldPreviewPanel => set(() => ({ showInputFieldPreviewPanel })),
|
||||
inputFieldEditPanelProps: null,
|
||||
setInputFieldEditPanelProps: inputFieldEditPanelProps => set(() => ({ inputFieldEditPanelProps })),
|
||||
nodesDefaultConfigs: {},
|
||||
setNodesDefaultConfigs: nodesDefaultConfigs => set(() => ({ nodesDefaultConfigs })),
|
||||
ragPipelineVariables: [],
|
||||
|
|
|
|||
|
|
@ -54,11 +54,10 @@ const Editor: FC<Props> = ({
|
|||
|
||||
useEffect(() => {
|
||||
onFocusChange?.(isFocus)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isFocus])
|
||||
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
|
||||
return (
|
||||
<div className={cn(className, 'relative')}>
|
||||
|
|
@ -108,7 +107,7 @@ const Editor: FC<Props> = ({
|
|||
return acc
|
||||
}, {} as any),
|
||||
showManageInputField: !!pipelineId,
|
||||
onManageInputField: () => setShowInputFieldDialog?.(true),
|
||||
onManageInputField: () => setShowInputFieldPanel?.(true),
|
||||
}}
|
||||
onChange={onChange}
|
||||
editable={!readOnly}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ const Editor: FC<Props> = ({
|
|||
|
||||
const getVarType = useWorkflowVariableType()
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
|
||||
return (
|
||||
<Wrap className={cn(className, wrapClassName)} style={wrapStyle} isInNode isExpand={isExpand}>
|
||||
|
|
@ -273,7 +273,7 @@ const Editor: FC<Props> = ({
|
|||
return acc
|
||||
}, {} as any),
|
||||
showManageInputField: !!pipelineId,
|
||||
onManageInputField: () => setShowInputFieldDialog?.(true),
|
||||
onManageInputField: () => setShowInputFieldPanel?.(true),
|
||||
}}
|
||||
onChange={onChange}
|
||||
onBlur={setBlur}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const VarReferencePopup: FC<Props> = ({
|
|||
const { t } = useTranslation()
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const showManageRagInputFields = useMemo(() => !!pipelineId, [pipelineId])
|
||||
const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
const docLink = useDocLink()
|
||||
// max-h-[300px] overflow-y-auto todo: use portal to handle long list
|
||||
return (
|
||||
|
|
@ -68,7 +68,7 @@ const VarReferencePopup: FC<Props> = ({
|
|||
isSupportFileVar={isSupportFileVar}
|
||||
zIndex={zIndex}
|
||||
showManageInputField={showManageRagInputFields}
|
||||
onManageInputField={() => setShowInputFieldDialog?.(true)}
|
||||
onManageInputField={() => setShowInputFieldPanel?.(true)}
|
||||
/>
|
||||
}
|
||||
</div >
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const ConditionInput = ({
|
|||
const { t } = useTranslation()
|
||||
const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey)
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
|
||||
return (
|
||||
<PromptEditor
|
||||
|
|
@ -52,7 +52,7 @@ const ConditionInput = ({
|
|||
return acc
|
||||
}, {} as any),
|
||||
showManageInputField: !!pipelineId,
|
||||
onManageInputField: () => setShowInputFieldDialog?.(true),
|
||||
onManageInputField: () => setShowInputFieldPanel?.(true),
|
||||
}}
|
||||
onChange={onChange}
|
||||
editable={!disabled}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const ConditionInput = ({
|
|||
const { t } = useTranslation()
|
||||
const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey)
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
|
||||
return (
|
||||
<PromptEditor
|
||||
|
|
@ -46,7 +46,7 @@ const ConditionInput = ({
|
|||
return acc
|
||||
}, {} as any),
|
||||
showManageInputField: !!pipelineId,
|
||||
onManageInputField: () => setShowInputFieldDialog?.(true),
|
||||
onManageInputField: () => setShowInputFieldPanel?.(true),
|
||||
}}
|
||||
onChange={onChange}
|
||||
editable={!disabled}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||
|
||||
const [collapsed, setCollapsed] = React.useState(false)
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
|
||||
if (isLoading) {
|
||||
return <div className='flex h-[200px] items-center justify-center'>
|
||||
|
|
@ -65,7 +65,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
|||
currentProvider={currCollection}
|
||||
currentTool={currTool}
|
||||
showManageInputField={!!pipelineId}
|
||||
onManageInputField={() => setShowInputFieldDialog?.(true)}
|
||||
onManageInputField={() => setShowInputFieldPanel?.(true)}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue