mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 12:37:20 +08:00
feat: key value input
This commit is contained in:
parent
649c3d0732
commit
7fa25934af
@ -0,0 +1,25 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React from 'react'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import { Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
onClick: (e: React.MouseEvent) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Remove: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(className, 'p-1 cursor-pointer rounded-md hover:bg-black/5 text-gray-500 hover:text-gray-800')}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Trash03 className='w-4 h-4' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(Remove)
|
||||||
@ -1,61 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React, { useCallback } from 'react'
|
|
||||||
import { useBoolean } from 'ahooks'
|
|
||||||
import type { KeyValue } from '../types'
|
|
||||||
import { Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
payload: KeyValue
|
|
||||||
onChange: (newPayload: KeyValue) => void
|
|
||||||
onRemove: () => void
|
|
||||||
isLastItem: boolean
|
|
||||||
onAdd: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const KeyValueItem: FC<Props> = ({
|
|
||||||
payload,
|
|
||||||
onChange,
|
|
||||||
onRemove,
|
|
||||||
isLastItem,
|
|
||||||
onAdd,
|
|
||||||
}) => {
|
|
||||||
const [isKeyEditing, {
|
|
||||||
setTrue: setIsKeyEditing,
|
|
||||||
setFalse: setIsKeyEditingFalse,
|
|
||||||
|
|
||||||
}] = useBoolean(false)
|
|
||||||
const handleKeyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
onChange({
|
|
||||||
key: e.target.value,
|
|
||||||
value: payload.value,
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
{isKeyEditing
|
|
||||||
? (
|
|
||||||
<input
|
|
||||||
type='text'
|
|
||||||
value={payload.key}
|
|
||||||
onChange={handleKeyChange}
|
|
||||||
onBlur={setIsKeyEditingFalse}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
: <div onClick={setIsKeyEditing}>{payload.key}</div>}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
>
|
|
||||||
{payload.value}
|
|
||||||
<div
|
|
||||||
className='p-1 cursor-pointer rounded-md hover:bg-black/5'
|
|
||||||
onClick={onRemove}
|
|
||||||
>
|
|
||||||
<Trash03 className='w-4 h-4' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(KeyValueItem)
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import type { FC } from 'react'
|
|
||||||
import React from 'react'
|
|
||||||
import type { KeyValue } from '../types'
|
|
||||||
import KeyValueItem from './key-value-item'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
list: KeyValue[]
|
|
||||||
onChange: (newList: KeyValue[]) => void
|
|
||||||
onAdd: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const KeyValueList: FC<Props> = ({
|
|
||||||
list,
|
|
||||||
onChange,
|
|
||||||
onAdd,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<div>key</div>
|
|
||||||
<div>value</div>
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
list.map((item, index) => (
|
|
||||||
<KeyValueItem
|
|
||||||
key={index}
|
|
||||||
payload={item}
|
|
||||||
onChange={(newItem) => {
|
|
||||||
const newList = [...list]
|
|
||||||
newList[index] = newItem
|
|
||||||
onChange(newList)
|
|
||||||
}}
|
|
||||||
onRemove={() => {
|
|
||||||
const newList = [...list]
|
|
||||||
newList.splice(index, 1)
|
|
||||||
onChange(newList)
|
|
||||||
}}
|
|
||||||
isLastItem={index === list.length - 1}
|
|
||||||
onAdd={onAdd}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default React.memo(KeyValueList)
|
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback } from 'react'
|
||||||
|
import { useBoolean } from 'ahooks'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button'
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
value: string
|
||||||
|
onChange: (newValue: string) => void
|
||||||
|
hasRemove: boolean
|
||||||
|
onRemove?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputItem: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
hasRemove,
|
||||||
|
onRemove,
|
||||||
|
}) => {
|
||||||
|
const [isEdit, {
|
||||||
|
setTrue: setIsEditTrue,
|
||||||
|
setFalse: setIsEditFalse,
|
||||||
|
}] = useBoolean(false)
|
||||||
|
|
||||||
|
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
onChange(e.target.value)
|
||||||
|
}, [onChange])
|
||||||
|
|
||||||
|
const handleRemove = useCallback((e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onRemove?.()
|
||||||
|
}, [onRemove])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn(className, !isEdit && 'hover:bg-gray-50 hover:cursor-text', 'relative flex h-full items-center pl-2')}>
|
||||||
|
{isEdit
|
||||||
|
? (
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
className='w-full h-[18px] leading-[18px] pl-0.5 text-gray-900 text-xs font-normal placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
onBlur={setIsEditFalse}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: <div
|
||||||
|
className="pl-0.5 w-full h-[18px] leading-[18px]"
|
||||||
|
onClick={setIsEditTrue}
|
||||||
|
>
|
||||||
|
<div className='text-gray-900 text-xs font-normal'>{value}</div>
|
||||||
|
{hasRemove && !isEdit && (
|
||||||
|
<RemoveButton
|
||||||
|
className='group-hover:block hidden absolute right-1 top-0.5'
|
||||||
|
onClick={handleRemove}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(InputItem)
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback } from 'react'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import produce from 'immer'
|
||||||
|
import type { KeyValue } from '../../types'
|
||||||
|
import InputItem from './input-item'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string
|
||||||
|
readonly: boolean
|
||||||
|
canRemove: boolean
|
||||||
|
payload: KeyValue
|
||||||
|
onChange: (newPayload: KeyValue) => void
|
||||||
|
onRemove: () => void
|
||||||
|
isLastItem: boolean
|
||||||
|
onAdd: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyValueItem: FC<Props> = ({
|
||||||
|
className,
|
||||||
|
readonly,
|
||||||
|
canRemove,
|
||||||
|
payload,
|
||||||
|
onChange,
|
||||||
|
onRemove,
|
||||||
|
isLastItem,
|
||||||
|
onAdd,
|
||||||
|
}) => {
|
||||||
|
const handleChange = useCallback((key: string) => {
|
||||||
|
return (value: string) => {
|
||||||
|
const newPayload = produce(payload, (draft: any) => {
|
||||||
|
draft[key] = value
|
||||||
|
})
|
||||||
|
onChange(newPayload)
|
||||||
|
if (key === 'value' && isLastItem)
|
||||||
|
onAdd()
|
||||||
|
}
|
||||||
|
}, [onChange, onAdd, isLastItem, payload])
|
||||||
|
|
||||||
|
return (
|
||||||
|
// group class name is for hover row show remove button
|
||||||
|
<div className={cn(className, 'group flex items-center h-7 border-t border-gray-200')}>
|
||||||
|
<div className='w-1/2 h-full border-r border-gray-200'>
|
||||||
|
<InputItem
|
||||||
|
className='pr-2.5'
|
||||||
|
value={payload.key}
|
||||||
|
onChange={handleChange('key')}
|
||||||
|
hasRemove={false} />
|
||||||
|
</div>
|
||||||
|
<div className='w-1/2 h-full'>
|
||||||
|
<InputItem
|
||||||
|
className='pr-1'
|
||||||
|
value={payload.value}
|
||||||
|
onChange={handleChange('value')}
|
||||||
|
hasRemove={!readonly && canRemove}
|
||||||
|
onRemove={onRemove}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(KeyValueItem)
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback } from 'react'
|
||||||
|
import produce from 'immer'
|
||||||
|
import type { KeyValue } from '../../types'
|
||||||
|
import KeyValueItem from './item'
|
||||||
|
type Props = {
|
||||||
|
readonly: boolean
|
||||||
|
list: KeyValue[]
|
||||||
|
onChange: (newList: KeyValue[]) => void
|
||||||
|
onAdd: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyValueList: FC<Props> = ({
|
||||||
|
readonly,
|
||||||
|
list,
|
||||||
|
onChange,
|
||||||
|
onAdd,
|
||||||
|
}) => {
|
||||||
|
const handleChange = useCallback((index: number) => {
|
||||||
|
return (newItem: KeyValue) => {
|
||||||
|
const newList = produce(list, (draft: any) => {
|
||||||
|
draft[index] = newItem
|
||||||
|
})
|
||||||
|
onChange(newList)
|
||||||
|
}
|
||||||
|
}, [list, onChange])
|
||||||
|
|
||||||
|
const handleRemove = useCallback((index: number) => {
|
||||||
|
return () => {
|
||||||
|
const newList = produce(list, (draft: any) => {
|
||||||
|
draft.splice(index, 1)
|
||||||
|
})
|
||||||
|
onChange(newList)
|
||||||
|
}
|
||||||
|
}, [list, onChange])
|
||||||
|
|
||||||
|
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'>key</div>
|
||||||
|
<div className='w-1/2 h-full pl-3'>value</div>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
list.map((item, index) => (
|
||||||
|
<KeyValueItem
|
||||||
|
key={index}
|
||||||
|
payload={item}
|
||||||
|
onChange={handleChange(index)}
|
||||||
|
onRemove={handleRemove(index)}
|
||||||
|
isLastItem={index === list.length - 1}
|
||||||
|
onAdd={onAdd}
|
||||||
|
readonly={readonly}
|
||||||
|
canRemove={list.length > 1}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default React.memo(KeyValueList)
|
||||||
@ -1,9 +1,11 @@
|
|||||||
|
import { BlockEnum } from '../../types'
|
||||||
|
import { MethodEnum } from './types'
|
||||||
import type { HttpNodeType } from './types'
|
import type { HttpNodeType } from './types'
|
||||||
|
|
||||||
export const mockData: HttpNodeType = {
|
export const mockData: HttpNodeType = {
|
||||||
title: 'Test',
|
title: 'Test',
|
||||||
desc: 'Test',
|
desc: 'Test',
|
||||||
type: 'Test',
|
type: BlockEnum.HttpRequest,
|
||||||
variables: [
|
variables: [
|
||||||
{
|
{
|
||||||
variable: 'name',
|
variable: 'name',
|
||||||
@ -14,9 +16,9 @@ export const mockData: HttpNodeType = {
|
|||||||
value_selector: ['bbb', 'b', 'c'],
|
value_selector: ['bbb', 'b', 'c'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
method: 'get',
|
method: MethodEnum.get,
|
||||||
url: 'https://api.dify.com/xx',
|
url: 'https://api.dify.com/xx',
|
||||||
headers: '',
|
headers: 'Content-Type: application/json\nAccept: */*',
|
||||||
params: '',
|
params: '',
|
||||||
body: {
|
body: {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
|
|||||||
@ -3,11 +3,13 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import useConfig from './use-config'
|
import useConfig from './use-config'
|
||||||
import { mockData } from './mock'
|
import { mockData } from './mock'
|
||||||
import ApiInput from './components/api-input'
|
import ApiInput from './components/api-input'
|
||||||
|
import KeyValueList from './components/key-value/list'
|
||||||
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
import VarList from '@/app/components/workflow/nodes/_base/components/variable/var-list'
|
||||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||||
import AddButton from '@/app/components/base/button/add-button'
|
import AddButton from '@/app/components/base/button/add-button'
|
||||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.http'
|
const i18nPrefix = 'workflow.nodes.http'
|
||||||
|
|
||||||
const Panel: FC = () => {
|
const Panel: FC = () => {
|
||||||
@ -20,6 +22,9 @@ const Panel: FC = () => {
|
|||||||
handleAddVariable,
|
handleAddVariable,
|
||||||
handleMethodChange,
|
handleMethodChange,
|
||||||
handleUrlChange,
|
handleUrlChange,
|
||||||
|
headers,
|
||||||
|
setHeaders,
|
||||||
|
addHeader,
|
||||||
} = useConfig(mockData)
|
} = useConfig(mockData)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -51,7 +56,12 @@ const Panel: FC = () => {
|
|||||||
<Field
|
<Field
|
||||||
title={t(`${i18nPrefix}.headers`)}
|
title={t(`${i18nPrefix}.headers`)}
|
||||||
>
|
>
|
||||||
headers
|
<KeyValueList
|
||||||
|
list={headers}
|
||||||
|
onChange={setHeaders}
|
||||||
|
onAdd={addHeader}
|
||||||
|
readonly={readOnly}
|
||||||
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field
|
<Field
|
||||||
title={t(`${i18nPrefix}.params`)}
|
title={t(`${i18nPrefix}.params`)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user