mirror of https://github.com/langgenius/dify.git
update data structure
This commit is contained in:
parent
3ed561d943
commit
ce8325c83c
|
|
@ -0,0 +1,103 @@
|
|||
import { memo, useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Input from '@/app/components/base/input'
|
||||
import TextArea from '@/app/components/base/textarea'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { noop } from 'lodash-es'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.humanInput'
|
||||
|
||||
type Recipient = {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
type EmailConfigureModalProps = {
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
onConfirm: (data: any) => void
|
||||
}
|
||||
|
||||
const EmailConfigureModal = ({
|
||||
isShow,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}: EmailConfigureModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [recipients, setRecipients] = useState<Recipient[]>([])
|
||||
const [subject, setSubject] = useState('')
|
||||
const [body, setBody] = useState('')
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
onConfirm({
|
||||
recipients: recipients.map(recipient => recipient.value),
|
||||
subject,
|
||||
body,
|
||||
})
|
||||
}, [recipients, subject, body, onConfirm])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={noop}
|
||||
className='relative !max-w-[720px] !p-0'
|
||||
>
|
||||
<div className='absolute right-5 top-5 cursor-pointer p-1.5' onClick={onClose}>
|
||||
<RiCloseLine className='h-5 w-5 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='space-y-1 p-6 pb-3'>
|
||||
<div className='title-2xl-semi-bold text-text-primary'>{t(`${i18nPrefix}.deliveryMethod.emailConfigure.title`)}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{t(`${i18nPrefix}.deliveryMethod.emailConfigure.description`)}</div>
|
||||
</div>
|
||||
<div className='space-y-5 px-6 py-3'>
|
||||
<div>
|
||||
<div className='system-sm-medium mb-1 flex h-6 items-center text-text-secondary'>
|
||||
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.recipient`)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className='system-sm-medium mb-1 flex h-6 items-center text-text-secondary'>
|
||||
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.subject`)}
|
||||
</div>
|
||||
<Input
|
||||
className='w-full'
|
||||
value={subject}
|
||||
onChange={e => setSubject(e.target.value)}
|
||||
placeholder={t(`${i18nPrefix}.deliveryMethod.emailConfigure.subjectPlaceholder`)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className='system-sm-medium mb-1 flex h-6 items-center text-text-secondary'>
|
||||
{t(`${i18nPrefix}.deliveryMethod.emailConfigure.body`)}
|
||||
</div>
|
||||
<TextArea
|
||||
className="min-h-[200px] w-full"
|
||||
value={body}
|
||||
onChange={e => setBody(e.target.value)}
|
||||
placeholder={t('email.configure.enterBody', 'Enter email content')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-row-reverse gap-2 p-6 pt-5'>
|
||||
<Button
|
||||
variant='primary'
|
||||
className='w-[72px]'
|
||||
onClick={onClose}
|
||||
>
|
||||
{t('common.operation.save')}
|
||||
</Button>
|
||||
<Button
|
||||
className='w-[72px]'
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(EmailConfigureModal)
|
||||
|
|
@ -37,36 +37,36 @@ const DeliveryMethodForm: React.FC<Props> = ({ value, onchange }) => {
|
|||
|
||||
return (
|
||||
<div className='px-4 py-2'>
|
||||
<div className='mb-1 flex items-center justify-between'>
|
||||
<div className='flex items-center gap-0.5'>
|
||||
<div className='system-sm-semibold-uppercase text-text-secondary'>{t(`${i18nPrefix}.deliveryMethod.title`)}</div>
|
||||
<Tooltip
|
||||
popupContent={t(`${i18nPrefix}.deliveryMethod.tooltip`)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center px-1'>
|
||||
<MethodSelector
|
||||
data={value}
|
||||
onAdd={handleMethodAdd}
|
||||
/>
|
||||
</div>
|
||||
<div className='mb-1 flex items-center justify-between'>
|
||||
<div className='flex items-center gap-0.5'>
|
||||
<div className='system-sm-semibold-uppercase text-text-secondary'>{t(`${i18nPrefix}.deliveryMethod.title`)}</div>
|
||||
<Tooltip
|
||||
popupContent={t(`${i18nPrefix}.deliveryMethod.tooltip`)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center px-1'>
|
||||
<MethodSelector
|
||||
data={value}
|
||||
onAdd={handleMethodAdd}
|
||||
/>
|
||||
</div>
|
||||
{!value.length && (
|
||||
<div className='system-xs-regular flex items-center justify-center rounded-[10px] bg-background-section p-3 text-text-tertiary'>{t(`${i18nPrefix}.deliveryMethod.emptyTip`)}</div>
|
||||
)}
|
||||
{value.length > 0 && (
|
||||
<div className='space-y-1'>
|
||||
{value.map((method, index) => (
|
||||
<MethodItem
|
||||
method={method}
|
||||
key={index}
|
||||
onChange={handleMethodChange}
|
||||
onDelete={handleMethodDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!value.length && (
|
||||
<div className='system-xs-regular flex items-center justify-center rounded-[10px] bg-background-section p-3 text-text-tertiary'>{t(`${i18nPrefix}.deliveryMethod.emptyTip`)}</div>
|
||||
)}
|
||||
{value.length > 0 && (
|
||||
<div className='space-y-1'>
|
||||
{value.map((method, index) => (
|
||||
<MethodItem
|
||||
method={method}
|
||||
key={index}
|
||||
onChange={handleMethodChange}
|
||||
onDelete={handleMethodDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu
|
|||
import Button from '@/app/components/base/button'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import EmailConfigureModal from './email-configure-modal'
|
||||
import type { DeliveryMethod } from '../../types'
|
||||
import { DeliveryMethodType } from '../../types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
|
@ -25,6 +26,7 @@ type Props = {
|
|||
const DeliveryMethodItem: React.FC<Props> = ({ method, onChange, onDelete }) => {
|
||||
const { t } = useTranslation()
|
||||
const [isHovering, setIsHovering] = React.useState(false)
|
||||
const [showEmailModal, setShowEmailModal] = React.useState(false)
|
||||
|
||||
const handleEnableStatusChange = (enabled: boolean) => {
|
||||
onChange({
|
||||
|
|
@ -34,59 +36,71 @@ const DeliveryMethodItem: React.FC<Props> = ({ method, onChange, onDelete }) =>
|
|||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('group flex h-8 items-center justify-between rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg pl-1.5 pr-2 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm', isHovering && 'border-state-destructive-border bg-state-destructive-hover hover:bg-state-destructive-hover')}
|
||||
>
|
||||
<div className='flex items-center gap-1.5'>
|
||||
{method.type === DeliveryMethodType.WebApp && (
|
||||
<div className='rounded-[4px] border border-divider-regular bg-components-icon-bg-indigo-solid p-0.5'>
|
||||
<RiRobot2Fill className='h-3.5 w-3.5 text-text-primary-on-surface' />
|
||||
</div>
|
||||
)}
|
||||
{method.type === DeliveryMethodType.Email && (
|
||||
<div className='rounded-[4px] border border-divider-regular bg-components-icon-bg-blue-solid p-0.5'>
|
||||
<RiMailSendFill className='h-3.5 w-3.5 text-text-primary-on-surface' />
|
||||
</div>
|
||||
)}
|
||||
<div className='system-xs-medium capitalize text-text-secondary'>{method.type}</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='hidden items-end gap-1 group-hover:flex'>
|
||||
{method.type === DeliveryMethodType.Email && method.configure && (
|
||||
<ActionButton>
|
||||
<RiEqualizer2Line className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
<>
|
||||
<div
|
||||
className={cn('group flex h-8 items-center justify-between rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg pl-1.5 pr-2 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm', isHovering && 'border-state-destructive-border bg-state-destructive-hover hover:bg-state-destructive-hover')}
|
||||
>
|
||||
<div className='flex items-center gap-1.5'>
|
||||
{method.type === DeliveryMethodType.WebApp && (
|
||||
<div className='rounded-[4px] border border-divider-regular bg-components-icon-bg-indigo-solid p-0.5'>
|
||||
<RiRobot2Fill className='h-3.5 w-3.5 text-text-primary-on-surface' />
|
||||
</div>
|
||||
)}
|
||||
{method.type === DeliveryMethodType.Email && (
|
||||
<div className='rounded-[4px] border border-divider-regular bg-components-icon-bg-blue-solid p-0.5'>
|
||||
<RiMailSendFill className='h-3.5 w-3.5 text-text-primary-on-surface' />
|
||||
</div>
|
||||
)}
|
||||
<div className='system-xs-medium capitalize text-text-secondary'>{method.type}</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='hidden items-end gap-1 group-hover:flex'>
|
||||
{method.type === DeliveryMethodType.Email && method.config && (
|
||||
<ActionButton onClick={() => setShowEmailModal(true)}>
|
||||
<RiEqualizer2Line className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)}
|
||||
<div
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
>
|
||||
<ActionButton
|
||||
state={isHovering ? ActionButtonState.Destructive : ActionButtonState.Default}
|
||||
onClick={() => onDelete(method.type)}
|
||||
>
|
||||
<RiDeleteBinLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
{(method.config || method.type === DeliveryMethodType.WebApp) && (
|
||||
<Switch
|
||||
defaultValue={method.enabled}
|
||||
onChange={handleEnableStatusChange}
|
||||
/>
|
||||
)}
|
||||
{method.type === DeliveryMethodType.Email && !method.config && (
|
||||
<Button
|
||||
className='-mr-1'
|
||||
size='small'
|
||||
onClick={() => setShowEmailModal(true)}
|
||||
>
|
||||
{t(`${i18nPrefix}.deliveryMethod.notConfigured`)}
|
||||
<Indicator color='orange' className='ml-1' />
|
||||
</Button>
|
||||
)}
|
||||
<div
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
>
|
||||
<ActionButton
|
||||
state={isHovering ? ActionButtonState.Destructive : ActionButtonState.Default}
|
||||
onClick={() => onDelete(method.type)}
|
||||
>
|
||||
<RiDeleteBinLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
{(method.configure || method.type === DeliveryMethodType.WebApp) && (
|
||||
<Switch
|
||||
defaultValue={method.enabled}
|
||||
onChange={handleEnableStatusChange}
|
||||
/>
|
||||
)}
|
||||
{method.type === DeliveryMethodType.Email && !method.configure && (
|
||||
<Button
|
||||
className='-mr-1'
|
||||
size='small'
|
||||
onClick={() => onChange({ ...method, enabled: !method.enabled })}
|
||||
>
|
||||
{t(`${i18nPrefix}.deliveryMethod.notConfigured`)}
|
||||
<Indicator color='orange' className='ml-1' />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{showEmailModal && (
|
||||
<EmailConfigureModal
|
||||
isShow={showEmailModal}
|
||||
onClose={() => setShowEmailModal(false)}
|
||||
onConfirm={(data) => {
|
||||
onChange({ ...method, ...data })
|
||||
setShowEmailModal(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,22 +28,22 @@ const UserActionItem: FC<Props> = ({
|
|||
<div className='shrink-0'>
|
||||
<Input
|
||||
wrapperClassName='w-[120px]'
|
||||
value={data.name}
|
||||
value={data.id}
|
||||
placeholder={t(`${i18nPrefix}.userActions.actionNamePlaceholder`)}
|
||||
onChange={e => onChange({ ...data, name: e.target.value })}
|
||||
onChange={e => onChange({ ...data, id: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<Input
|
||||
value={data.text}
|
||||
value={data.title}
|
||||
placeholder={t(`${i18nPrefix}.userActions.buttonTextPlaceholder`)}
|
||||
onChange={e => onChange({ ...data, text: e.target.value })}
|
||||
onChange={e => onChange({ ...data, title: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<ButtonStyleDropdown
|
||||
text={data.text}
|
||||
data={data.type}
|
||||
onChange={type => onChange({ ...data, type })}
|
||||
text={data.title}
|
||||
data={data.button_style}
|
||||
onChange={type => onChange({ ...data, button_style: type })}
|
||||
/>
|
||||
<Button
|
||||
className='px-2'
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ALL_CHAT_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
|
|||
|
||||
const nodeDefault: NodeDefault<HumanInputNodeType> = {
|
||||
defaultValue: {
|
||||
deliveryMethod: [
|
||||
delivery_methods: [
|
||||
{
|
||||
type: DeliveryMethodType.WebApp,
|
||||
enabled: true,
|
||||
|
|
@ -15,30 +15,26 @@ const nodeDefault: NodeDefault<HumanInputNodeType> = {
|
|||
enabled: false,
|
||||
},
|
||||
],
|
||||
userActions: [
|
||||
user_actions: [
|
||||
{
|
||||
id: 'approve-action',
|
||||
name: 'approve',
|
||||
text: 'Post to X',
|
||||
type: UserActionButtonType.Primary,
|
||||
title: 'Post to X',
|
||||
button_style: UserActionButtonType.Primary,
|
||||
},
|
||||
{
|
||||
id: 'regenerate-action',
|
||||
name: 'regenerate',
|
||||
text: 'regenerate',
|
||||
type: UserActionButtonType.Default,
|
||||
title: 'regenerate',
|
||||
button_style: UserActionButtonType.Default,
|
||||
},
|
||||
{
|
||||
id: 'thinking-action',
|
||||
name: 'thinking',
|
||||
text: 'think more',
|
||||
type: UserActionButtonType.Accent,
|
||||
title: 'thinking',
|
||||
button_style: UserActionButtonType.Accent,
|
||||
},
|
||||
{
|
||||
id: 'cancel-action',
|
||||
name: 'cancel',
|
||||
text: 'cancel',
|
||||
type: UserActionButtonType.Ghost,
|
||||
title: 'cancel',
|
||||
button_style: UserActionButtonType.Ghost,
|
||||
},
|
||||
],
|
||||
timeout: {
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ const Node: FC<NodeProps<HumanInputNodeType>> = (props) => {
|
|||
const { t } = useTranslation()
|
||||
|
||||
const { data } = props
|
||||
const deliveryMethods = data.deliveryMethod
|
||||
const userActions = data.userActions
|
||||
const deliveryMethods = data.delivery_methods
|
||||
const userActions = data.user_actions
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import Divider from '@/app/components/base/divider'
|
|||
import DeliveryMethod from './components/delivery-method'
|
||||
import UserActionItem from './components/user-action'
|
||||
import TimeoutInput from './components/timeout'
|
||||
import { v4 as uuid4 } from 'uuid'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.humanInput'
|
||||
|
||||
|
|
@ -35,7 +34,7 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({
|
|||
<div className='py-2'>
|
||||
{/* delivery methods */}
|
||||
<DeliveryMethod
|
||||
value={inputs.deliveryMethod || []}
|
||||
value={inputs.delivery_methods || []}
|
||||
onchange={handleDeliveryMethodChange}
|
||||
/>
|
||||
<div className='px-4 py-2'>
|
||||
|
|
@ -54,10 +53,9 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({
|
|||
<ActionButton
|
||||
onClick={() => {
|
||||
handleUserActionAdd({
|
||||
id: uuid4(),
|
||||
name: 'Action',
|
||||
text: 'Button Text',
|
||||
type: UserActionButtonType.Default,
|
||||
id: 'Action',
|
||||
title: 'Button Text',
|
||||
button_style: UserActionButtonType.Default,
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
|
@ -65,12 +63,12 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({
|
|||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
{!inputs.userActions.length && (
|
||||
{!inputs.user_actions.length && (
|
||||
<div className='system-xs-regular flex items-center justify-center rounded-[10px] bg-background-section p-3 text-text-tertiary'>{t(`${i18nPrefix}.userActions.emptyTip`)}</div>
|
||||
)}
|
||||
{inputs.userActions.length > 0 && (
|
||||
{inputs.user_actions.length > 0 && (
|
||||
<div className='space-y-2'>
|
||||
{inputs.userActions.map(action => (
|
||||
{inputs.user_actions.map(action => (
|
||||
<UserActionItem
|
||||
key={action.id}
|
||||
data={action}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { CommonNodeType, Variable } from '@/app/components/workflow/types'
|
||||
|
||||
export type HumanInputNodeType = CommonNodeType & {
|
||||
deliveryMethod: DeliveryMethod[]
|
||||
formContent: any
|
||||
userActions: UserAction[]
|
||||
delivery_methods: DeliveryMethod[]
|
||||
form_content: any
|
||||
user_actions: UserAction[]
|
||||
timeout: Timeout
|
||||
outputs: Variable[]
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ export enum DeliveryMethodType {
|
|||
export type DeliveryMethod = {
|
||||
type: DeliveryMethodType
|
||||
enabled: boolean
|
||||
configure?: Record<string, any>
|
||||
config?: Record<string, any>
|
||||
}
|
||||
|
||||
export enum UserActionButtonType {
|
||||
|
|
@ -34,7 +34,6 @@ export enum UserActionButtonType {
|
|||
|
||||
export type UserAction = {
|
||||
id: string
|
||||
name: string
|
||||
text: string
|
||||
type: UserActionButtonType
|
||||
title: string
|
||||
button_style: UserActionButtonType
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,34 +14,34 @@ const useConfig = (id: string, payload: HumanInputNodeType) => {
|
|||
const handleDeliveryMethodChange = (methods: DeliveryMethod[]) => {
|
||||
setInputs({
|
||||
...inputs,
|
||||
deliveryMethod: methods,
|
||||
delivery_methods: methods,
|
||||
})
|
||||
}
|
||||
|
||||
const handleUserActionAdd = (newAction: UserAction) => {
|
||||
setInputs({
|
||||
...inputs,
|
||||
userActions: [...inputs.userActions, newAction],
|
||||
user_actions: [...inputs.user_actions, newAction],
|
||||
})
|
||||
}
|
||||
|
||||
const handleUserActionChange = (updatedAction: UserAction) => {
|
||||
const newActions = produce(inputs.userActions, (draft) => {
|
||||
const newActions = produce(inputs.user_actions, (draft) => {
|
||||
const index = draft.findIndex(a => a.id === updatedAction.id)
|
||||
if (index !== -1)
|
||||
draft[index] = updatedAction
|
||||
})
|
||||
setInputs({
|
||||
...inputs,
|
||||
userActions: newActions,
|
||||
user_actions: newActions,
|
||||
})
|
||||
}
|
||||
|
||||
const handleUserActionDelete = (actionId: string) => {
|
||||
const newActions = inputs.userActions.filter(action => action.id !== actionId)
|
||||
const newActions = inputs.user_actions.filter(action => action.id !== actionId)
|
||||
setInputs({
|
||||
...inputs,
|
||||
userActions: newActions,
|
||||
user_actions: newActions,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -928,6 +928,20 @@ const translation = {
|
|||
emailConfigure: {
|
||||
title: 'Email',
|
||||
description: 'Send request for input via email',
|
||||
recipient: 'Recipient',
|
||||
allMembers: 'All members within the {{workspaceName}}',
|
||||
subject: 'Email Subject',
|
||||
subjectPlaceholder: 'Enter email subject',
|
||||
body: 'Email Body',
|
||||
bodyPlaceholder: 'Enter email body',
|
||||
requestURLTip: 'The Request URL variable is the triggering entry point for Human Input.',
|
||||
memberSelector: {
|
||||
title: 'Add workspace members or external recipients',
|
||||
trigger: 'Select',
|
||||
placeholder: 'Emails, comma separated',
|
||||
add: '+ Add',
|
||||
added: 'Added',
|
||||
},
|
||||
},
|
||||
},
|
||||
formContent: 'form content',
|
||||
|
|
|
|||
|
|
@ -929,6 +929,20 @@ const translation = {
|
|||
emailConfigure: {
|
||||
title: '电子邮件配置',
|
||||
description: '通过电子邮件发送输入请求',
|
||||
recipient: '收件人',
|
||||
allMembers: '所有成员({{workspaceName}})',
|
||||
subject: '邮件主题',
|
||||
subjectPlaceholder: '输入邮件主题',
|
||||
body: '邮件正文',
|
||||
bodyPlaceholder: '输入邮件正文',
|
||||
requestURLTip: '请求 URL 变量是人类输入的触发入口。',
|
||||
memberSelector: {
|
||||
title: '添加工作区成员或外部收件人',
|
||||
trigger: '选择',
|
||||
placeholder: '电子邮件,以逗号分隔',
|
||||
add: '+ 添加',
|
||||
added: '已添加',
|
||||
},
|
||||
},
|
||||
},
|
||||
formContent: '表单内容',
|
||||
|
|
|
|||
Loading…
Reference in New Issue