diff --git a/web/app/components/workflow/nodes/human-input/components/button-style-dropdown.tsx b/web/app/components/workflow/nodes/human-input/components/button-style-dropdown.tsx new file mode 100644 index 0000000000..269b147cdf --- /dev/null +++ b/web/app/components/workflow/nodes/human-input/components/button-style-dropdown.tsx @@ -0,0 +1,108 @@ +import type { FC } from 'react' +import React, { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + RiFontSize, +} from '@remixicon/react' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Button from '@/app/components/base/button' +import { UserActionButtonType } from '../types' +import cn from '@/utils/classnames' + +const i18nPrefix = 'workflow.nodes.humanInput' + +type Props = { + text: string + data: UserActionButtonType + onChange: (state: UserActionButtonType) => void +} + +const ButtonStyleDropdown: FC = ({ + text = 'Button Text', + data, + onChange, +}) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const currentStyle = useMemo(() => { + switch (data) { + case UserActionButtonType.Primary: + return 'primary' + case UserActionButtonType.Default: + return 'secondary' + case UserActionButtonType.Accent: + return 'secondary-accent' + default: + return 'ghost' + } + }, [data]) + + return ( + + setOpen(v => !v)}> +
+ +
+
+ +
+
{t(`${i18nPrefix}.userActions.chooseStyle`)}
+
+
onChange(UserActionButtonType.Primary)} + > + +
+
onChange(UserActionButtonType.Default)} + > + +
+
onChange(UserActionButtonType.Accent)} + > + +
+
onChange(UserActionButtonType.Ghost)} + > + +
+
+
+
+
+ ) +} + +export default ButtonStyleDropdown diff --git a/web/app/components/workflow/nodes/human-input/components/user-action.tsx b/web/app/components/workflow/nodes/human-input/components/user-action.tsx new file mode 100644 index 0000000000..0370fb331e --- /dev/null +++ b/web/app/components/workflow/nodes/human-input/components/user-action.tsx @@ -0,0 +1,59 @@ +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { + RiDeleteBinLine, +} from '@remixicon/react' +import type { UserAction } from '../types' +import Input from '@/app/components/base/input' +import Button from '@/app/components/base/button' +import ButtonStyleDropdown from './button-style-dropdown' + +const i18nPrefix = 'workflow.nodes.humanInput' + +type Props = { + data: UserAction + onChange: (state: UserAction) => void + onDelete: (id: string) => void +} + +const UserActionItem: FC = ({ + data, + onChange, + onDelete, +}) => { + const { t } = useTranslation() + return ( +
+
+ onChange({ ...data, name: e.target.value })} + /> +
+
+ onChange({ ...data, text: e.target.value })} + /> +
+ onChange({ ...data, type })} + /> + +
+ ) +} + +export default UserActionItem diff --git a/web/app/components/workflow/nodes/human-input/default.ts b/web/app/components/workflow/nodes/human-input/default.ts index 4b702f05c6..5bb792cd47 100644 --- a/web/app/components/workflow/nodes/human-input/default.ts +++ b/web/app/components/workflow/nodes/human-input/default.ts @@ -17,21 +17,25 @@ const nodeDefault: NodeDefault = { ], userActions: [ { + id: 'approve-action', name: 'approve', text: 'Post to X', type: UserActionButtonType.Primary, }, { + id: 'regenerate-action', name: 'regenerate', text: 'regenerate', type: UserActionButtonType.Default, }, { + id: 'thinking-action', name: 'thinking', text: 'think more', type: UserActionButtonType.Accent, }, { + id: 'cancel-action', name: 'cancel', text: 'cancel', type: UserActionButtonType.Ghost, diff --git a/web/app/components/workflow/nodes/human-input/node.tsx b/web/app/components/workflow/nodes/human-input/node.tsx index f3278e244a..15298b1711 100644 --- a/web/app/components/workflow/nodes/human-input/node.tsx +++ b/web/app/components/workflow/nodes/human-input/node.tsx @@ -46,7 +46,7 @@ const Node: FC> = (props) => { {userActions.length > 0 && (
{userActions.map(userAction => ( -
+
{userAction.name} > = ({ const { t } = useTranslation() const { inputs, + handleUserActionChange, + handleUserActionDelete, handleTimeoutChange, } = useConfig(id, data) return (
+
+
+
+
{t(`${i18nPrefix}.userActions.title`)}
+ +
+
+ { + inputs.userActions.push({ + id: uuid4(), + name: 'Action', + text: 'Button Text', + type: UserActionButtonType.Default, + }) + }} + > + + +
+
+ {!inputs.userActions.length && ( +
{t(`${i18nPrefix}.userActions.emptyTip`)}
+ )} + {inputs.userActions.length > 0 && ( +
+ {inputs.userActions.map(action => ( + + ))} +
+ )} +
diff --git a/web/app/components/workflow/nodes/human-input/types.ts b/web/app/components/workflow/nodes/human-input/types.ts index 6ccad31eb9..4960c523d2 100644 --- a/web/app/components/workflow/nodes/human-input/types.ts +++ b/web/app/components/workflow/nodes/human-input/types.ts @@ -33,6 +33,7 @@ export enum UserActionButtonType { } export type UserAction = { + id: string name: string text: string type: UserActionButtonType diff --git a/web/app/components/workflow/nodes/human-input/use-config.ts b/web/app/components/workflow/nodes/human-input/use-config.ts index ae99b46013..148c4d5c83 100644 --- a/web/app/components/workflow/nodes/human-input/use-config.ts +++ b/web/app/components/workflow/nodes/human-input/use-config.ts @@ -1,4 +1,5 @@ -import type { HumanInputNodeType, Timeout } from './types' +import produce from 'immer' +import type { HumanInputNodeType, Timeout, UserAction } from './types' import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud' import { useNodesReadOnly, @@ -7,6 +8,26 @@ const useConfig = (id: string, payload: HumanInputNodeType) => { const { nodesReadOnly: readOnly } = useNodesReadOnly() const { inputs, setInputs } = useNodeCrud(id, payload) + const handleUserActionChange = (updatedAction: UserAction) => { + const newActions = produce(inputs.userActions, (draft) => { + const index = draft.findIndex(a => a.id === updatedAction.id) + if (index !== -1) + draft[index] = updatedAction + }) + setInputs({ + ...inputs, + userActions: newActions, + }) + } + + const handleUserActionDelete = (actionId: string) => { + const newActions = inputs.userActions.filter(action => action.id !== actionId) + setInputs({ + ...inputs, + userActions: newActions, + }) + } + const handleTimeoutChange = (timeout: Timeout) => { setInputs({ ...inputs, @@ -17,6 +38,8 @@ const useConfig = (id: string, payload: HumanInputNodeType) => { return { readOnly, inputs, + handleUserActionChange, + handleUserActionDelete, handleTimeoutChange, } } diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 56516bf13d..dfe78e1758 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -909,7 +909,14 @@ const translation = { title: 'Delivery Method', }, formContent: 'form content', - userActions: 'user actions', + userActions: { + title: 'User Actions', + tooltip: 'Define buttons that users can click to respond to this form. Each button can trigger different workflow paths.', + emptyTip: 'Click the \'+\' button to add user actions', + actionNamePlaceholder: 'Action Name', + buttonTextPlaceholder: 'Button display Text', + chooseStyle: 'Choose a button style', + }, timeout: { title: 'Timeout', hours: 'Hours', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index ae8a7000e4..c383dcf6d9 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -910,7 +910,14 @@ const translation = { title: '提交方式', }, formContent: '表单内容', - userActions: '用户操作', + userActions: { + title: '用户操作', + tooltip: '定义用户可以点击以响应此表单的按钮。每个按钮都可以触发不同的工作流路径。', + emptyTip: '点击 \'+\' 按钮添加用户操作', + actionNamePlaceholder: '操作名称', + buttonTextPlaceholder: '按钮显示文本', + chooseStyle: '选择按钮样式', + }, timeout: { title: '超时设置', hours: '小时',