Spaces:
Paused
Paused
| 'use client' | |
| import type { FC } from 'react' | |
| import React, { useState } from 'react' | |
| import { useTranslation } from 'react-i18next' | |
| import produce from 'immer' | |
| import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest } from '../types' | |
| import cn from '@/utils/classnames' | |
| import Drawer from '@/app/components/base/drawer-plus' | |
| import Input from '@/app/components/base/input' | |
| import Textarea from '@/app/components/base/textarea' | |
| import Button from '@/app/components/base/button' | |
| import Toast from '@/app/components/base/toast' | |
| import EmojiPicker from '@/app/components/base/emoji-picker' | |
| import AppIcon from '@/app/components/base/app-icon' | |
| import MethodSelector from '@/app/components/tools/workflow-tool/method-selector' | |
| import LabelSelector from '@/app/components/tools/labels/selector' | |
| import ConfirmModal from '@/app/components/tools/workflow-tool/confirm-modal' | |
| import Tooltip from '@/app/components/base/tooltip' | |
| type Props = { | |
| isAdd?: boolean | |
| payload: any | |
| onHide: () => void | |
| onRemove?: () => void | |
| onCreate?: (payload: WorkflowToolProviderRequest & { workflow_app_id: string }) => void | |
| onSave?: (payload: WorkflowToolProviderRequest & Partial<{ | |
| workflow_app_id: string | |
| workflow_tool_id: string | |
| }>) => void | |
| } | |
| // Add and Edit | |
| const WorkflowToolAsModal: FC<Props> = ({ | |
| isAdd, | |
| payload, | |
| onHide, | |
| onRemove, | |
| onSave, | |
| onCreate, | |
| }) => { | |
| const { t } = useTranslation() | |
| const [showEmojiPicker, setShowEmojiPicker] = useState<Boolean>(false) | |
| const [emoji, setEmoji] = useState<Emoji>(payload.icon) | |
| const [label, setLabel] = useState<string>(payload.label) | |
| const [name, setName] = useState(payload.name) | |
| const [description, setDescription] = useState(payload.description) | |
| const [parameters, setParameters] = useState<WorkflowToolProviderParameter[]>(payload.parameters) | |
| const handleParameterChange = (key: string, value: string, index: number) => { | |
| const newData = produce(parameters, (draft: WorkflowToolProviderParameter[]) => { | |
| if (key === 'description') | |
| draft[index].description = value | |
| else | |
| draft[index].form = value | |
| }) | |
| setParameters(newData) | |
| } | |
| const [labels, setLabels] = useState<string[]>(payload.labels) | |
| const handleLabelSelect = (value: string[]) => { | |
| setLabels(value) | |
| } | |
| const [privacyPolicy, setPrivacyPolicy] = useState(payload.privacy_policy) | |
| const [showModal, setShowModal] = useState(false) | |
| const isNameValid = (name: string) => { | |
| // when the user has not input anything, no need for a warning | |
| if (name === '') | |
| return true | |
| return /^[a-zA-Z0-9_]+$/.test(name) | |
| } | |
| const onConfirm = () => { | |
| let errorMessage = '' | |
| if (!label) | |
| errorMessage = t('common.errorMsg.fieldRequired', { field: t('tools.createTool.name') }) | |
| if (!name) | |
| errorMessage = t('common.errorMsg.fieldRequired', { field: t('tools.createTool.nameForToolCall') }) | |
| if (!isNameValid(name)) | |
| errorMessage = t('tools.createTool.nameForToolCall') + t('tools.createTool.nameForToolCallTip') | |
| if (errorMessage) { | |
| Toast.notify({ | |
| type: 'error', | |
| message: errorMessage, | |
| }) | |
| return | |
| } | |
| const requestParams = { | |
| name, | |
| description, | |
| icon: emoji, | |
| label, | |
| parameters: parameters.map(item => ({ | |
| name: item.name, | |
| description: item.description, | |
| form: item.form, | |
| })), | |
| labels, | |
| privacy_policy: privacyPolicy, | |
| } | |
| if (!isAdd) { | |
| onSave?.({ | |
| ...requestParams, | |
| workflow_tool_id: payload.workflow_tool_id, | |
| }) | |
| } | |
| else { | |
| onCreate?.({ | |
| ...requestParams, | |
| workflow_app_id: payload.workflow_app_id, | |
| }) | |
| } | |
| } | |
| return ( | |
| <> | |
| <Drawer | |
| isShow | |
| onHide={onHide} | |
| title={t('workflow.common.workflowAsTool')!} | |
| panelClassName='mt-2 !w-[640px]' | |
| maxWidthClassName='!max-w-[640px]' | |
| height='calc(100vh - 16px)' | |
| headerClassName='!border-b-black/5' | |
| body={ | |
| <div className='flex flex-col h-full'> | |
| <div className='grow h-0 overflow-y-auto px-6 py-3 space-y-4'> | |
| {/* name & icon */} | |
| <div> | |
| <div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.name')} <span className='ml-1 text-red-500'>*</span></div> | |
| <div className='flex items-center justify-between gap-3'> | |
| <AppIcon size='large' onClick={() => { setShowEmojiPicker(true) }} className='cursor-pointer' iconType='emoji' icon={emoji.content} background={emoji.background} /> | |
| <Input | |
| className='grow h-10' | |
| placeholder={t('tools.createTool.toolNamePlaceHolder')!} | |
| value={label} | |
| onChange={e => setLabel(e.target.value)} | |
| /> | |
| </div> | |
| </div> | |
| {/* name for tool call */} | |
| <div> | |
| <div className='flex items-center py-2 leading-5 text-sm font-medium text-gray-900'> | |
| {t('tools.createTool.nameForToolCall')} <span className='ml-1 text-red-500'>*</span> | |
| <Tooltip | |
| popupContent={ | |
| <div className='w-[180px]'> | |
| {t('tools.createTool.nameForToolCallPlaceHolder')} | |
| </div> | |
| } | |
| /> | |
| </div> | |
| <Input | |
| className='h-10' | |
| placeholder={t('tools.createTool.nameForToolCallPlaceHolder')!} | |
| value={name} | |
| onChange={e => setName(e.target.value)} | |
| /> | |
| {!isNameValid(name) && ( | |
| <div className='text-xs leading-[18px] text-red-500'>{t('tools.createTool.nameForToolCallTip')}</div> | |
| )} | |
| </div> | |
| {/* description */} | |
| <div> | |
| <div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.description')}</div> | |
| <Textarea | |
| placeholder={t('tools.createTool.descriptionPlaceholder') || ''} | |
| value={description} | |
| onChange={e => setDescription(e.target.value)} | |
| /> | |
| </div> | |
| {/* Tool Input */} | |
| <div> | |
| <div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.toolInput.title')}</div> | |
| <div className='rounded-lg border border-gray-200 w-full overflow-x-auto'> | |
| <table className='w-full leading-[18px] text-xs text-gray-700 font-normal'> | |
| <thead className='text-gray-500 uppercase'> | |
| <tr className='border-b border-gray-200'> | |
| <th className="p-2 pl-3 font-medium w-[156px]">{t('tools.createTool.toolInput.name')}</th> | |
| <th className="p-2 pl-3 font-medium w-[102px]">{t('tools.createTool.toolInput.method')}</th> | |
| <th className="p-2 pl-3 font-medium">{t('tools.createTool.toolInput.description')}</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {parameters.map((item, index) => ( | |
| <tr key={index} className='border-b last:border-0 border-gray-200'> | |
| <td className="p-2 pl-3 max-w-[156px]"> | |
| <div className='text-[13px] leading-[18px]'> | |
| <div title={item.name} className='flex'> | |
| <span className='font-medium text-gray-900 truncate'>{item.name}</span> | |
| <span className='shrink-0 pl-1 text-[#ec4a0a] text-xs leading-[18px]'>{item.required ? t('tools.createTool.toolInput.required') : ''}</span> | |
| </div> | |
| <div className='text-gray-500'>{item.type}</div> | |
| </div> | |
| </td> | |
| <td> | |
| {item.name === '__image' && ( | |
| <div className={cn( | |
| 'flex items-center gap-1 min-h-[56px] px-3 py-2 h-9 bg-white cursor-default', | |
| )}> | |
| <div className={cn('grow text-[13px] leading-[18px] text-gray-700 truncate')}> | |
| {t('tools.createTool.toolInput.methodParameter')} | |
| </div> | |
| </div> | |
| )} | |
| {item.name !== '__image' && ( | |
| <MethodSelector value={item.form} onChange={value => handleParameterChange('form', value, index)} /> | |
| )} | |
| </td> | |
| <td className="p-2 pl-3 text-gray-500 w-[236px]"> | |
| <input | |
| type='text' | |
| className='grow text-gray-700 text-[13px] leading-[18px] font-normal bg-white outline-none appearance-none caret-primary-600 placeholder:text-gray-300' | |
| placeholder={t('tools.createTool.toolInput.descriptionPlaceholder')!} | |
| value={item.description} | |
| onChange={e => handleParameterChange('description', e.target.value, index)} | |
| /> | |
| </td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| {/* Tags */} | |
| <div> | |
| <div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.toolInput.label')}</div> | |
| <LabelSelector value={labels} onChange={handleLabelSelect} /> | |
| </div> | |
| {/* Privacy Policy */} | |
| <div> | |
| <div className='py-2 leading-5 text-sm font-medium text-gray-900'>{t('tools.createTool.privacyPolicy')}</div> | |
| <Input | |
| className='h-10' | |
| value={privacyPolicy} | |
| onChange={e => setPrivacyPolicy(e.target.value)} | |
| placeholder={t('tools.createTool.privacyPolicyPlaceholder') || ''} /> | |
| </div> | |
| </div> | |
| <div className={cn((!isAdd && onRemove) ? 'justify-between' : 'justify-end', 'mt-2 shrink-0 flex py-4 px-6 rounded-b-[10px] bg-gray-50 border-t border-black/5')} > | |
| {!isAdd && onRemove && ( | |
| <Button onClick={onRemove} className='text-red-500 border-red-50 hover:border-red-500'>{t('common.operation.delete')}</Button> | |
| )} | |
| <div className='flex space-x-2 '> | |
| <Button onClick={onHide}>{t('common.operation.cancel')}</Button> | |
| <Button variant='primary' onClick={() => { | |
| if (isAdd) | |
| onConfirm() | |
| else | |
| setShowModal(true) | |
| }}>{t('common.operation.save')}</Button> | |
| </div> | |
| </div> | |
| </div> | |
| } | |
| isShowMask={true} | |
| clickOutsideNotOpen={true} | |
| /> | |
| {showEmojiPicker && <EmojiPicker | |
| onSelect={(icon, icon_background) => { | |
| setEmoji({ content: icon, background: icon_background }) | |
| setShowEmojiPicker(false) | |
| }} | |
| onClose={() => { | |
| setShowEmojiPicker(false) | |
| }} | |
| />} | |
| {showModal && ( | |
| <ConfirmModal | |
| show={showModal} | |
| onClose={() => setShowModal(false)} | |
| onConfirm={onConfirm} | |
| /> | |
| )} | |
| </> | |
| ) | |
| } | |
| export default React.memo(WorkflowToolAsModal) | |