mirror of https://github.com/langgenius/dify.git
feat: support file values in body
This commit is contained in:
parent
51597629b1
commit
dca4f9fe9c
|
|
@ -59,6 +59,7 @@ import {
|
|||
UPDATE_HISTORY_EVENT_EMITTER,
|
||||
} from './constants'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type PromptEditorProps = {
|
||||
instanceId?: string
|
||||
|
|
@ -147,7 +148,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
|||
<div className='relative min-h-5'>
|
||||
<RichTextPlugin
|
||||
contentEditable={<ContentEditable className={`${className} outline-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'} text-gray-700`} style={style || {}} />}
|
||||
placeholder={<Placeholder value={placeholder} className={placeholderClassName} compact={compact} />}
|
||||
placeholder={<Placeholder value={placeholder} className={cn('truncate', placeholderClassName)} compact={compact} />}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<ComponentPickerBlock
|
||||
|
|
|
|||
|
|
@ -289,14 +289,20 @@ type PortalSelectProps = {
|
|||
onSelect: (value: Item) => void
|
||||
items: Item[]
|
||||
placeholder?: string
|
||||
triggerClassName?: string
|
||||
triggerClassNameFn?: (open: boolean) => string
|
||||
popupClassName?: string
|
||||
readonly?: boolean
|
||||
}
|
||||
const PortalSelect: FC<PortalSelectProps> = ({
|
||||
value,
|
||||
onSelect,
|
||||
items,
|
||||
placeholder,
|
||||
triggerClassName,
|
||||
triggerClassNameFn,
|
||||
popupClassName,
|
||||
readonly,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
|
@ -310,11 +316,11 @@ const PortalSelect: FC<PortalSelectProps> = ({
|
|||
placement='bottom-start'
|
||||
offset={4}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)} className='w-full'>
|
||||
<PortalToFollowElemTrigger onClick={() => !readonly && setOpen(v => !v)} className='w-full'>
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-between px-2.5 h-9 rounded-lg border-0 bg-gray-100 text-sm cursor-pointer
|
||||
`}
|
||||
className={classNames(`
|
||||
flex items-center justify-between px-2.5 h-9 rounded-lg border-0 bg-gray-100 text-sm ${readonly ? 'cursor-not-allowed' : 'cursor-pointer'}
|
||||
`, triggerClassName, triggerClassNameFn?.(open))}
|
||||
title={selectedItem?.name}
|
||||
>
|
||||
<span
|
||||
|
|
@ -352,7 +358,7 @@ const PortalSelect: FC<PortalSelectProps> = ({
|
|||
{item.name}
|
||||
</span>
|
||||
{item.value === value && (
|
||||
<CheckIcon className='shrink-0 h-4 w-4' />
|
||||
<CheckIcon className='shrink-0 h-4 w-4 text-text-accent' />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ const EditBody: FC<Props> = ({
|
|||
onChange(newBody)
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleBinaryFileChange = useCallback((value: ValueSelector | string) => {
|
||||
const handleFileChange = useCallback((value: ValueSelector | string) => {
|
||||
const newBody = produce(payload, (draft: Body) => {
|
||||
if ((draft.data as BodyPayload).length === 0) {
|
||||
(draft.data as BodyPayload).push({
|
||||
|
|
@ -193,7 +193,7 @@ const EditBody: FC<Props> = ({
|
|||
nodeId={nodeId}
|
||||
readonly={readonly}
|
||||
value={bodyPayload[0]?.file || []}
|
||||
onChange={handleBinaryFileChange}
|
||||
onChange={handleFileChange}
|
||||
filterVar={filterOnlyFileVariable}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import produce from 'immer'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import type { KeyValue } from '../../../types'
|
||||
import KeyValueItem from './item'
|
||||
// import { EditList } from '@/app/components/base/icons/src/vender/solid/communication'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http'
|
||||
|
||||
|
|
@ -57,22 +57,10 @@ const KeyValueList: FC<Props> = ({
|
|||
|
||||
return (
|
||||
<div className='border border-divider-regular rounded-lg overflow-hidden'>
|
||||
<div className='flex items-center h-7 leading-7 text-text-tertiary system-xs-medium-uppercase'>
|
||||
<div className='w-1/2 h-full pl-3 border-r border-divider-regular'>{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={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) => (
|
||||
|
|
|
|||
|
|
@ -4,8 +4,12 @@ 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 { 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'
|
||||
|
|
@ -44,20 +48,22 @@ const KeyValueItem: FC<Props> = ({
|
|||
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])
|
||||
|
||||
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
|
||||
|
|
@ -79,19 +85,47 @@ const KeyValueItem: FC<Props> = ({
|
|||
/>
|
||||
)}
|
||||
</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}
|
||||
isSupportFile={isSupportFile}
|
||||
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}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ export type KeyValue = {
|
|||
id?: string
|
||||
key: string
|
||||
value: string
|
||||
type?: string
|
||||
file?: ValueSelector
|
||||
}
|
||||
|
||||
export enum BodyPayloadValueType {
|
||||
|
|
|
|||
|
|
@ -348,6 +348,7 @@ const translation = {
|
|||
apiPlaceholder: 'Enter URL, type ‘/’ insert variable',
|
||||
notStartWithHttp: 'API should start with http:// or https://',
|
||||
key: 'Key',
|
||||
type: 'Type',
|
||||
value: 'Value',
|
||||
bulkEdit: 'Bulk Edit',
|
||||
keyValueEdit: 'Key-Value Edit',
|
||||
|
|
|
|||
|
|
@ -348,6 +348,7 @@ const translation = {
|
|||
apiPlaceholder: '输入 URL,输入变量时请键入‘/’',
|
||||
notStartWithHttp: 'API 应该以 http:// 或 https:// 开头',
|
||||
key: '键',
|
||||
type: '类型',
|
||||
value: '值',
|
||||
bulkEdit: '批量编辑',
|
||||
keyValueEdit: '键值编辑',
|
||||
|
|
|
|||
Loading…
Reference in New Issue