diff --git a/web/app/components/rag-pipeline/components/input-field/field-list/field-item.tsx b/web/app/components/rag-pipeline/components/input-field/field-list/field-item.tsx
index 8faa5abdd5..080aca33e8 100644
--- a/web/app/components/rag-pipeline/components/input-field/field-list/field-item.tsx
+++ b/web/app/components/rag-pipeline/components/input-field/field-list/field-item.tsx
@@ -1,5 +1,5 @@
'use client'
-import React, { useRef } from 'react'
+import React, { useCallback, useRef } from 'react'
import { useHover } from 'ahooks'
import { useTranslation } from 'react-i18next'
import {
@@ -13,12 +13,13 @@ import cn from '@/utils/classnames'
import Badge from '@/app/components/base/badge'
import type { InputVar } from '@/models/pipeline'
import type { InputVarType } from '@/app/components/workflow/types'
+import ActionButton from '@/app/components/base/action-button'
type FieldItemProps = {
readonly?: boolean
payload: InputVar
- onClickEdit: () => void
- onRemove: () => void
+ onClickEdit: (id: string) => void
+ onRemove: (id: string) => void
}
const FieldItem = ({
@@ -32,6 +33,16 @@ const FieldItem = ({
const ref = useRef(null)
const isHovering = useHover(ref)
+ const handleOnClickEdit = useCallback(() => {
+ if (readonly) return
+ onClickEdit(payload.variable)
+ }, [onClickEdit, payload.variable, readonly])
+
+ const handleRemove = useCallback(() => {
+ if (readonly) return
+ onRemove(payload.variable)
+ }, [onRemove, payload.variable, readonly])
+
return (
)
: (
diff --git a/web/app/components/rag-pipeline/components/input-field/field-list/field-list-container.tsx b/web/app/components/rag-pipeline/components/input-field/field-list/field-list-container.tsx
new file mode 100644
index 0000000000..c1a082ea98
--- /dev/null
+++ b/web/app/components/rag-pipeline/components/input-field/field-list/field-list-container.tsx
@@ -0,0 +1,59 @@
+import {
+ memo,
+ useMemo,
+} from 'react'
+import { ReactSortable } from 'react-sortablejs'
+import cn from '@/utils/classnames'
+import type { InputVar } from '@/models/pipeline'
+import FieldItem from './field-item'
+import type { SortableItem } from './types'
+
+type FieldListContainerProps = {
+ className?: string
+ inputFields: InputVar[]
+ onListSortChange: (list: SortableItem[]) => void
+ onRemoveField: (id: string) => void
+ onEditField: (id: string) => void
+ readonly?: boolean
+}
+const FieldListContainer = ({
+ className,
+ inputFields,
+ onListSortChange,
+ onRemoveField,
+ onEditField,
+ readonly,
+}: FieldListContainerProps) => {
+ const list = useMemo(() => {
+ return inputFields.map((content) => {
+ return ({
+ id: content.variable,
+ name: content.variable,
+ })
+ })
+ }, [inputFields])
+
+ return (
+
+ className={cn(className)}
+ list={list}
+ setList={onListSortChange}
+ handle='.handle'
+ ghostClass='opacity-50'
+ animation={150}
+ disabled={readonly}
+ >
+ {inputFields?.map((item, index) => (
+
+ ))}
+
+ )
+}
+
+export default memo(FieldListContainer)
diff --git a/web/app/components/rag-pipeline/components/input-field/field-list/hooks.ts b/web/app/components/rag-pipeline/components/input-field/field-list/hooks.ts
new file mode 100644
index 0000000000..3a2efdb80b
--- /dev/null
+++ b/web/app/components/rag-pipeline/components/input-field/field-list/hooks.ts
@@ -0,0 +1,69 @@
+import {
+ useCallback,
+ useRef,
+ useState,
+} from 'react'
+import { produce } from 'immer'
+import type { InputVar } from '@/models/pipeline'
+import type { SortableItem } from './types'
+
+export const useFieldList = (
+ initialInputFields: InputVar[],
+ onInputFieldsChange: (value: InputVar[]) => void,
+) => {
+ const [inputFields, setInputFields] = useState(initialInputFields)
+ const inputFieldsRef = useRef(inputFields)
+ const handleInputFieldsChange = useCallback((newInputFields: InputVar[]) => {
+ setInputFields(newInputFields)
+ inputFieldsRef.current = newInputFields
+ onInputFieldsChange(newInputFields)
+ }, [onInputFieldsChange])
+
+ const handleListSortChange = useCallback((list: SortableItem[]) => {
+ const newInputFields = list.map((item) => {
+ return inputFieldsRef.current.find(field => field.variable === item.name)
+ })
+ handleInputFieldsChange(newInputFields as InputVar[])
+ }, [handleInputFieldsChange])
+
+ const [editingField, setEditingField] = useState()
+ const [showInputFieldEditor, setShowInputFieldEditor] = useState(false)
+ const handleOpenInputFieldEditor = useCallback((id?: string) => {
+ const fieldToEdit = inputFieldsRef.current.find(field => field.variable === id)
+ setEditingField(fieldToEdit)
+ setShowInputFieldEditor(true)
+ }, [])
+ const handleCancelInputFieldEditor = useCallback(() => {
+ setShowInputFieldEditor(false)
+ setEditingField(undefined)
+ }, [])
+
+ const handleRemoveField = useCallback((id: string) => {
+ const newInputFields = inputFieldsRef.current.filter(field => field.variable !== id)
+ handleInputFieldsChange(newInputFields)
+ }, [handleInputFieldsChange])
+
+ const handleSubmitField = useCallback((data: InputVar) => {
+ const newInputFields = produce(inputFieldsRef.current, (draft) => {
+ const currentIndex = draft.findIndex(field => field.variable === data.variable)
+ if (currentIndex === -1) {
+ draft.push(data)
+ return
+ }
+ draft[currentIndex] = data
+ })
+ handleInputFieldsChange(newInputFields)
+ }, [handleInputFieldsChange])
+
+ return {
+ inputFields,
+ handleInputFieldsChange,
+ handleListSortChange,
+ handleRemoveField,
+ handleSubmitField,
+ editingField,
+ showInputFieldEditor,
+ handleOpenInputFieldEditor,
+ handleCancelInputFieldEditor,
+ }
+}
diff --git a/web/app/components/rag-pipeline/components/input-field/field-list/index.tsx b/web/app/components/rag-pipeline/components/input-field/field-list/index.tsx
index a2cd88c571..dd567818c8 100644
--- a/web/app/components/rag-pipeline/components/input-field/field-list/index.tsx
+++ b/web/app/components/rag-pipeline/components/input-field/field-list/index.tsx
@@ -1,11 +1,10 @@
import { RiAddLine } from '@remixicon/react'
-import FieldItem from './field-item'
import cn from '@/utils/classnames'
-import { useCallback, useMemo, useState } from 'react'
import InputFieldEditor from '../editor'
-import { ReactSortable } from 'react-sortablejs'
-import produce from 'immer'
import type { InputVar } from '@/models/pipeline'
+import ActionButton from '@/app/components/base/action-button'
+import { useFieldList } from './hooks'
+import FieldListContainer from './field-list-container'
type FieldListProps = {
LabelRightContent: React.ReactNode
@@ -17,62 +16,21 @@ type FieldListProps = {
const FieldList = ({
LabelRightContent,
- inputFields,
- handleInputFieldsChange,
+ inputFields: initialInputFields,
+ handleInputFieldsChange: onInputFieldsChange,
readonly,
labelClassName,
}: FieldListProps) => {
- const [showInputFieldEditor, setShowInputFieldEditor] = useState(false)
- const [currentIndex, setCurrentIndex] = useState(-1)
- const [currentInputField, setCurrentInputField] = useState()
-
- 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 = () => {
- setCurrentIndex(-1) // -1 means add new field
- setCurrentInputField(undefined)
- setShowInputFieldEditor(true)
- }
-
- const handleEditField = useCallback((index: number) => {
- setCurrentIndex(index)
- setCurrentInputField(inputFields[index])
- setShowInputFieldEditor(true)
- }, [inputFields])
-
- const handleSubmitChange = useCallback((data: InputVar) => {
- const newInputFields = produce(inputFields, (draft) => {
- if (currentIndex === -1) {
- draft.push(data)
- return
- }
- draft[currentIndex] = data
- })
- handleInputFieldsChange(newInputFields)
- }, [currentIndex, handleInputFieldsChange, inputFields])
-
- const handleCloseEditor = useCallback(() => {
- setShowInputFieldEditor(false)
- }, [])
+ const {
+ inputFields,
+ handleSubmitField,
+ handleListSortChange,
+ handleRemoveField,
+ handleCancelInputFieldEditor,
+ handleOpenInputFieldEditor,
+ showInputFieldEditor,
+ editingField,
+ } = useFieldList(initialInputFields, onInputFieldsChange)
return (
@@ -80,41 +38,27 @@ const FieldList = ({
{LabelRightContent}
-
+
- handleListSortChange(list)}
- handle='.handle'
- ghostClass='opacity-50'
- animation={150}
- disabled={readonly}
- >
- {inputFields?.map((item, index) => (
-
- ))}
-
+ inputFields={inputFields}
+ onEditField={handleOpenInputFieldEditor}
+ onRemoveField={handleRemoveField}
+ onListSortChange={handleListSortChange}
+ readonly={readonly}
+ />
{showInputFieldEditor && (
)}
diff --git a/web/app/components/rag-pipeline/components/input-field/field-list/types.ts b/web/app/components/rag-pipeline/components/input-field/field-list/types.ts
new file mode 100644
index 0000000000..44e4c20096
--- /dev/null
+++ b/web/app/components/rag-pipeline/components/input-field/field-list/types.ts
@@ -0,0 +1,4 @@
+export type SortableItem = {
+ id: string
+ name: string
+}
diff --git a/web/app/components/workflow/nodes/data-source/default.ts b/web/app/components/workflow/nodes/data-source/default.ts
index 6be0666375..e27ccc155b 100644
--- a/web/app/components/workflow/nodes/data-source/default.ts
+++ b/web/app/components/workflow/nodes/data-source/default.ts
@@ -11,6 +11,7 @@ const metaData = genNodeMetaData({
const nodeDefault: NodeDefault = {
metaData,
defaultValue: {
+ variables: [],
datasource_parameters: {},
datasource_configurations: {},
},
diff --git a/web/app/components/workflow/nodes/data-source/hooks/use-config.ts b/web/app/components/workflow/nodes/data-source/hooks/use-config.ts
index 66d27dda8c..cec456c8db 100644
--- a/web/app/components/workflow/nodes/data-source/hooks/use-config.ts
+++ b/web/app/components/workflow/nodes/data-source/hooks/use-config.ts
@@ -1,6 +1,7 @@
import { useCallback } from 'react'
import { useStoreApi } from 'reactflow'
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
+import type { InputVar } from '@/models/pipeline'
import type { DataSourceNodeType } from '../types'
export const useConfig = (id: string) => {
@@ -28,7 +29,16 @@ export const useConfig = (id: string) => {
})
}, [handleNodeDataUpdate, getNodeData])
+ const handleInputFieldVariablesChange = useCallback((variables: InputVar[]) => {
+ const nodeData = getNodeData()
+ handleNodeDataUpdate({
+ ...nodeData?.data,
+ variables,
+ })
+ }, [handleNodeDataUpdate, getNodeData])
+
return {
handleFileExtensionsChange,
+ handleInputFieldVariablesChange,
}
}
diff --git a/web/app/components/workflow/nodes/data-source/panel.tsx b/web/app/components/workflow/nodes/data-source/panel.tsx
index 258f181e4f..c5f738756b 100644
--- a/web/app/components/workflow/nodes/data-source/panel.tsx
+++ b/web/app/components/workflow/nodes/data-source/panel.tsx
@@ -1,25 +1,79 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { memo } from 'react'
+import { RiAddLine } from '@remixicon/react'
import type { DataSourceNodeType } from './types'
import type { NodePanelProps } from '@/app/components/workflow/types'
import { BoxGroupField } from '@/app/components/workflow/nodes/_base/components/layout'
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
import TagInput from '@/app/components/base/tag-input'
+import FieldList from '@/app/components/rag-pipeline/components/input-field/field-list/field-list-container'
+import { useFieldList } from '@/app/components/rag-pipeline/components/input-field/field-list/hooks'
+import InputFieldEditor from '@/app/components/rag-pipeline/components/input-field/editor'
+import { useNodesReadOnly } from '@/app/components/workflow/hooks'
import { useConfig } from './hooks/use-config'
import { OUTPUT_VARIABLES_MAP } from './constants'
+import ActionButton from '@/app/components/base/action-button'
const Panel: FC> = ({ id, data }) => {
const { t } = useTranslation()
+ const { nodesReadOnly } = useNodesReadOnly()
const {
+ variables,
provider_type,
fileExtensions = [],
} = data
- const { handleFileExtensionsChange } = useConfig(id)
+ const {
+ handleInputFieldVariablesChange,
+ handleFileExtensionsChange,
+ } = useConfig(id)
const isLocalFile = provider_type === 'local_file'
+ const {
+ inputFields,
+ handleListSortChange,
+ handleRemoveField,
+ handleOpenInputFieldEditor,
+ showInputFieldEditor,
+ editingField,
+ handleSubmitField,
+ handleCancelInputFieldEditor,
+ } = useFieldList(variables, handleInputFieldVariablesChange)
return (
+ {
+ !isLocalFile && (
+ {
+ e.stopPropagation()
+ handleOpenInputFieldEditor()
+ }}
+ >
+
+
+ ),
+ },
+ supportCollapse: true,
+ }}
+ >
+
+
+ )
+ }
{
isLocalFile && (
> = ({ id, data }) => {
)
}
+ {showInputFieldEditor && (
+
+ )}
)
}