mirror of
https://github.com/langgenius/dify.git
synced 2025-10-30 18:33:30 +00:00
feat: enhance FieldList component with sorting and dynamic input field management
This commit is contained in:
parent
5b8c43052e
commit
efb27eb443
@ -2,12 +2,14 @@ import type { InputVar } from '@/app/components/workflow/types'
|
|||||||
import { RiAddLine } from '@remixicon/react'
|
import { RiAddLine } from '@remixicon/react'
|
||||||
import FieldItem from './field-item'
|
import FieldItem from './field-item'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import { useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import InputFieldEditor from '../editor'
|
import InputFieldEditor from '../editor'
|
||||||
|
import { ReactSortable } from 'react-sortablejs'
|
||||||
|
|
||||||
type FieldListProps = {
|
type FieldListProps = {
|
||||||
LabelRightContent: React.ReactNode
|
LabelRightContent: React.ReactNode
|
||||||
inputFields?: InputVar[]
|
inputFields: InputVar[]
|
||||||
|
handleInputFieldsChange: (value: InputVar[]) => void
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
labelClassName?: string
|
labelClassName?: string
|
||||||
}
|
}
|
||||||
@ -15,22 +17,44 @@ type FieldListProps = {
|
|||||||
const FieldList = ({
|
const FieldList = ({
|
||||||
LabelRightContent,
|
LabelRightContent,
|
||||||
inputFields,
|
inputFields,
|
||||||
|
handleInputFieldsChange,
|
||||||
readonly,
|
readonly,
|
||||||
labelClassName,
|
labelClassName,
|
||||||
}: FieldListProps) => {
|
}: FieldListProps) => {
|
||||||
const [showInputFieldEditor, setShowInputFieldEditor] = useState(false)
|
const [showInputFieldEditor, setShowInputFieldEditor] = useState(false)
|
||||||
|
|
||||||
|
const optionList = useMemo(() => {
|
||||||
|
return inputFields.map((content, index) => {
|
||||||
|
return ({
|
||||||
|
id: index,
|
||||||
|
name: content.variable,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, [inputFields])
|
||||||
|
|
||||||
|
const handleListSortChange = useCallback((list: Array<{ id: number, name: string }>) => {
|
||||||
|
const newInputFields = list.map((item) => {
|
||||||
|
return inputFields.find(field => field.variable === item.name)
|
||||||
|
})
|
||||||
|
handleInputFieldsChange(newInputFields as InputVar[])
|
||||||
|
}, [handleInputFieldsChange, inputFields])
|
||||||
|
|
||||||
|
const handleRemoveField = useCallback((index: number) => {
|
||||||
|
const newInputFields = inputFields.filter((_, i) => i !== index)
|
||||||
|
handleInputFieldsChange(newInputFields)
|
||||||
|
}, [handleInputFieldsChange, inputFields])
|
||||||
|
|
||||||
const handleAddField = () => {
|
const handleAddField = () => {
|
||||||
setShowInputFieldEditor(true)
|
setShowInputFieldEditor(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEditField = (index: number) => {
|
const handleEditField = useCallback((index: number) => {
|
||||||
setShowInputFieldEditor(true)
|
setShowInputFieldEditor(true)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const handleCloseEditor = () => {
|
const handleCloseEditor = useCallback(() => {
|
||||||
setShowInputFieldEditor(false)
|
setShowInputFieldEditor(false)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
@ -48,19 +72,25 @@ const FieldList = ({
|
|||||||
<RiAddLine className='h-4 w-4 text-text-tertiary' />
|
<RiAddLine className='h-4 w-4 text-text-tertiary' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col gap-y-1 px-4 pb-2'>
|
<ReactSortable
|
||||||
|
className='flex flex-col gap-y-1 px-4 pb-2'
|
||||||
|
list={optionList}
|
||||||
|
setList={list => handleListSortChange(list)}
|
||||||
|
handle='.handle'
|
||||||
|
ghostClass="opacity-50"
|
||||||
|
animation={150}
|
||||||
|
disabled={readonly}
|
||||||
|
>
|
||||||
{inputFields?.map((item, index) => (
|
{inputFields?.map((item, index) => (
|
||||||
<FieldItem
|
<FieldItem
|
||||||
key={index}
|
key={index}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
payload={item}
|
payload={item}
|
||||||
onRemove={() => {
|
onRemove={handleRemoveField.bind(null, index)}
|
||||||
// Handle remove action
|
|
||||||
}}
|
|
||||||
onClickEdit={handleEditField.bind(null, index)}
|
onClickEdit={handleEditField.bind(null, index)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</ReactSortable>
|
||||||
{showInputFieldEditor && (
|
{showInputFieldEditor && (
|
||||||
<InputFieldEditor
|
<InputFieldEditor
|
||||||
show={showInputFieldEditor}
|
show={showInputFieldEditor}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useStore } from '@/app/components/workflow/store'
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
import { RiCloseLine } from '@remixicon/react'
|
import { RiCloseLine } from '@remixicon/react'
|
||||||
import { Jina } from '@/app/components/base/icons/src/public/llm'
|
import { Jina } from '@/app/components/base/icons/src/public/llm'
|
||||||
|
import type { InputVar } from '@/app/components/workflow/types'
|
||||||
import { InputVarType } from '@/app/components/workflow/types'
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import DialogWrapper from './dialog-wrapper'
|
import DialogWrapper from './dialog-wrapper'
|
||||||
@ -13,13 +15,51 @@ import FooterTip from './footer-tip'
|
|||||||
|
|
||||||
type InputFieldDialogProps = {
|
type InputFieldDialogProps = {
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
|
initialInputFieldsMap?: Record<string, InputVar[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputFieldDialog = ({
|
const InputFieldDialog = ({
|
||||||
readonly = false,
|
readonly = false,
|
||||||
|
initialInputFieldsMap,
|
||||||
}: InputFieldDialogProps) => {
|
}: InputFieldDialogProps) => {
|
||||||
const showInputFieldDialog = useStore(state => state.showInputFieldDialog)
|
const showInputFieldDialog = useStore(state => state.showInputFieldDialog)
|
||||||
const setShowInputFieldDialog = useStore(state => state.setShowInputFieldDialog)
|
const setShowInputFieldDialog = useStore(state => state.setShowInputFieldDialog)
|
||||||
|
// TODO: delete mock data
|
||||||
|
const [inputFieldsMap, setInputFieldsMap] = useState(initialInputFieldsMap || {
|
||||||
|
jina: [{
|
||||||
|
variable: 'name',
|
||||||
|
label: 'name',
|
||||||
|
type: InputVarType.textInput,
|
||||||
|
required: true,
|
||||||
|
max_length: 12,
|
||||||
|
}, {
|
||||||
|
variable: 'num',
|
||||||
|
label: 'num',
|
||||||
|
type: InputVarType.number,
|
||||||
|
required: true,
|
||||||
|
}],
|
||||||
|
firecrawl: [{
|
||||||
|
variable: 'name',
|
||||||
|
label: 'name',
|
||||||
|
type: InputVarType.textInput,
|
||||||
|
required: true,
|
||||||
|
max_length: 12,
|
||||||
|
}],
|
||||||
|
shared: [{
|
||||||
|
variable: 'name',
|
||||||
|
label: 'name',
|
||||||
|
type: InputVarType.textInput,
|
||||||
|
required: true,
|
||||||
|
max_length: 12,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateInputFields = useCallback((key: string, value: InputVar[]) => {
|
||||||
|
setInputFieldsMap(prev => ({
|
||||||
|
...prev,
|
||||||
|
[key]: value,
|
||||||
|
}))
|
||||||
|
}, [])
|
||||||
|
|
||||||
const closePanel = useCallback(() => {
|
const closePanel = useCallback(() => {
|
||||||
setShowInputFieldDialog?.(false)
|
setShowInputFieldDialog?.(false)
|
||||||
@ -58,20 +98,10 @@ const InputFieldDialog = ({
|
|||||||
<span className='system-sm-medium text-text-secondary'>Jina Reader</span>
|
<span className='system-sm-medium text-text-secondary'>Jina Reader</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
inputFields={[{
|
inputFields={inputFieldsMap.jina}
|
||||||
variable: 'name',
|
|
||||||
label: 'name',
|
|
||||||
type: InputVarType.textInput,
|
|
||||||
required: true,
|
|
||||||
max_length: 12,
|
|
||||||
}, {
|
|
||||||
variable: 'num',
|
|
||||||
label: 'num',
|
|
||||||
type: InputVarType.number,
|
|
||||||
required: true,
|
|
||||||
}]}
|
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
labelClassName='pt-2 pb-1'
|
labelClassName='pt-2 pb-1'
|
||||||
|
handleInputFieldsChange={updateInputFields.bind(null, 'jina')}
|
||||||
/>
|
/>
|
||||||
{/* Firecrawl Field List */}
|
{/* Firecrawl Field List */}
|
||||||
<FieldList
|
<FieldList
|
||||||
@ -83,15 +113,10 @@ const InputFieldDialog = ({
|
|||||||
<span className='system-sm-medium text-text-secondary'>Firecrawl</span>
|
<span className='system-sm-medium text-text-secondary'>Firecrawl</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
inputFields={[{
|
inputFields={inputFieldsMap.firecrawl}
|
||||||
variable: 'name',
|
|
||||||
label: 'name',
|
|
||||||
type: InputVarType.textInput,
|
|
||||||
required: true,
|
|
||||||
max_length: 12,
|
|
||||||
}]}
|
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
labelClassName='pt-2 pb-1'
|
labelClassName='pt-2 pb-1'
|
||||||
|
handleInputFieldsChange={updateInputFields.bind(null, 'firecrawl')}
|
||||||
/>
|
/>
|
||||||
{/* Shared Inputs */}
|
{/* Shared Inputs */}
|
||||||
<FieldList
|
<FieldList
|
||||||
@ -104,15 +129,10 @@ const InputFieldDialog = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
inputFields={[{
|
inputFields={inputFieldsMap.shared}
|
||||||
variable: 'name',
|
|
||||||
label: 'name',
|
|
||||||
type: InputVarType.textInput,
|
|
||||||
required: true,
|
|
||||||
max_length: 12,
|
|
||||||
}]}
|
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
labelClassName='pt-1 pb-2'
|
labelClassName='pt-1 pb-2'
|
||||||
|
handleInputFieldsChange={updateInputFields.bind(null, 'shared')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<FooterTip />
|
<FooterTip />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user