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:
twwu 2026-01-16 15:31:13 +08:00
parent a298140d8f
commit bd634b165d
6 changed files with 70 additions and 32 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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