feat: support file values in body

This commit is contained in:
Joel 2024-08-29 16:18:16 +08:00
parent 51597629b1
commit dca4f9fe9c
8 changed files with 75 additions and 42 deletions

View File

@ -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

View File

@ -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>
))}

View File

@ -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}
/>
)}

View File

@ -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) => (

View File

@ -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>
)

View File

@ -22,6 +22,8 @@ export type KeyValue = {
id?: string
key: string
value: string
type?: string
file?: ValueSelector
}
export enum BodyPayloadValueType {

View File

@ -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',

View File

@ -348,6 +348,7 @@ const translation = {
apiPlaceholder: '输入 URL输入变量时请键入/',
notStartWithHttp: 'API 应该以 http:// 或 https:// 开头',
key: '键',
type: '类型',
value: '值',
bulkEdit: '批量编辑',
keyValueEdit: '键值编辑',