mirror of
https://github.com/langgenius/dify.git
synced 2026-04-27 02:36:29 +08:00
feat: support file values in body
This commit is contained in:
parent
51597629b1
commit
dca4f9fe9c
@ -59,6 +59,7 @@ import {
|
|||||||
UPDATE_HISTORY_EVENT_EMITTER,
|
UPDATE_HISTORY_EVENT_EMITTER,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
export type PromptEditorProps = {
|
export type PromptEditorProps = {
|
||||||
instanceId?: string
|
instanceId?: string
|
||||||
@ -147,7 +148,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
|||||||
<div className='relative min-h-5'>
|
<div className='relative min-h-5'>
|
||||||
<RichTextPlugin
|
<RichTextPlugin
|
||||||
contentEditable={<ContentEditable className={`${className} outline-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'} text-gray-700`} style={style || {}} />}
|
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}
|
ErrorBoundary={LexicalErrorBoundary}
|
||||||
/>
|
/>
|
||||||
<ComponentPickerBlock
|
<ComponentPickerBlock
|
||||||
|
|||||||
@ -289,14 +289,20 @@ type PortalSelectProps = {
|
|||||||
onSelect: (value: Item) => void
|
onSelect: (value: Item) => void
|
||||||
items: Item[]
|
items: Item[]
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
|
triggerClassName?: string
|
||||||
|
triggerClassNameFn?: (open: boolean) => string
|
||||||
popupClassName?: string
|
popupClassName?: string
|
||||||
|
readonly?: boolean
|
||||||
}
|
}
|
||||||
const PortalSelect: FC<PortalSelectProps> = ({
|
const PortalSelect: FC<PortalSelectProps> = ({
|
||||||
value,
|
value,
|
||||||
onSelect,
|
onSelect,
|
||||||
items,
|
items,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
triggerClassName,
|
||||||
|
triggerClassNameFn,
|
||||||
popupClassName,
|
popupClassName,
|
||||||
|
readonly,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
@ -310,11 +316,11 @@ const PortalSelect: FC<PortalSelectProps> = ({
|
|||||||
placement='bottom-start'
|
placement='bottom-start'
|
||||||
offset={4}
|
offset={4}
|
||||||
>
|
>
|
||||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)} className='w-full'>
|
<PortalToFollowElemTrigger onClick={() => !readonly && setOpen(v => !v)} className='w-full'>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={classNames(`
|
||||||
flex items-center justify-between px-2.5 h-9 rounded-lg border-0 bg-gray-100 text-sm cursor-pointer
|
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}
|
title={selectedItem?.name}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@ -352,7 +358,7 @@ const PortalSelect: FC<PortalSelectProps> = ({
|
|||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
{item.value === value && (
|
{item.value === value && (
|
||||||
<CheckIcon className='shrink-0 h-4 w-4' />
|
<CheckIcon className='shrink-0 h-4 w-4 text-text-accent' />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -117,7 +117,7 @@ const EditBody: FC<Props> = ({
|
|||||||
onChange(newBody)
|
onChange(newBody)
|
||||||
}, [onChange, payload])
|
}, [onChange, payload])
|
||||||
|
|
||||||
const handleBinaryFileChange = useCallback((value: ValueSelector | string) => {
|
const handleFileChange = useCallback((value: ValueSelector | string) => {
|
||||||
const newBody = produce(payload, (draft: Body) => {
|
const newBody = produce(payload, (draft: Body) => {
|
||||||
if ((draft.data as BodyPayload).length === 0) {
|
if ((draft.data as BodyPayload).length === 0) {
|
||||||
(draft.data as BodyPayload).push({
|
(draft.data as BodyPayload).push({
|
||||||
@ -193,7 +193,7 @@ const EditBody: FC<Props> = ({
|
|||||||
nodeId={nodeId}
|
nodeId={nodeId}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
value={bodyPayload[0]?.file || []}
|
value={bodyPayload[0]?.file || []}
|
||||||
onChange={handleBinaryFileChange}
|
onChange={handleFileChange}
|
||||||
filterVar={filterOnlyFileVariable}
|
filterVar={filterOnlyFileVariable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import produce from 'immer'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { KeyValue } from '../../../types'
|
import type { KeyValue } from '../../../types'
|
||||||
import KeyValueItem from './item'
|
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'
|
const i18nPrefix = 'workflow.nodes.http'
|
||||||
|
|
||||||
@ -57,22 +57,10 @@ const KeyValueList: FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='border border-divider-regular rounded-lg overflow-hidden'>
|
<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={cn('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={cn('h-full pl-3 border-r border-divider-regular', isSupportFile ? 'w-[140px]' : 'w-1/2')}>{t(`${i18nPrefix}.key`)}</div>
|
||||||
<div className='flex w-1/2 h-full pl-3 pr-1 items-center justify-between'>
|
{isSupportFile && <div className='shrink-0 w-[70px] h-full pl-3 border-r border-divider-regular'>{t(`${i18nPrefix}.type`)}</div>}
|
||||||
<div>{t(`${i18nPrefix}.value`)}</div>
|
<div className={cn('h-full pl-3 pr-1 items-center justify-between', isSupportFile ? 'grow' : 'w-1/2')}>{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>
|
</div>
|
||||||
{
|
{
|
||||||
list.map((item, index) => (
|
list.map((item, index) => (
|
||||||
|
|||||||
@ -4,8 +4,12 @@ import React, { useCallback } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import type { KeyValue } from '../../../types'
|
import type { KeyValue } from '../../../types'
|
||||||
|
import VarReferencePicker from '../../../../_base/components/variable/var-reference-picker'
|
||||||
import InputItem from './input-item'
|
import InputItem from './input-item'
|
||||||
import cn from '@/utils/classnames'
|
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'
|
// import Input from '@/app/components/base/input'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.http'
|
const i18nPrefix = 'workflow.nodes.http'
|
||||||
@ -44,20 +48,22 @@ const KeyValueItem: FC<Props> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleChange = useCallback((key: string) => {
|
const handleChange = useCallback((key: string) => {
|
||||||
return (value: string) => {
|
return (value: string | ValueSelector) => {
|
||||||
const newPayload = produce(payload, (draft: any) => {
|
const newPayload = produce(payload, (draft: any) => {
|
||||||
draft[key] = value
|
draft[key] = value
|
||||||
})
|
})
|
||||||
onChange(newPayload)
|
onChange(newPayload)
|
||||||
if (key === 'value' && isLastItem)
|
|
||||||
onAdd()
|
|
||||||
}
|
}
|
||||||
}, [onChange, onAdd, isLastItem, payload])
|
}, [onChange, onAdd, isLastItem, payload])
|
||||||
|
|
||||||
|
const filterOnlyFileVariable = (varPayload: Var) => {
|
||||||
|
return [VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// group class name is for hover row show remove button
|
// 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={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
|
{!keyNotSupportVar
|
||||||
? (
|
? (
|
||||||
<InputItem
|
<InputItem
|
||||||
@ -79,19 +85,47 @@ const KeyValueItem: FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='w-1/2'>
|
{isSupportFile && (
|
||||||
<InputItem
|
<div className='shrink-0 w-[70px] border-r border-divider-regular'>
|
||||||
instanceId={`http-value-${instanceId}`}
|
<PortalSelect
|
||||||
nodeId={nodeId}
|
value={payload.type!}
|
||||||
value={payload.value}
|
onSelect={item => handleChange('type')(item.value as string)}
|
||||||
onChange={handleChange('value')}
|
items={[
|
||||||
hasRemove={!readonly && canRemove}
|
{ name: 'text', value: 'text' },
|
||||||
onRemove={onRemove}
|
{ name: 'file', value: 'file' },
|
||||||
placeholder={t(`${i18nPrefix}.value`)!}
|
]}
|
||||||
readOnly={readonly}
|
readonly={readonly}
|
||||||
isSupportFile={isSupportFile}
|
triggerClassName='rounded-none h-7'
|
||||||
insertVarTipToLeft={insertVarTipToLeft}
|
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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -22,6 +22,8 @@ export type KeyValue = {
|
|||||||
id?: string
|
id?: string
|
||||||
key: string
|
key: string
|
||||||
value: string
|
value: string
|
||||||
|
type?: string
|
||||||
|
file?: ValueSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BodyPayloadValueType {
|
export enum BodyPayloadValueType {
|
||||||
|
|||||||
@ -348,6 +348,7 @@ const translation = {
|
|||||||
apiPlaceholder: 'Enter URL, type ‘/’ insert variable',
|
apiPlaceholder: 'Enter URL, type ‘/’ insert variable',
|
||||||
notStartWithHttp: 'API should start with http:// or https://',
|
notStartWithHttp: 'API should start with http:// or https://',
|
||||||
key: 'Key',
|
key: 'Key',
|
||||||
|
type: 'Type',
|
||||||
value: 'Value',
|
value: 'Value',
|
||||||
bulkEdit: 'Bulk Edit',
|
bulkEdit: 'Bulk Edit',
|
||||||
keyValueEdit: 'Key-Value Edit',
|
keyValueEdit: 'Key-Value Edit',
|
||||||
|
|||||||
@ -348,6 +348,7 @@ const translation = {
|
|||||||
apiPlaceholder: '输入 URL,输入变量时请键入‘/’',
|
apiPlaceholder: '输入 URL,输入变量时请键入‘/’',
|
||||||
notStartWithHttp: 'API 应该以 http:// 或 https:// 开头',
|
notStartWithHttp: 'API 应该以 http:// 或 https:// 开头',
|
||||||
key: '键',
|
key: '键',
|
||||||
|
type: '类型',
|
||||||
value: '值',
|
value: '值',
|
||||||
bulkEdit: '批量编辑',
|
bulkEdit: '批量编辑',
|
||||||
keyValueEdit: '键值编辑',
|
keyValueEdit: '键值编辑',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user