mirror of https://github.com/langgenius/dify.git
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'
|
||||
|
||||
export const mockData: HttpNodeType = {
|
||||
title: 'Test',
|
||||
desc: 'Test',
|
||||
type: 'Test',
|
||||
type: BlockEnum.HttpRequest,
|
||||
variables: [
|
||||
{
|
||||
variable: 'name',
|
||||
|
|
@ -14,9 +16,9 @@ export const mockData: HttpNodeType = {
|
|||
value_selector: ['bbb', 'b', 'c'],
|
||||
},
|
||||
],
|
||||
method: 'get',
|
||||
method: MethodEnum.get,
|
||||
url: 'https://api.dify.com/xx',
|
||||
headers: '',
|
||||
headers: 'Content-Type: application/json\nAccept: */*',
|
||||
params: '',
|
||||
body: {
|
||||
type: 'json',
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ import { useTranslation } from 'react-i18next'
|
|||
import useConfig from './use-config'
|
||||
import { mockData } from './mock'
|
||||
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 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 = () => {
|
||||
|
|
@ -20,6 +22,9 @@ const Panel: FC = () => {
|
|||
handleAddVariable,
|
||||
handleMethodChange,
|
||||
handleUrlChange,
|
||||
headers,
|
||||
setHeaders,
|
||||
addHeader,
|
||||
} = useConfig(mockData)
|
||||
|
||||
return (
|
||||
|
|
@ -51,7 +56,12 @@ const Panel: FC = () => {
|
|||
<Field
|
||||
title={t(`${i18nPrefix}.headers`)}
|
||||
>
|
||||
headers
|
||||
<KeyValueList
|
||||
list={headers}
|
||||
onChange={setHeaders}
|
||||
onAdd={addHeader}
|
||||
readonly={readOnly}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.params`)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue