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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: '键值编辑',