mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 09:28:04 +08:00
feat: add File Upload node functionality and related components
- Implemented File Upload node with support for uploading files to the sandbox. - Added necessary UI components including node panel and default configurations. - Enhanced workflow constants and enums to include File Upload. - Updated error handling for file upload operations. - Integrated File Upload into existing workflow structure, ensuring compatibility with variable handling and output management. - Added translations for new File Upload features in workflow.json.
This commit is contained in:
35
web/app/components/workflow/nodes/file-upload/default.ts
Normal file
35
web/app/components/workflow/nodes/file-upload/default.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import type { NodeDefault } from '../../types'
|
||||
import type { FileUploadNodeType } from './types'
|
||||
import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { genNodeMetaData } from '@/app/components/workflow/utils'
|
||||
|
||||
const i18nPrefix = 'errorMsg'
|
||||
|
||||
const metaData = genNodeMetaData({
|
||||
classification: BlockClassificationEnum.Utilities,
|
||||
sort: 3,
|
||||
type: BlockEnum.FileUpload,
|
||||
})
|
||||
|
||||
const nodeDefault: NodeDefault<FileUploadNodeType> = {
|
||||
metaData,
|
||||
defaultValue: {
|
||||
variable_selector: [],
|
||||
is_array_file: false,
|
||||
},
|
||||
checkValid(payload: FileUploadNodeType, t: (key: string, options?: Record<string, unknown>) => string) {
|
||||
let errorMessages = ''
|
||||
const { variable_selector: variable } = payload
|
||||
|
||||
if (!errorMessages && !variable?.length)
|
||||
errorMessages = t(`${i18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.fields.fileVariable`, { ns: 'workflow' }) })
|
||||
|
||||
return {
|
||||
isValid: !errorMessages,
|
||||
errorMessage: errorMessages,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default nodeDefault
|
||||
12
web/app/components/workflow/nodes/file-upload/node.tsx
Normal file
12
web/app/components/workflow/nodes/file-upload/node.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import type { FC } from 'react'
|
||||
import type { FileUploadNodeType } from './types'
|
||||
import type { NodeProps } from '@/app/components/workflow/types'
|
||||
import * as React from 'react'
|
||||
|
||||
const Node: FC<NodeProps<FileUploadNodeType>> = () => {
|
||||
return (
|
||||
<div></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Node)
|
||||
65
web/app/components/workflow/nodes/file-upload/panel.tsx
Normal file
65
web/app/components/workflow/nodes/file-upload/panel.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import type { FC } from 'react'
|
||||
import type { FileUploadNodeType } from './types'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
|
||||
import useConfig from './use-config'
|
||||
|
||||
const i18nPrefix = 'nodes.fileUpload'
|
||||
|
||||
const Panel: FC<NodePanelProps<FileUploadNodeType>> = ({
|
||||
id,
|
||||
data,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
readOnly,
|
||||
inputs,
|
||||
handleVarChanges,
|
||||
filterVar,
|
||||
} = useConfig(id, data)
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
<div className="space-y-4 px-4 pb-4">
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.inputVar`, { ns: 'workflow' })}
|
||||
required
|
||||
>
|
||||
<VarReferencePicker
|
||||
readonly={readOnly}
|
||||
nodeId={id}
|
||||
isShowNodeName
|
||||
value={inputs.variable_selector || []}
|
||||
onChange={handleVarChanges}
|
||||
filterVar={filterVar}
|
||||
typePlaceHolder="File | Array[File]"
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<Split />
|
||||
<div>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
name="sandbox_path"
|
||||
type={inputs.is_array_file ? 'array[string]' : 'string'}
|
||||
description={t(`${i18nPrefix}.outputVars.sandboxPath`, { ns: 'workflow' })}
|
||||
/>
|
||||
<VarItem
|
||||
name="file_name"
|
||||
type={inputs.is_array_file ? 'array[string]' : 'string'}
|
||||
description={t(`${i18nPrefix}.outputVars.fileName`, { ns: 'workflow' })}
|
||||
/>
|
||||
</>
|
||||
</OutputVars>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Panel)
|
||||
6
web/app/components/workflow/nodes/file-upload/types.ts
Normal file
6
web/app/components/workflow/nodes/file-upload/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types'
|
||||
|
||||
export type FileUploadNodeType = CommonNodeType & {
|
||||
variable_selector: ValueSelector
|
||||
is_array_file: boolean
|
||||
}
|
||||
65
web/app/components/workflow/nodes/file-upload/use-config.ts
Normal file
65
web/app/components/workflow/nodes/file-upload/use-config.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import type { ValueSelector, Var } from '../../types'
|
||||
import type { FileUploadNodeType } from './types'
|
||||
import { produce } from 'immer'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodesReadOnly,
|
||||
useWorkflow,
|
||||
useWorkflowVariables,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import { VarType } from '../../types'
|
||||
|
||||
const useConfig = (id: string, payload: FileUploadNodeType) => {
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
const { inputs, setInputs } = useNodeCrud<FileUploadNodeType>(id, payload)
|
||||
|
||||
const filterVar = useCallback((varPayload: Var) => {
|
||||
return varPayload.type === VarType.file || varPayload.type === VarType.arrayFile
|
||||
}, [])
|
||||
|
||||
const isChatMode = useIsChatMode()
|
||||
const store = useStoreApi()
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const { getNodes } = store.getState()
|
||||
const currentNode = getNodes().find(n => n.id === id)
|
||||
const isInIteration = payload.isInIteration
|
||||
const iterationNode = isInIteration ? getNodes().find(n => n.id === currentNode!.parentId) : null
|
||||
const isInLoop = payload.isInLoop
|
||||
const loopNode = isInLoop ? getNodes().find(n => n.id === currentNode!.parentId) : null
|
||||
|
||||
const availableNodes = useMemo(() => {
|
||||
return getBeforeNodesInSameBranch(id)
|
||||
}, [getBeforeNodesInSameBranch, id])
|
||||
|
||||
const { getCurrentVariableType } = useWorkflowVariables()
|
||||
const getType = useCallback((variable?: ValueSelector) => {
|
||||
const varType = getCurrentVariableType({
|
||||
parentNode: isInIteration ? iterationNode : loopNode,
|
||||
valueSelector: variable || [],
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
isConstant: false,
|
||||
})
|
||||
return varType
|
||||
}, [getCurrentVariableType, isInIteration, iterationNode, loopNode, availableNodes, isChatMode])
|
||||
|
||||
const handleVarChanges = useCallback((variable: ValueSelector | string) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.variable_selector = variable as ValueSelector
|
||||
draft.is_array_file = getType(draft.variable_selector) === VarType.arrayFile
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs, getType])
|
||||
|
||||
return {
|
||||
readOnly,
|
||||
inputs,
|
||||
filterVar,
|
||||
handleVarChanges,
|
||||
}
|
||||
}
|
||||
|
||||
export default useConfig
|
||||
@ -0,0 +1,66 @@
|
||||
import type { RefObject } from 'react'
|
||||
import type { FileUploadNodeType } from './types'
|
||||
import type { InputVar, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
|
||||
const i18nPrefix = 'nodes.fileUpload'
|
||||
|
||||
type Params = {
|
||||
id: string
|
||||
payload: FileUploadNodeType
|
||||
runInputData: Record<string, unknown>
|
||||
runInputDataRef: RefObject<Record<string, unknown>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, unknown>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
}
|
||||
|
||||
const useSingleRunFormParams = ({
|
||||
payload,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
}: Params) => {
|
||||
const { t } = useTranslation()
|
||||
const files = runInputData.files
|
||||
|
||||
const setFiles = useCallback((newFiles: []) => {
|
||||
setRunInputData({
|
||||
...runInputData,
|
||||
files: newFiles,
|
||||
})
|
||||
}, [runInputData, setRunInputData])
|
||||
|
||||
const forms = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
inputs: [{
|
||||
label: t(`${i18nPrefix}.inputVar`, { ns: 'workflow' })!,
|
||||
variable: 'files',
|
||||
type: payload.is_array_file ? InputVarType.multiFiles : InputVarType.singleFile,
|
||||
required: true,
|
||||
}],
|
||||
values: { files },
|
||||
onChange: (keyValue: Record<string, unknown>) => setFiles((keyValue.files as []) || []),
|
||||
},
|
||||
]
|
||||
}, [files, payload.is_array_file, setFiles, t])
|
||||
|
||||
const getDependentVars = () => {
|
||||
return [payload.variable_selector]
|
||||
}
|
||||
|
||||
const getDependentVar = (variable: string) => {
|
||||
if (variable === 'files')
|
||||
return payload.variable_selector
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
getDependentVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
Reference in New Issue
Block a user