mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-15 22:06:52 +08:00
Feat/attachments (#9526)
Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: JzoNg <jzongcode@gmail.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import type { Var } from '@/app/components/workflow/types'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||
import BaseInput from '@/app/components/base/input'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http.authorization'
|
||||
@@ -146,9 +147,7 @@ const Authorization: FC<Props> = ({
|
||||
</Field>
|
||||
{tempPayload.config?.type === APIType.custom && (
|
||||
<Field title={t(`${i18nPrefix}.header`)} isRequired>
|
||||
<input
|
||||
type='text'
|
||||
className='w-full h-8 leading-8 px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px] placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
|
||||
<BaseInput
|
||||
value={tempPayload.config?.header || ''}
|
||||
onChange={handleAPIKeyOrHeaderChange('header')}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import produce from 'immer'
|
||||
import type { Body } from '../../types'
|
||||
import { BodyType } from '../../types'
|
||||
import useKeyValueList from '../../hooks/use-key-value-list'
|
||||
import { uniqueId } from 'lodash-es'
|
||||
import type { Body, BodyPayload, KeyValue as KeyValueType } from '../../types'
|
||||
import { BodyPayloadValueType, BodyType } from '../../types'
|
||||
import KeyValue from '../key-value'
|
||||
import useAvailableVarList from '../../../_base/hooks/use-available-var-list'
|
||||
import VarReferencePicker from '../../../_base/components/variable/var-reference-picker'
|
||||
import cn from '@/utils/classnames'
|
||||
import InputWithVar from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import type { Var } from '@/app/components/workflow/types'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
|
||||
const UNIQUE_ID_PREFIX = 'key-value-'
|
||||
|
||||
type Props = {
|
||||
readonly: boolean
|
||||
nodeId: string
|
||||
@@ -23,15 +26,17 @@ const allTypes = [
|
||||
BodyType.none,
|
||||
BodyType.formData,
|
||||
BodyType.xWwwFormUrlencoded,
|
||||
BodyType.rawText,
|
||||
BodyType.json,
|
||||
BodyType.rawText,
|
||||
BodyType.binary,
|
||||
]
|
||||
const bodyTextMap = {
|
||||
[BodyType.none]: 'none',
|
||||
[BodyType.formData]: 'form-data',
|
||||
[BodyType.xWwwFormUrlencoded]: 'x-www-form-urlencoded',
|
||||
[BodyType.rawText]: 'raw text',
|
||||
[BodyType.rawText]: 'raw',
|
||||
[BodyType.json]: 'JSON',
|
||||
[BodyType.binary]: 'binary',
|
||||
}
|
||||
|
||||
const EditBody: FC<Props> = ({
|
||||
@@ -40,7 +45,15 @@ const EditBody: FC<Props> = ({
|
||||
payload,
|
||||
onChange,
|
||||
}) => {
|
||||
const { type } = payload
|
||||
const { type, data } = payload
|
||||
const bodyPayload = useMemo(() => {
|
||||
if (typeof data === 'string') { // old data
|
||||
return []
|
||||
}
|
||||
return data
|
||||
}, [data])
|
||||
const stringValue = [BodyType.formData, BodyType.xWwwFormUrlencoded].includes(type) ? '' : (bodyPayload[0]?.value || '')
|
||||
|
||||
const { availableVars, availableNodes } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
@@ -50,49 +63,69 @@ const EditBody: FC<Props> = ({
|
||||
|
||||
const handleTypeChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newType = e.target.value as BodyType
|
||||
const hasKeyValue = [BodyType.formData, BodyType.xWwwFormUrlencoded].includes(newType)
|
||||
onChange({
|
||||
type: newType,
|
||||
data: '',
|
||||
data: hasKeyValue
|
||||
? [
|
||||
{
|
||||
id: uniqueId(UNIQUE_ID_PREFIX),
|
||||
type: BodyPayloadValueType.text,
|
||||
key: '',
|
||||
value: '',
|
||||
},
|
||||
]
|
||||
: [],
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
setBody([])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [onChange])
|
||||
|
||||
const isCurrentKeyValue = type === BodyType.formData || type === BodyType.xWwwFormUrlencoded
|
||||
|
||||
const {
|
||||
list: body,
|
||||
setList: setBody,
|
||||
addItem: addBody,
|
||||
} = useKeyValueList(payload.data, (value) => {
|
||||
if (!isCurrentKeyValue)
|
||||
return
|
||||
|
||||
const newBody = produce(payload, (draft: Body) => {
|
||||
draft.data = value
|
||||
const handleAddBody = useCallback(() => {
|
||||
const newPayload = produce(payload, (draft) => {
|
||||
(draft.data as BodyPayload).push({
|
||||
id: uniqueId(UNIQUE_ID_PREFIX),
|
||||
type: BodyPayloadValueType.text,
|
||||
key: '',
|
||||
value: '',
|
||||
})
|
||||
})
|
||||
onChange(newBody)
|
||||
}, type === BodyType.json)
|
||||
onChange(newPayload)
|
||||
}, [onChange, payload])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCurrentKeyValue)
|
||||
return
|
||||
|
||||
const newBody = produce(payload, (draft: Body) => {
|
||||
draft.data = body.map((item) => {
|
||||
if (!item.key && !item.value)
|
||||
return ''
|
||||
return `${item.key}:${item.value}`
|
||||
}).join('\n')
|
||||
const handleBodyPayloadChange = useCallback((newList: KeyValueType[]) => {
|
||||
const newPayload = produce(payload, (draft) => {
|
||||
draft.data = newList as BodyPayload
|
||||
})
|
||||
onChange(newBody)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isCurrentKeyValue])
|
||||
onChange(newPayload)
|
||||
}, [onChange, payload])
|
||||
|
||||
const filterOnlyFileVariable = (varPayload: Var) => {
|
||||
return [VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||
}
|
||||
|
||||
const handleBodyValueChange = useCallback((value: string) => {
|
||||
const newBody = produce(payload, (draft: Body) => {
|
||||
draft.data = value
|
||||
if ((draft.data as BodyPayload).length === 0) {
|
||||
(draft.data as BodyPayload).push({
|
||||
id: uniqueId(UNIQUE_ID_PREFIX),
|
||||
type: BodyPayloadValueType.text,
|
||||
key: '',
|
||||
value: '',
|
||||
})
|
||||
}
|
||||
(draft.data as BodyPayload)[0].value = value
|
||||
})
|
||||
onChange(newBody)
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleFileChange = useCallback((value: ValueSelector | string) => {
|
||||
const newBody = produce(payload, (draft: Body) => {
|
||||
if ((draft.data as BodyPayload).length === 0) {
|
||||
(draft.data as BodyPayload).push({
|
||||
id: uniqueId(UNIQUE_ID_PREFIX),
|
||||
type: BodyPayloadValueType.file,
|
||||
})
|
||||
}
|
||||
(draft.data as BodyPayload)[0].file = value as ValueSelector
|
||||
})
|
||||
onChange(newBody)
|
||||
}, [onChange, payload])
|
||||
@@ -122,9 +155,10 @@ const EditBody: FC<Props> = ({
|
||||
<KeyValue
|
||||
readonly={readonly}
|
||||
nodeId={nodeId}
|
||||
list={body}
|
||||
onChange={setBody}
|
||||
onAdd={addBody}
|
||||
list={bodyPayload as KeyValueType[]}
|
||||
onChange={handleBodyPayloadChange}
|
||||
onAdd={handleAddBody}
|
||||
isSupportFile={type === BodyType.formData}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -133,7 +167,7 @@ const EditBody: FC<Props> = ({
|
||||
instanceId={'http-body-raw'}
|
||||
title={<div className='uppercase'>Raw text</div>}
|
||||
onChange={handleBodyValueChange}
|
||||
value={payload.data}
|
||||
value={stringValue}
|
||||
justVar
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodes}
|
||||
@@ -145,7 +179,7 @@ const EditBody: FC<Props> = ({
|
||||
<InputWithVar
|
||||
instanceId={'http-body-json'}
|
||||
title='JSON'
|
||||
value={payload.data}
|
||||
value={stringValue}
|
||||
onChange={handleBodyValueChange}
|
||||
justVar
|
||||
nodesOutputVars={availableVars}
|
||||
@@ -153,6 +187,16 @@ const EditBody: FC<Props> = ({
|
||||
readOnly={readonly}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === BodyType.binary && (
|
||||
<VarReferencePicker
|
||||
nodeId={nodeId}
|
||||
readonly={readonly}
|
||||
value={bodyPayload[0]?.file || []}
|
||||
onChange={handleFileChange}
|
||||
filterVar={filterOnlyFileVariable}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ type Props = {
|
||||
list: KeyValue[]
|
||||
onChange: (newList: KeyValue[]) => void
|
||||
onAdd: () => void
|
||||
isSupportFile?: boolean
|
||||
// toggleKeyValueEdit: () => void
|
||||
}
|
||||
|
||||
@@ -19,6 +20,7 @@ const KeyValueList: FC<Props> = ({
|
||||
list,
|
||||
onChange,
|
||||
onAdd,
|
||||
isSupportFile,
|
||||
// toggleKeyValueEdit,
|
||||
}) => {
|
||||
// const handleBulkValueChange = useCallback((value: string) => {
|
||||
@@ -48,6 +50,7 @@ const KeyValueList: FC<Props> = ({
|
||||
list={list}
|
||||
onChange={onChange}
|
||||
onAdd={onAdd}
|
||||
isSupportFile={isSupportFile}
|
||||
// onSwitchToBulkEdit={toggleKeyValueEdit}
|
||||
/>
|
||||
// : <BulkEdit
|
||||
|
||||
@@ -5,8 +5,7 @@ import produce from 'immer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { KeyValue } from '../../../types'
|
||||
import KeyValueItem from './item'
|
||||
// import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
// import { EditList } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http'
|
||||
|
||||
@@ -16,6 +15,7 @@ type Props = {
|
||||
list: KeyValue[]
|
||||
onChange: (newList: KeyValue[]) => void
|
||||
onAdd: () => void
|
||||
isSupportFile?: boolean
|
||||
// onSwitchToBulkEdit: () => void
|
||||
keyNotSupportVar?: boolean
|
||||
insertVarTipToLeft?: boolean
|
||||
@@ -27,6 +27,7 @@ const KeyValueList: FC<Props> = ({
|
||||
list,
|
||||
onChange,
|
||||
onAdd,
|
||||
isSupportFile,
|
||||
// onSwitchToBulkEdit,
|
||||
keyNotSupportVar,
|
||||
insertVarTipToLeft,
|
||||
@@ -55,23 +56,11 @@ const KeyValueList: FC<Props> = ({
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='border border-gray-200 rounded-lg overflow-hidden'>
|
||||
<div className='flex items-center h-7 leading-7 text-xs font-medium text-gray-500 uppercase'>
|
||||
<div className='w-1/2 h-full pl-3 border-r border-gray-200'>{t(`${i18nPrefix}.key`)}</div>
|
||||
<div className='flex w-1/2 h-full pl-3 pr-1 items-center justify-between'>
|
||||
<div>{t(`${i18nPrefix}.value`)}</div>
|
||||
{/* {!readonly && (
|
||||
<TooltipPlus
|
||||
popupContent={t(`${i18nPrefix}.bulkEdit`)}
|
||||
>
|
||||
<div
|
||||
className='p-1 cursor-pointer rounded-md hover:bg-black/5 text-gray-500 hover:text-gray-800'
|
||||
onClick={onSwitchToBulkEdit}
|
||||
>
|
||||
<EditList className='w-3 h-3' />
|
||||
</div>
|
||||
</TooltipPlus>)} */}
|
||||
</div>
|
||||
<div className='border border-divider-regular rounded-lg overflow-hidden'>
|
||||
<div className={cn('flex items-center h-7 leading-7 text-text-tertiary system-xs-medium-uppercase')}>
|
||||
<div className={cn('h-full pl-3 border-r border-divider-regular', isSupportFile ? 'w-[140px]' : 'w-1/2')}>{t(`${i18nPrefix}.key`)}</div>
|
||||
{isSupportFile && <div className='shrink-0 w-[70px] h-full pl-3 border-r border-divider-regular'>{t(`${i18nPrefix}.type`)}</div>}
|
||||
<div className={cn('h-full pl-3 pr-1 items-center justify-between', isSupportFile ? 'grow' : 'w-1/2')}>{t(`${i18nPrefix}.value`)}</div>
|
||||
</div>
|
||||
{
|
||||
list.map((item, index) => (
|
||||
@@ -86,6 +75,7 @@ const KeyValueList: FC<Props> = ({
|
||||
onAdd={onAdd}
|
||||
readonly={readonly}
|
||||
canRemove={list.length > 1}
|
||||
isSupportFile={isSupportFile}
|
||||
keyNotSupportVar={keyNotSupportVar}
|
||||
insertVarTipToLeft={insertVarTipToLeft}
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,7 @@ type Props = {
|
||||
onRemove?: () => void
|
||||
placeholder?: string
|
||||
readOnly?: boolean
|
||||
isSupportFile?: boolean
|
||||
insertVarTipToLeft?: boolean
|
||||
}
|
||||
|
||||
@@ -31,6 +32,7 @@ const InputItem: FC<Props> = ({
|
||||
onRemove,
|
||||
placeholder,
|
||||
readOnly,
|
||||
isSupportFile,
|
||||
insertVarTipToLeft,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -41,7 +43,11 @@ const InputItem: FC<Props> = ({
|
||||
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||
const supportVarTypes = [VarType.string, VarType.number, VarType.secret]
|
||||
if (isSupportFile)
|
||||
supportVarTypes.push(...[VarType.file, VarType.arrayFile])
|
||||
|
||||
return supportVarTypes.includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -4,9 +4,13 @@ import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import type { KeyValue } from '../../../types'
|
||||
import VarReferencePicker from '../../../../_base/components/variable/var-reference-picker'
|
||||
import InputItem from './input-item'
|
||||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { PortalSelect } from '@/app/components/base/select'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
// import Input from '@/app/components/base/input'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http'
|
||||
|
||||
@@ -21,6 +25,7 @@ type Props = {
|
||||
onRemove: () => void
|
||||
isLastItem: boolean
|
||||
onAdd: () => void
|
||||
isSupportFile?: boolean
|
||||
keyNotSupportVar?: boolean
|
||||
insertVarTipToLeft?: boolean
|
||||
}
|
||||
@@ -36,26 +41,29 @@ const KeyValueItem: FC<Props> = ({
|
||||
onRemove,
|
||||
isLastItem,
|
||||
onAdd,
|
||||
isSupportFile,
|
||||
keyNotSupportVar,
|
||||
insertVarTipToLeft,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleChange = useCallback((key: string) => {
|
||||
return (value: string) => {
|
||||
return (value: string | ValueSelector) => {
|
||||
const newPayload = produce(payload, (draft: any) => {
|
||||
draft[key] = value
|
||||
})
|
||||
onChange(newPayload)
|
||||
if (key === 'value' && isLastItem)
|
||||
onAdd()
|
||||
}
|
||||
}, [onChange, onAdd, isLastItem, payload])
|
||||
}, [onChange, payload])
|
||||
|
||||
const filterOnlyFileVariable = (varPayload: Var) => {
|
||||
return [VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||
}
|
||||
|
||||
return (
|
||||
// group class name is for hover row show remove button
|
||||
<div className={cn(className, 'group flex h-min-7 border-t border-gray-200')}>
|
||||
<div className='w-1/2 border-r border-gray-200'>
|
||||
<div className={cn('shrink-0 border-r border-divider-regular', isSupportFile ? 'w-[140px]' : 'w-1/2')}>
|
||||
{!keyNotSupportVar
|
||||
? (
|
||||
<InputItem
|
||||
@@ -70,25 +78,56 @@ const KeyValueItem: FC<Props> = ({
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Input
|
||||
className='rounded-none bg-white border-none system-sm-regular focus:ring-0 focus:bg-gray-100! hover:bg-gray-50'
|
||||
<input
|
||||
className='appearance-none outline-none rounded-none bg-white border-none system-sm-regular focus:ring-0 focus:bg-gray-100! hover:bg-gray-50'
|
||||
value={payload.key}
|
||||
onChange={e => handleChange('key')(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='w-1/2'>
|
||||
<InputItem
|
||||
instanceId={`http-value-${instanceId}`}
|
||||
nodeId={nodeId}
|
||||
value={payload.value}
|
||||
onChange={handleChange('value')}
|
||||
hasRemove={!readonly && canRemove}
|
||||
onRemove={onRemove}
|
||||
placeholder={t(`${i18nPrefix}.value`)!}
|
||||
readOnly={readonly}
|
||||
insertVarTipToLeft={insertVarTipToLeft}
|
||||
/>
|
||||
{isSupportFile && (
|
||||
<div className='shrink-0 w-[70px] border-r border-divider-regular'>
|
||||
<PortalSelect
|
||||
value={payload.type!}
|
||||
onSelect={item => handleChange('type')(item.value as string)}
|
||||
items={[
|
||||
{ name: 'text', value: 'text' },
|
||||
{ name: 'file', value: 'file' },
|
||||
]}
|
||||
readonly={readonly}
|
||||
triggerClassName='rounded-none h-7'
|
||||
triggerClassNameFn={isOpen => isOpen ? 'bg-state-base-hover' : 'bg-transparent'}
|
||||
popupClassName='w-[80px] h-7'
|
||||
/>
|
||||
</div>)}
|
||||
<div className={cn(isSupportFile ? 'grow' : 'w-1/2')} onClick={() => isLastItem && onAdd()}>
|
||||
{(isSupportFile && payload.type === 'file')
|
||||
? (
|
||||
<VarReferencePicker
|
||||
nodeId={nodeId}
|
||||
readonly={readonly}
|
||||
value={payload.file || []}
|
||||
onChange={handleChange('file')}
|
||||
filterVar={filterOnlyFileVariable}
|
||||
isInTable
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<InputItem
|
||||
instanceId={`http-value-${instanceId}`}
|
||||
nodeId={nodeId}
|
||||
value={payload.value}
|
||||
onChange={handleChange('value')}
|
||||
hasRemove={!readonly && canRemove}
|
||||
onRemove={onRemove}
|
||||
placeholder={t(`${i18nPrefix}.value`)!}
|
||||
readOnly={readonly}
|
||||
isSupportFile={isSupportFile}
|
||||
insertVarTipToLeft={insertVarTipToLeft}
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { Timeout as TimeoutPayloadType } from '../../types'
|
||||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
|
||||
type Props = {
|
||||
@@ -32,10 +33,18 @@ const InputField: FC<{
|
||||
<span className="text-[13px] font-medium text-gray-900">{title}</span>
|
||||
<span className="text-xs font-normal text-gray-500">{description}</span>
|
||||
</div>
|
||||
<input className="w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-9 bg-gray-100 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200" value={value} onChange={(e) => {
|
||||
const value = Math.max(min, Math.min(max, parseInt(e.target.value, 10)))
|
||||
onChange(value)
|
||||
}} placeholder={placeholder} type='number' readOnly={readOnly} min={min} max={max} />
|
||||
<Input
|
||||
type='number'
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
const value = Math.max(min, Math.min(max, parseInt(e.target.value, 10)))
|
||||
onChange(value)
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
min={min}
|
||||
max={max}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BlockEnum } from '../../types'
|
||||
import type { NodeDefault } from '../../types'
|
||||
import { AuthorizationType, BodyType, type HttpNodeType, Method } from './types'
|
||||
import { AuthorizationType, BodyType, Method } from './types'
|
||||
import type { BodyPayload, HttpNodeType } from './types'
|
||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
||||
|
||||
const nodeDefault: NodeDefault<HttpNodeType> = {
|
||||
@@ -16,7 +17,7 @@ const nodeDefault: NodeDefault<HttpNodeType> = {
|
||||
params: '',
|
||||
body: {
|
||||
type: BodyType.none,
|
||||
data: '',
|
||||
data: [],
|
||||
},
|
||||
timeout: {
|
||||
max_connect_timeout: 0,
|
||||
@@ -40,6 +41,12 @@ const nodeDefault: NodeDefault<HttpNodeType> = {
|
||||
if (!errorMessages && !payload.url)
|
||||
errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.api') })
|
||||
|
||||
if (!errorMessages
|
||||
&& payload.body.type === BodyType.binary
|
||||
&& ((!(payload.body.data as BodyPayload)[0]?.file) || (payload.body.data as BodyPayload)[0]?.file?.length === 0)
|
||||
)
|
||||
errorMessages = t('workflow.errorMsg.fieldRequired', { field: t('workflow.nodes.http.binaryFileVariable') })
|
||||
|
||||
return {
|
||||
isValid: !errorMessages,
|
||||
errorMessage: errorMessages,
|
||||
|
||||
@@ -27,6 +27,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
|
||||
const {
|
||||
readOnly,
|
||||
isDataReady,
|
||||
inputs,
|
||||
handleMethodChange,
|
||||
handleUrlChange,
|
||||
@@ -53,6 +54,9 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
setInputVarValues,
|
||||
runResult,
|
||||
} = useConfig(id, data)
|
||||
// To prevent prompt editor in body not update data.
|
||||
if (!isDataReady)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CommonNodeType, Variable } from '@/app/components/workflow/types'
|
||||
import type { CommonNodeType, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||
|
||||
export enum Method {
|
||||
get = 'get',
|
||||
@@ -15,17 +15,32 @@ export enum BodyType {
|
||||
xWwwFormUrlencoded = 'x-www-form-urlencoded',
|
||||
rawText = 'raw-text',
|
||||
json = 'json',
|
||||
binary = 'binary',
|
||||
}
|
||||
|
||||
export type KeyValue = {
|
||||
id?: string
|
||||
key: string
|
||||
value: string
|
||||
type?: string
|
||||
file?: ValueSelector
|
||||
}
|
||||
|
||||
export enum BodyPayloadValueType {
|
||||
text = 'text',
|
||||
file = 'file',
|
||||
}
|
||||
|
||||
export type BodyPayload = {
|
||||
id?: string
|
||||
key?: string
|
||||
type: BodyPayloadValueType
|
||||
file?: ValueSelector // when type is file
|
||||
value?: string // when type is text
|
||||
}[]
|
||||
export type Body = {
|
||||
type: BodyType
|
||||
data: string
|
||||
data: string | BodyPayload // string is deprecated, it would convert to BodyPayload after loaded
|
||||
}
|
||||
|
||||
export enum AuthorizationType {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import produce from 'immer'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import useVarList from '../_base/hooks/use-var-list'
|
||||
import { VarType } from '../../types'
|
||||
import type { Var } from '../../types'
|
||||
import { useStore } from '../../store'
|
||||
import type { Authorization, Body, HttpNodeType, Method, Timeout } from './types'
|
||||
import { type Authorization, type Body, BodyType, type HttpNodeType, type Method, type Timeout } from './types'
|
||||
import useKeyValueList from './hooks/use-key-value-list'
|
||||
import { transformToBodyPayload } from './utils'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import useOneStepRun from '@/app/components/workflow/nodes/_base/hooks/use-one-step-run'
|
||||
import {
|
||||
@@ -25,15 +26,23 @@ const useConfig = (id: string, payload: HttpNodeType) => {
|
||||
setInputs,
|
||||
})
|
||||
|
||||
const [isDataReady, setIsDataReady] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const isReady = defaultConfig && Object.keys(defaultConfig).length > 0
|
||||
if (isReady) {
|
||||
setInputs({
|
||||
const newInputs = {
|
||||
...defaultConfig,
|
||||
...inputs,
|
||||
})
|
||||
}
|
||||
const bodyData = newInputs.body.data
|
||||
if (typeof bodyData === 'string')
|
||||
newInputs.body.data = transformToBodyPayload(bodyData, [BodyType.formData, BodyType.xWwwFormUrlencoded].includes(newInputs.body.type))
|
||||
|
||||
setInputs(newInputs)
|
||||
setIsDataReady(true)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultConfig])
|
||||
|
||||
const handleMethodChange = useCallback((method: Method) => {
|
||||
@@ -123,11 +132,23 @@ const useConfig = (id: string, payload: HttpNodeType) => {
|
||||
defaultRunInputData: {},
|
||||
})
|
||||
|
||||
const fileVarInputs = useMemo(() => {
|
||||
if (!Array.isArray(inputs.body.data))
|
||||
return ''
|
||||
|
||||
const res = inputs.body.data
|
||||
.filter(item => item.file?.length)
|
||||
.map(item => item.file ? `{{#${item.file.join('.')}#}}` : '')
|
||||
.join(' ')
|
||||
return res
|
||||
}, [inputs.body.data])
|
||||
|
||||
const varInputs = getInputVars([
|
||||
inputs.url,
|
||||
inputs.headers,
|
||||
inputs.params,
|
||||
inputs.body.data,
|
||||
typeof inputs.body.data === 'string' ? inputs.body.data : inputs.body.data.map(item => item.value).join(''),
|
||||
fileVarInputs,
|
||||
])
|
||||
|
||||
const inputVarValues = (() => {
|
||||
@@ -145,6 +166,7 @@ const useConfig = (id: string, payload: HttpNodeType) => {
|
||||
|
||||
return {
|
||||
readOnly,
|
||||
isDataReady,
|
||||
inputs,
|
||||
handleVarListChange,
|
||||
handleAddVariable,
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
import type { HttpNodeType } from './types'
|
||||
import { type BodyPayload, BodyPayloadValueType } from './types'
|
||||
|
||||
export const checkNodeValid = (payload: HttpNodeType) => {
|
||||
return true
|
||||
export const transformToBodyPayload = (old: string, hasKey: boolean): BodyPayload => {
|
||||
if (!hasKey) {
|
||||
return [
|
||||
{
|
||||
type: BodyPayloadValueType.text,
|
||||
value: old,
|
||||
},
|
||||
]
|
||||
}
|
||||
const bodyPayload = old.split('\n').map((item) => {
|
||||
const [key, value] = item.split(':')
|
||||
return {
|
||||
key: key || '',
|
||||
type: BodyPayloadValueType.text,
|
||||
value: value || '',
|
||||
}
|
||||
})
|
||||
return bodyPayload
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user