mirror of
				https://github.com/langgenius/dify.git
				synced 2025-10-31 02:42:59 +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
	 twwu
						twwu