mirror of https://github.com/langgenius/dify.git
feat: enhance user action validation in human input form by adding checks for duplicate IDs, empty IDs, and empty titles; update translations accordingly
This commit is contained in:
parent
a298140d8f
commit
bd634b165d
|
|
@ -32,7 +32,7 @@ const RunMode = ({
|
|||
handleWorkflowRunAllTriggersInWorkflow,
|
||||
} = useWorkflowStartRun()
|
||||
const { handleStopRun } = useWorkflowRun()
|
||||
const { validateBeforeRun, warningNodes } = useWorkflowRunValidation()
|
||||
const { warningNodes } = useWorkflowRunValidation()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const isListening = useStore(s => s.isListening)
|
||||
|
||||
|
|
@ -98,14 +98,7 @@ const RunMode = ({
|
|||
// Placeholder for trigger-specific execution logic for schedule, webhook, plugin types
|
||||
console.log('TODO: Handle trigger execution for type:', option.type, 'nodeId:', option.nodeId)
|
||||
}
|
||||
}, [
|
||||
validateBeforeRun,
|
||||
handleWorkflowStartRunInWorkflow,
|
||||
handleWorkflowTriggerScheduleRunInWorkflow,
|
||||
handleWorkflowTriggerWebhookRunInWorkflow,
|
||||
handleWorkflowTriggerPluginRunInWorkflow,
|
||||
handleWorkflowRunAllTriggersInWorkflow,
|
||||
])
|
||||
}, [warningNodes, notify, t, handleWorkflowStartRunInWorkflow, handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, handleWorkflowRunAllTriggersInWorkflow])
|
||||
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
|
|
|
|||
|
|
@ -316,20 +316,7 @@ const useOneStepRun = <T>({
|
|||
invalidateSysVarValues()
|
||||
invalidateConversationVarValues() // loop, iteration, variable assigner node can update the conversation variables, but to simple the logic(some nodes may also can update in the future), all nodes refresh.
|
||||
}
|
||||
}, [
|
||||
isRunAfterSingleRun,
|
||||
runningStatus,
|
||||
flowId,
|
||||
id,
|
||||
store,
|
||||
appendNodeInspectVars,
|
||||
updateNodeInspectRunningState,
|
||||
invalidLastRun,
|
||||
isStartNode,
|
||||
isTriggerNode,
|
||||
invalidateSysVarValues,
|
||||
invalidateConversationVarValues,
|
||||
])
|
||||
}, [isRunAfterSingleRun, runningStatus, flowType, flowId, id, store, appendNodeInspectVars, updateNodeInspectRunningState, invalidLastRun, isStartNode, isTriggerNode, invalidateSysVarValues, invalidateConversationVarValues])
|
||||
|
||||
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
||||
const setNodeRunning = () => {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import * as React from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { genActionId } from '../utils'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import ButtonStyleDropdown from './button-style-dropdown'
|
||||
|
||||
const i18nPrefix = 'nodes.humanInput'
|
||||
const ACTION_ID_MAX_LENGTH = 20
|
||||
const BUTTON_TEXT_MAX_LENGTH = 40
|
||||
|
||||
type UserActionItemProps = {
|
||||
data: UserAction
|
||||
|
|
@ -28,17 +30,42 @@ const UserActionItem: FC<UserActionItemProps> = ({
|
|||
const { t } = useTranslation()
|
||||
|
||||
const handleIDChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.value.trim())
|
||||
onChange({ ...data, id: genActionId() })
|
||||
else
|
||||
onChange({ ...data, id: e.target.value })
|
||||
const value = e.target.value
|
||||
if (!value.trim()) {
|
||||
onChange({ ...data, id: '' })
|
||||
return
|
||||
}
|
||||
// Convert spaces to underscores, then only allow characters matching /^[A-Za-z_][A-Za-z0-9_]*$/
|
||||
const withUnderscores = value.replace(/ /g, '_')
|
||||
let sanitized = withUnderscores
|
||||
.split('')
|
||||
.filter((char, index) => {
|
||||
if (index === 0)
|
||||
return /^[a-z_]$/i.test(char)
|
||||
return /^\w$/.test(char)
|
||||
})
|
||||
.join('')
|
||||
|
||||
if (sanitized !== withUnderscores)
|
||||
Toast.notify({ type: 'error', message: t(`${i18nPrefix}.userActions.invalidActionIdFormat`, { ns: 'workflow' }) })
|
||||
|
||||
// Limit to 20 characters
|
||||
if (sanitized.length > ACTION_ID_MAX_LENGTH) {
|
||||
sanitized = sanitized.slice(0, ACTION_ID_MAX_LENGTH)
|
||||
Toast.notify({ type: 'error', message: t(`${i18nPrefix}.userActions.actionIdTooLong`, { ns: 'workflow' }) })
|
||||
}
|
||||
|
||||
if (sanitized)
|
||||
onChange({ ...data, id: sanitized })
|
||||
}
|
||||
|
||||
const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.value.trim())
|
||||
onChange({ ...data, title: 'Button Text' })
|
||||
else
|
||||
onChange({ ...data, title: e.target.value })
|
||||
let value = e.target.value
|
||||
if (value.length > BUTTON_TEXT_MAX_LENGTH) {
|
||||
value = value.slice(0, BUTTON_TEXT_MAX_LENGTH)
|
||||
Toast.notify({ type: 'error', message: t(`${i18nPrefix}.userActions.buttonTextTooLong`, { ns: 'workflow' }) })
|
||||
}
|
||||
onChange({ ...data, title: value })
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -42,6 +42,25 @@ const nodeDefault: NodeDefault<HumanInputNodeType> = {
|
|||
if (!errorMessages && !payload.user_actions.length)
|
||||
errorMessages = t(`${i18nPrefix}.noUserActions`, { ns: 'workflow' })
|
||||
|
||||
if (!errorMessages && payload.user_actions.length > 0) {
|
||||
const actionIds = payload.user_actions.map(action => action.id)
|
||||
const hasDuplicateIds = actionIds.length !== new Set(actionIds).size
|
||||
if (hasDuplicateIds)
|
||||
errorMessages = t(`${i18nPrefix}.duplicateActionId`, { ns: 'workflow' })
|
||||
}
|
||||
|
||||
if (!errorMessages && payload.user_actions.length > 0) {
|
||||
const hasEmptyId = payload.user_actions.some(action => !action.id?.trim())
|
||||
if (hasEmptyId)
|
||||
errorMessages = t(`${i18nPrefix}.emptyActionId`, { ns: 'workflow' })
|
||||
}
|
||||
|
||||
if (!errorMessages && payload.user_actions.length > 0) {
|
||||
const hasEmptyTitle = payload.user_actions.some(action => !action.title?.trim())
|
||||
if (hasEmptyTitle)
|
||||
errorMessages = t(`${i18nPrefix}.emptyActionTitle`, { ns: 'workflow' })
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: !errorMessages,
|
||||
errorMessage: errorMessages,
|
||||
|
|
|
|||
|
|
@ -558,6 +558,9 @@
|
|||
"nodes.humanInput.deliveryMethod.types.webapp.description": "Display to end-user in webapp",
|
||||
"nodes.humanInput.deliveryMethod.types.webapp.title": "Webapp",
|
||||
"nodes.humanInput.editor.previewTip": "In preview mode, action buttons are not functional.",
|
||||
"nodes.humanInput.errorMsg.duplicateActionId": "Duplicate action ID found in user actions",
|
||||
"nodes.humanInput.errorMsg.emptyActionId": "Action ID cannot be empty",
|
||||
"nodes.humanInput.errorMsg.emptyActionTitle": "Action title cannot be empty",
|
||||
"nodes.humanInput.errorMsg.noDeliveryMethod": "Please select at least one delivery method",
|
||||
"nodes.humanInput.errorMsg.noDeliveryMethodEnabled": "Please enable at least one delivery method",
|
||||
"nodes.humanInput.errorMsg.noUserActions": "Please add at least one user action",
|
||||
|
|
@ -586,10 +589,13 @@
|
|||
"nodes.humanInput.timeout.days": "Days",
|
||||
"nodes.humanInput.timeout.hours": "Hours",
|
||||
"nodes.humanInput.timeout.title": "Timeout",
|
||||
"nodes.humanInput.userActions.actionIdTooLong": "Action ID must be 20 characters or less",
|
||||
"nodes.humanInput.userActions.actionNamePlaceholder": "Action Name",
|
||||
"nodes.humanInput.userActions.buttonTextPlaceholder": "Button display Text",
|
||||
"nodes.humanInput.userActions.buttonTextTooLong": "Button text must be 40 characters or less",
|
||||
"nodes.humanInput.userActions.chooseStyle": "Choose a button style",
|
||||
"nodes.humanInput.userActions.emptyTip": "Click the '+' button to add user actions",
|
||||
"nodes.humanInput.userActions.invalidActionIdFormat": "Action ID must start with a letter or underscore, followed by letters, numbers, or underscores",
|
||||
"nodes.humanInput.userActions.title": "User Actions",
|
||||
"nodes.humanInput.userActions.tooltip": "Define buttons that users can click to respond to this form. Each button can trigger different workflow paths.",
|
||||
"nodes.humanInput.userActions.triggered": "<strong>{{actionName}}</strong> has been triggered",
|
||||
|
|
|
|||
|
|
@ -558,6 +558,9 @@
|
|||
"nodes.humanInput.deliveryMethod.types.webapp.description": "在 Web 应用中显示给最终用户",
|
||||
"nodes.humanInput.deliveryMethod.types.webapp.title": "Webapp",
|
||||
"nodes.humanInput.editor.previewTip": "在预览模式下,操作按钮无法使用。",
|
||||
"nodes.humanInput.errorMsg.duplicateActionId": "用户操作中存在重复的操作 ID",
|
||||
"nodes.humanInput.errorMsg.emptyActionId": "操作 ID 不能为空",
|
||||
"nodes.humanInput.errorMsg.emptyActionTitle": "操作标题不能为空",
|
||||
"nodes.humanInput.errorMsg.noDeliveryMethod": "请至少选择一种提交方式",
|
||||
"nodes.humanInput.errorMsg.noDeliveryMethodEnabled": "请至少启用一种提交方式",
|
||||
"nodes.humanInput.errorMsg.noUserActions": "请添加至少一个用户操作",
|
||||
|
|
@ -586,10 +589,13 @@
|
|||
"nodes.humanInput.timeout.days": "日",
|
||||
"nodes.humanInput.timeout.hours": "小时",
|
||||
"nodes.humanInput.timeout.title": "超时设置",
|
||||
"nodes.humanInput.userActions.actionIdTooLong": "操作 ID 不能超过 20 个字符",
|
||||
"nodes.humanInput.userActions.actionNamePlaceholder": "操作名称",
|
||||
"nodes.humanInput.userActions.buttonTextPlaceholder": "按钮显示文本",
|
||||
"nodes.humanInput.userActions.buttonTextTooLong": "按钮文本不能超过 40 个字符",
|
||||
"nodes.humanInput.userActions.chooseStyle": "选择按钮样式",
|
||||
"nodes.humanInput.userActions.emptyTip": "点击 '+' 按钮添加用户操作",
|
||||
"nodes.humanInput.userActions.invalidActionIdFormat": "操作 ID 必须以字母或下划线开头,后跟字母、数字或下划线",
|
||||
"nodes.humanInput.userActions.title": "用户操作",
|
||||
"nodes.humanInput.userActions.tooltip": "定义用户可以点击以响应此表单的按钮。每个按钮都可以触发不同的工作流路径。",
|
||||
"nodes.humanInput.userActions.triggered": "已触发<strong>{{actionName}}</strong>",
|
||||
|
|
|
|||
Loading…
Reference in New Issue