mirror of https://github.com/langgenius/dify.git
feat: edit body
This commit is contained in:
parent
f1b868d5d9
commit
b08327cb4b
|
|
@ -7,6 +7,7 @@ type Props = {
|
|||
value: string
|
||||
onChange: (value: string) => void
|
||||
title: JSX.Element
|
||||
language?: string
|
||||
headerRight?: JSX.Element
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import Base from './base'
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
title: JSX.Element
|
||||
headerRight?: JSX.Element
|
||||
minHeight?: number
|
||||
onBlur?: () => void
|
||||
}
|
||||
|
||||
const TextEditor: FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
title,
|
||||
headerRight,
|
||||
minHeight,
|
||||
onBlur,
|
||||
}) => {
|
||||
const [isFocus, {
|
||||
setTrue: setIsFocus,
|
||||
setFalse: setIsNotFocus,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
setIsNotFocus()
|
||||
onBlur?.()
|
||||
}, [setIsNotFocus, onBlur])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Base
|
||||
title={title}
|
||||
value={value}
|
||||
headerRight={headerRight}
|
||||
isFocus={isFocus}
|
||||
minHeight={minHeight}
|
||||
>
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
onFocus={setIsFocus}
|
||||
onBlur={handleBlur}
|
||||
className='w-full h-full p-3 resize-none bg-transparent'
|
||||
/>
|
||||
</Base>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(TextEditor)
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { MethodEnum } from '../types'
|
||||
import { Method } from '../types'
|
||||
import Selector from '../../_base/components/selector'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
|
||||
const MethodOptions = [
|
||||
{ label: 'GET', value: MethodEnum.get },
|
||||
{ label: 'POST', value: MethodEnum.post },
|
||||
{ label: 'HEAD', value: MethodEnum.head },
|
||||
{ label: 'PATCH', value: MethodEnum.patch },
|
||||
{ label: 'PUT', value: MethodEnum.put },
|
||||
{ label: 'DELETE', value: MethodEnum.delete },
|
||||
{ label: 'GET', value: Method.get },
|
||||
{ label: 'POST', value: Method.post },
|
||||
{ label: 'HEAD', value: Method.head },
|
||||
{ label: 'PATCH', value: Method.patch },
|
||||
{ label: 'PUT', value: Method.put },
|
||||
{ label: 'DELETE', value: Method.delete },
|
||||
]
|
||||
type Props = {
|
||||
readonly: boolean
|
||||
method: MethodEnum
|
||||
onMethodChange: (method: MethodEnum) => void
|
||||
method: Method
|
||||
onMethodChange: (method: Method) => void
|
||||
url: string
|
||||
onUrlChange: (url: string) => void
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import produce from 'immer'
|
||||
import cn from 'classnames'
|
||||
import type { Body } from '../../types'
|
||||
import { BodyType } from '../../types'
|
||||
import useKeyValueList from '../../hooks/use-key-value-list'
|
||||
import KeyValue from '../key-value'
|
||||
import TextEditor from '../../../_base/components/editor/text-editor'
|
||||
import CodeEditor from '../../../_base/components/editor/code-editor'
|
||||
|
||||
type Props = {
|
||||
readonly: boolean
|
||||
payload: Body
|
||||
onChange: (newPayload: Body) => void
|
||||
}
|
||||
|
||||
const allTypes = [
|
||||
BodyType.none,
|
||||
BodyType.formData,
|
||||
BodyType.xWwwFormUrlencoded,
|
||||
BodyType.rawText,
|
||||
BodyType.json,
|
||||
]
|
||||
const bodyTextMap = {
|
||||
[BodyType.none]: 'none',
|
||||
[BodyType.formData]: 'form-data',
|
||||
[BodyType.xWwwFormUrlencoded]: 'x-www-form-urlencoded',
|
||||
[BodyType.rawText]: 'raw text',
|
||||
[BodyType.json]: 'JSON',
|
||||
}
|
||||
|
||||
const EditBody: FC<Props> = ({
|
||||
readonly,
|
||||
payload,
|
||||
onChange,
|
||||
}) => {
|
||||
const { type } = payload
|
||||
|
||||
const handleTypeChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newType = e.target.value as BodyType
|
||||
onChange({
|
||||
type: newType,
|
||||
data: '',
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
setBody([])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [onChange])
|
||||
|
||||
const {
|
||||
list: body,
|
||||
setList: setBody,
|
||||
addItem: addBody,
|
||||
isKeyValueEdit: isBodyKeyValueEdit,
|
||||
toggleIsKeyValueEdit: toggleIsBodyKeyValueEdit,
|
||||
} = useKeyValueList(payload.data)
|
||||
|
||||
const isCurrentKeyValue = type === BodyType.formData || type === BodyType.xWwwFormUrlencoded
|
||||
|
||||
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')
|
||||
})
|
||||
onChange(newBody)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [body, isCurrentKeyValue])
|
||||
|
||||
const handleBodyValueChange = useCallback((value: string) => {
|
||||
const newBody = produce(payload, (draft: Body) => {
|
||||
draft.data = value
|
||||
})
|
||||
onChange(newBody)
|
||||
}, [onChange, payload])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* body type */}
|
||||
<div className='flex flex-wrap'>
|
||||
{allTypes.map(t => (
|
||||
<label key={t} htmlFor={`body-type-${t}`} className='mr-4 flex items-center h-7 space-x-2'>
|
||||
<input
|
||||
type="radio"
|
||||
id={`body-type-${t}`}
|
||||
value={t}
|
||||
checked={type === t}
|
||||
onChange={handleTypeChange}
|
||||
/>
|
||||
<div className='leading-[18px] text-[13px] font-normal text-gray-700'>{bodyTextMap[t]}</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
{/* body value */}
|
||||
<div className={cn(type !== BodyType.none && 'mt-1')}>
|
||||
{type === BodyType.none && null}
|
||||
{(type === BodyType.formData || type === BodyType.xWwwFormUrlencoded) && (
|
||||
<KeyValue
|
||||
readonly={readonly}
|
||||
list={body}
|
||||
onChange={setBody}
|
||||
onAdd={addBody}
|
||||
isKeyValueEdit={isBodyKeyValueEdit}
|
||||
toggleKeyValueEdit={toggleIsBodyKeyValueEdit}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === BodyType.rawText && (
|
||||
<TextEditor
|
||||
title={<div className='uppercase'>Raw text</div>}
|
||||
onChange={handleBodyValueChange}
|
||||
value={payload.data}
|
||||
minHeight={150}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === BodyType.json && (
|
||||
<CodeEditor
|
||||
title={<div className='uppercase'>JSON</div>}
|
||||
value={payload.data} onChange={handleBodyValueChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(EditBody)
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/editor/base'
|
||||
import TextEditor from '@/app/components/workflow/nodes/_base/components/editor/text-editor'
|
||||
import { LayoutGrid02 } from '@/app/components/base/icons/src/vender/line/layout'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http'
|
||||
|
|
@ -21,12 +21,14 @@ const BulkEdit: FC<Props> = ({
|
|||
const { t } = useTranslation()
|
||||
const [tempValue, setTempValue] = React.useState(value)
|
||||
|
||||
const [isFocus, setIsFocus] = React.useState(false)
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setTempValue(e.target.value)
|
||||
const handleChange = useCallback((value: string) => {
|
||||
setTempValue(value)
|
||||
}, [])
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
onChange(tempValue)
|
||||
}, [tempValue, onChange])
|
||||
|
||||
const handleSwitchToKeyValueEdit = useCallback(() => {
|
||||
onChange(tempValue)
|
||||
onSwitchToKeyValueEdit()
|
||||
|
|
@ -34,9 +36,11 @@ const BulkEdit: FC<Props> = ({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<Editor
|
||||
<TextEditor
|
||||
title={<div className='uppercase'>{t(`${i18nPrefix}.bulkEdit`)}</div>}
|
||||
value={value}
|
||||
value={tempValue}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
headerRight={
|
||||
<div className='flex items-center h-[18px]'>
|
||||
<div
|
||||
|
|
@ -49,17 +53,8 @@ const BulkEdit: FC<Props> = ({
|
|||
<div className='ml-3 mr-1.5 w-px h-3 bg-gray-200'></div>
|
||||
</div>
|
||||
}
|
||||
isFocus={isFocus}
|
||||
minHeight={150}
|
||||
>
|
||||
<textarea
|
||||
value={tempValue}
|
||||
onChange={handleChange}
|
||||
onFocus={() => setIsFocus(true)}
|
||||
onBlur={() => setIsFocus(false)}
|
||||
className='w-full h-full p-3 resize-none bg-transparent'
|
||||
/>
|
||||
</Editor>
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,18 +5,21 @@ import type { KeyValue } from '../types'
|
|||
const strToKeyValueList = (value: string) => {
|
||||
return value.split('\n').map((item) => {
|
||||
const [key, value] = item.split(':')
|
||||
return { key: key.trim(), value: value.trim() }
|
||||
return { key: key.trim(), value: value?.trim() }
|
||||
})
|
||||
}
|
||||
|
||||
const useKeyValueList = (value: string) => {
|
||||
const [list, setList] = useState<KeyValue[]>(value ? strToKeyValueList(value) : [])
|
||||
|
||||
const addItem = useCallback(() => {
|
||||
setList(prev => [...prev, { key: '', value: '' }])
|
||||
}, [])
|
||||
|
||||
const [isKeyValueEdit, {
|
||||
toggle: toggleIsKeyValueEdit,
|
||||
}] = useBoolean(false)
|
||||
}] = useBoolean(true)
|
||||
|
||||
return {
|
||||
list: list.length === 0 ? [{ key: '', value: '' }] : list, // no item can not add new item
|
||||
setList,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { BlockEnum } from '../../types'
|
||||
import { MethodEnum } from './types'
|
||||
import { BodyType, Method } from './types'
|
||||
import type { HttpNodeType } from './types'
|
||||
|
||||
export const mockData: HttpNodeType = {
|
||||
|
|
@ -16,12 +16,12 @@ export const mockData: HttpNodeType = {
|
|||
value_selector: ['bbb', 'b', 'c'],
|
||||
},
|
||||
],
|
||||
method: MethodEnum.get,
|
||||
method: Method.get,
|
||||
url: 'https://api.dify.com/xx',
|
||||
headers: 'Content-Type: application/json\nAccept: */*',
|
||||
params: '',
|
||||
body: {
|
||||
type: 'json',
|
||||
type: BodyType.none,
|
||||
data: '',
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import useConfig from './use-config'
|
|||
import { mockData } from './mock'
|
||||
import ApiInput from './components/api-input'
|
||||
import KeyValue from './components/key-value'
|
||||
import EditBody from './components/edit-body'
|
||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http'
|
||||
|
||||
const Panel: FC = () => {
|
||||
|
|
@ -27,6 +27,12 @@ const Panel: FC = () => {
|
|||
addHeader,
|
||||
isHeaderKeyValueEdit,
|
||||
toggleIsHeaderKeyValueEdit,
|
||||
params,
|
||||
setParams,
|
||||
addParam,
|
||||
isParamKeyValueEdit,
|
||||
toggleIsParamKeyValueEdit,
|
||||
setBody,
|
||||
} = useConfig(mockData)
|
||||
|
||||
return (
|
||||
|
|
@ -70,12 +76,23 @@ const Panel: FC = () => {
|
|||
<Field
|
||||
title={t(`${i18nPrefix}.params`)}
|
||||
>
|
||||
params
|
||||
<KeyValue
|
||||
list={params}
|
||||
onChange={setParams}
|
||||
onAdd={addParam}
|
||||
readonly={readOnly}
|
||||
isKeyValueEdit={isParamKeyValueEdit}
|
||||
toggleKeyValueEdit={toggleIsParamKeyValueEdit}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.body`)}
|
||||
>
|
||||
body
|
||||
<EditBody
|
||||
readonly={readOnly}
|
||||
payload={inputs.body}
|
||||
onChange={setBody}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<Split />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { CommonNodeType, Variable } from '@/app/components/workflow/types'
|
||||
|
||||
export enum MethodEnum {
|
||||
export enum Method {
|
||||
get = 'get',
|
||||
post = 'post',
|
||||
head = 'head',
|
||||
|
|
@ -9,19 +9,29 @@ export enum MethodEnum {
|
|||
delete = 'delete',
|
||||
}
|
||||
|
||||
export enum BodyType {
|
||||
none = 'none',
|
||||
formData = 'form-data',
|
||||
xWwwFormUrlencoded = 'x-www-form-urlencoded',
|
||||
rawText = 'raw-text',
|
||||
json = 'json',
|
||||
}
|
||||
|
||||
export type KeyValue = {
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type Body = {
|
||||
type: BodyType
|
||||
data: string
|
||||
}
|
||||
|
||||
export type HttpNodeType = CommonNodeType & {
|
||||
variables: Variable[]
|
||||
method: MethodEnum
|
||||
method: Method
|
||||
url: string
|
||||
headers: string
|
||||
params: string
|
||||
body: {
|
||||
type: string
|
||||
data: string
|
||||
}
|
||||
body: Body
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import produce from 'immer'
|
||||
import useVarList from '../_base/hooks/use-var-list'
|
||||
import type { HttpNodeType, MethodEnum } from './types'
|
||||
import type { Body, HttpNodeType, Method } from './types'
|
||||
import useKeyValueList from './hooks/use-key-value-list'
|
||||
const useConfig = (initInputs: HttpNodeType) => {
|
||||
const [inputs, setInputs] = useState<HttpNodeType>(initInputs)
|
||||
|
|
@ -10,7 +11,7 @@ const useConfig = (initInputs: HttpNodeType) => {
|
|||
setInputs,
|
||||
})
|
||||
|
||||
const handleMethodChange = useCallback((method: MethodEnum) => {
|
||||
const handleMethodChange = useCallback((method: Method) => {
|
||||
setInputs(prev => ({
|
||||
...prev,
|
||||
method,
|
||||
|
|
@ -32,17 +33,41 @@ const useConfig = (initInputs: HttpNodeType) => {
|
|||
toggleIsKeyValueEdit: toggleIsHeaderKeyValueEdit,
|
||||
} = useKeyValueList(inputs.headers)
|
||||
|
||||
const {
|
||||
list: params,
|
||||
setList: setParams,
|
||||
addItem: addParam,
|
||||
isKeyValueEdit: isParamKeyValueEdit,
|
||||
toggleIsKeyValueEdit: toggleIsParamKeyValueEdit,
|
||||
} = useKeyValueList(inputs.params)
|
||||
|
||||
const setBody = useCallback((data: Body) => {
|
||||
const newInputs = produce(inputs, (draft: HttpNodeType) => {
|
||||
draft.body = data
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
return {
|
||||
inputs,
|
||||
handleVarListChange,
|
||||
handleAddVariable,
|
||||
handleMethodChange,
|
||||
handleUrlChange,
|
||||
// headers
|
||||
headers,
|
||||
setHeaders,
|
||||
addHeader,
|
||||
isHeaderKeyValueEdit,
|
||||
toggleIsHeaderKeyValueEdit,
|
||||
// params
|
||||
params,
|
||||
setParams,
|
||||
addParam,
|
||||
isParamKeyValueEdit,
|
||||
toggleIsParamKeyValueEdit,
|
||||
// body
|
||||
setBody,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue