diff --git a/web/app/components/app/configuration/config-var/config-modal/field.tsx b/web/app/components/app/configuration/config-var/config-modal/field.tsx new file mode 100644 index 0000000000..8253178486 --- /dev/null +++ b/web/app/components/app/configuration/config-var/config-modal/field.tsx @@ -0,0 +1,21 @@ +'use client' +import type { FC } from 'react' +import React from 'react' + +type Props = { + title: string + children: JSX.Element +} + +const Field: FC = ({ + title, + children, +}) => { + return ( +
+
{title}
+
{children}
+
+ ) +} +export default React.memo(Field) diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx index b923ce72f2..b7795d7f7b 100644 --- a/web/app/components/app/configuration/config-var/config-modal/index.tsx +++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx @@ -1,30 +1,35 @@ 'use client' import type { FC } from 'react' -import React, { useEffect, useState } from 'react' +import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import ModalFoot from '../modal-foot' -import type { Options } from '../config-select' import ConfigSelect from '../config-select' import ConfigString from '../config-string' import SelectTypeItem from '../select-type-item' -import s from './style.module.css' +import Field from './field' import Toast from '@/app/components/base/toast' -import type { PromptVariable } from '@/models/debug' -import { getNewVar } from '@/utils/var' +import { getNewVarInWorkflow } from '@/utils/var' import ConfigContext from '@/context/debug-configuration' +import { type InputVar, InputVarType } from '@/app/components/workflow/types' import Modal from '@/app/components/base/modal' +import Switch from '@/app/components/base/switch' export type IConfigModalProps = { - payload: PromptVariable + isCreate?: boolean + payload?: InputVar type?: string isShow: boolean onClose: () => void - onConfirm: (newValue: { type: string; value: any }) => void + // onConfirm: (newValue: { type: string; value: any }) => void + onConfirm: (newValue: InputVar) => void } +const inputClassName = 'w-full px-3 text-sm leading-9 text-gray-900 border-0 rounded-lg grow h-9 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200' + const ConfigModal: FC = ({ + isCreate, payload, isShow, onClose, @@ -32,45 +37,36 @@ const ConfigModal: FC = ({ }) => { const { modelConfig } = useContext(ConfigContext) const { t } = useTranslation() - const { type, name, key, options, max_length } = payload || getNewVar('') + const [tempPayload, setTempPayload] = useState(payload || getNewVarInWorkflow('') as any) + // const { type, name, key, options, max_length } = tempPayload; name => label; variable => key + const { type, label, variable, options, max_length } = tempPayload - const [tempType, setTempType] = useState(type) - useEffect(() => { - setTempType(type) - }, [type]) - const handleTypeChange = (type: string) => { - return () => { - setTempType(type) + const isStringInput = type === InputVarType.textInput || type === InputVarType.paragraph + + const handlePayloadChange = useCallback((key: string) => { + return (value: any) => { + setTempPayload((prev) => { + return { + ...prev, + [key]: value, + } + }) } - } - - const isStringInput = tempType === 'string' || tempType === 'paragraph' - const title = isStringInput ? t('appDebug.variableConig.maxLength') : t('appDebug.variableConig.options') - - // string type - const [tempMaxLength, setTempMaxValue] = useState(max_length) - useEffect(() => { - setTempMaxValue(max_length) - }, [max_length]) - - // select type - const [tempOptions, setTempOptions] = useState(options || []) - useEffect(() => { - setTempOptions(options || []) - }, [options]) + }, []) const handleConfirm = () => { if (isStringInput) { - onConfirm({ type: tempType, value: tempMaxLength }) + onConfirm(tempPayload) + // onConfirm({ type: type, value: tempMaxLength }) } else { - if (tempOptions.length === 0) { + if (options?.length === 0) { Toast.notify({ type: 'error', message: 'At least one option requied' }) return } const obj: Record = {} let hasRepeatedItem = false - tempOptions.forEach((o) => { + options?.forEach((o) => { if (obj[o]) { hasRepeatedItem = true return @@ -81,39 +77,62 @@ const ConfigModal: FC = ({ Toast.notify({ type: 'error', message: 'Has repeat items' }) return } - onConfirm({ type: tempType, value: tempOptions }) + onConfirm(tempPayload) } } return (
-
{t('appDebug.variableConig.description', { varName: `{{${name || key}}}` })}
-
-
{t('appDebug.variableConig.fieldType')}
-
- - - -
-
+
- {tempType !== 'paragraph' && ( -
-
{title}
- {isStringInput - ? ( - - ) - : ( - - )} -
- )} + +
+ {/* TODO handlePayloadChange(string) */} + handlePayloadChange('type')(InputVarType.textInput)} /> + handlePayloadChange('type')(InputVarType.paragraph)} /> + handlePayloadChange('type')(InputVarType.select)} /> +
+
+ + + handlePayloadChange('variable')(e.target.value)} + /> + + + handlePayloadChange('label')(e.target.value)} + /> + + + {isStringInput && ( + + + + + )} + {type === InputVarType.select && ( + + + + )} + + + + +
= ({ promptVariables, readonly, onPromptVar } const batchUpdatePromptVariable = (key: string, updateKeys: string[], newValues: any[], isParagraph?: boolean) => { + console.log(key) const newPromptVariables = promptVariables.map((item) => { if (item.key === key) { const newItem: any = { ...item } @@ -111,7 +114,7 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar }) conflictTimer = setTimeout(() => { - const isKeyExists = promptVariables.some(item => item.key.trim() === newKey.trim()) + const isKeyExists = promptVariables.some(item => item.key?.trim() === newKey.trim()) if (isKeyExists) { Toast.notify({ type: 'error', @@ -242,6 +245,17 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar const [currKey, setCurrKey] = useState(null) const currItem = currKey ? promptVariables.find(item => item.key === currKey) : null + const currItemToEdit: InputVar | null = (() => { + if (!currItem) + return null + + return { + ...currItem, + label: currItem.name, + variable: currItem.key, + type: currItem.type === 'string' ? InputVarType.textInput : currItem.type, + } as InputVar + })() const [isShowEditModal, { setTrue: showEditModal, setFalse: hideEditModal }] = useBoolean(false) const handleConfig = ({ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams) => { @@ -297,6 +311,7 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar
+ {type} {!readonly ? ( @@ -358,14 +373,17 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar {isShowEditModal && ( { - if (type === 'string') - batchUpdatePromptVariable(currKey as string, ['type', 'max_length'], [type, value || DEFAULT_VALUE_MAX_LEN]) + onConfirm={(item) => { + const { type, max_length, options } = item + if (type === InputVarType.textInput) + batchUpdatePromptVariable(currKey as string, ['type', 'max_length'], ['string', max_length || DEFAULT_VALUE_MAX_LEN]) + if (type === InputVarType.paragraph) + batchUpdatePromptVariable(currKey as string, ['type', 'max_length'], [InputVarType.paragraph, max_length || DEFAULT_VALUE_MAX_LEN], true) else - batchUpdatePromptVariable(currKey as string, ['type', 'options'], [type, value || []], type === 'paragraph') + batchUpdatePromptVariable(currKey as string, ['type', 'options'], [type, options || []], false) hideEditModal() }} diff --git a/web/app/components/app/configuration/config-var/select-type-item/index.tsx b/web/app/components/app/configuration/config-var/select-type-item/index.tsx index dc4ea1aae4..1ecd9bd99a 100644 --- a/web/app/components/app/configuration/config-var/select-type-item/index.tsx +++ b/web/app/components/app/configuration/config-var/select-type-item/index.tsx @@ -3,18 +3,18 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' import cn from 'classnames' - import s from './style.module.css' +import { InputVarType } from '@/app/components/workflow/types' export type ISelectTypeItemProps = { - type: string + type: InputVarType selected: boolean onClick: () => void } const Icon = ({ type, selected }: Partial) => { switch (type) { - case 'select': + case InputVarType.select: return selected ? ( @@ -28,16 +28,16 @@ const Icon = ({ type, selected }: Partial) => { ) - case 'paragraph': + case InputVarType.paragraph: return selected ? ( - - - - + + + + @@ -46,15 +46,15 @@ const Icon = ({ type, selected }: Partial) => { - - - - + + + + ) - case 'string': + case InputVarType.textInput: default: return selected ? ( diff --git a/web/app/components/workflow/nodes/start/panel.tsx b/web/app/components/workflow/nodes/start/panel.tsx index 6749c3852f..2324658f99 100644 --- a/web/app/components/workflow/nodes/start/panel.tsx +++ b/web/app/components/workflow/nodes/start/panel.tsx @@ -7,6 +7,7 @@ import Split from '@/app/components/workflow/nodes/_base/components/split' import Field from '@/app/components/workflow/nodes/_base/components/field' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import AddButton from '@/app/components/base/button/add-button' +import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal' const i18nPrefix = 'workflow.nodes.start' @@ -15,6 +16,10 @@ const Panel: FC = () => { const readOnly = false const { inputs, + isShowAddVarModal, + showAddVarModal, + handleAddVariable, + hideAddVarModal, handleVarListChange, } = useConfig(mockData) @@ -24,7 +29,7 @@ const Panel: FC = () => { { }} /> + } > {
+ {isShowAddVarModal && ( + { + handleAddVariable(payload) + hideAddVarModal() + }} + /> + )} ) } diff --git a/web/app/components/workflow/nodes/start/use-config.ts b/web/app/components/workflow/nodes/start/use-config.ts index 3d96f418f7..278ec2ee23 100644 --- a/web/app/components/workflow/nodes/start/use-config.ts +++ b/web/app/components/workflow/nodes/start/use-config.ts @@ -1,11 +1,17 @@ import { useCallback, useState } from 'react' import produce from 'immer' +import { useBoolean } from 'ahooks' import type { StartNodeType } from './types' import type { InputVar } from '@/app/components/workflow/types' const useConfig = (initInputs: StartNodeType) => { const [inputs, setInputs] = useState(initInputs) + const [isShowAddVarModal, { + setTrue: showAddVarModal, + setFalse: hideAddVarModal, + }] = useBoolean(true) + const handleVarListChange = useCallback((newList: InputVar[]) => { const newInputs = produce(inputs, (draft: any) => { draft.variables = newList @@ -21,6 +27,9 @@ const useConfig = (initInputs: StartNodeType) => { }, [inputs, setInputs]) return { inputs, + isShowAddVarModal, + showAddVarModal, + hideAddVarModal, handleVarListChange, handleAddVariable, } diff --git a/web/config/index.ts b/web/config/index.ts index b6b6eb3ee7..fb44606888 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -1,4 +1,5 @@ /* eslint-disable import/no-mutable-exports */ +import { InputVarType } from '@/app/components/workflow/types' import { AgentStrategy } from '@/types/app' export let apiPrefix = '' @@ -115,6 +116,15 @@ export const VAR_ITEM_TEMPLATE = { required: true, } +export const VAR_ITEM_TEMPLATE_IN_WORKFLOW = { + variable: '', + label: '', + type: InputVarType.textInput, + max_length: DEFAULT_VALUE_MAX_LEN, + required: true, + options: [], +} + export const appDefaultIconBackground = '#D5F5F6' export const NEED_REFRESH_APP_LIST_KEY = 'needRefreshAppList' diff --git a/web/i18n/lang/app-debug.en.ts b/web/i18n/lang/app-debug.en.ts index 82f9ae7926..3273c8eb33 100644 --- a/web/i18n/lang/app-debug.en.ts +++ b/web/i18n/lang/app-debug.en.ts @@ -266,18 +266,23 @@ const translation = { queryNoBeEmpty: 'Query must be set in the prompt', }, variableConig: { - modalTitle: 'Field settings', - description: 'Setting for variable {{varName}}', - fieldType: 'Field type', - string: 'Short Text', - paragraph: 'Paragraph', - select: 'Select', - notSet: 'Not set, try typing {{input}} in the prefix prompt', - stringTitle: 'Form text box options', - maxLength: 'Max length', - options: 'Options', - addOption: 'Add option', - apiBasedVar: 'API-based Variable', + 'addModalTitle': 'Add Input Field', + 'editModalTitle': 'Edit Input Field', + 'description': 'Setting for variable {{varName}}', + 'fieldType': 'Field type', + 'string': 'Short Text', + 'text-input': 'Short Text', + 'paragraph': 'Paragraph', + 'select': 'Select', + 'notSet': 'Not set, try typing {{input}} in the prefix prompt', + 'stringTitle': 'Form text box options', + 'maxLength': 'Max length', + 'options': 'Options', + 'addOption': 'Add option', + 'apiBasedVar': 'API-based Variable', + 'varName': 'Variable Name', + 'labelName': 'Label Name', + 'required': 'Required', }, vision: { name: 'Vision', diff --git a/web/i18n/lang/app-debug.pt.ts b/web/i18n/lang/app-debug.pt.ts index 4d706cc38c..160952bd17 100644 --- a/web/i18n/lang/app-debug.pt.ts +++ b/web/i18n/lang/app-debug.pt.ts @@ -266,18 +266,19 @@ const translation = { queryNoBeEmpty: 'A consulta deve ser definida na solicitação', }, variableConig: { - modalTitle: 'Configurações do Campo', - description: 'Configuração para a variável {{varName}}', - fieldType: 'Tipo de Campo', - string: 'Texto Curto', - paragraph: 'Parágrafo', - select: 'Selecionar', - notSet: 'Não definido, tente digitar {{input}} na solicitação', - stringTitle: 'Opções da Caixa de Texto do Formulário', - maxLength: 'Comprimento Máximo', - options: 'Opções', - addOption: 'Adicionar opção', - apiBasedVar: 'Variável Baseada em API', + 'addModalTitle': 'Configurações do Campo', + 'description': 'Configuração para a variável {{varName}}', + 'fieldType': 'Tipo de Campo', + 'string': 'Texto Curto', + 'text-input': 'Texto Curto', + 'paragraph': 'Parágrafo', + 'select': 'Selecionar', + 'notSet': 'Não definido, tente digitar {{input}} na solicitação', + 'stringTitle': 'Opções da Caixa de Texto do Formulário', + 'maxLength': 'Comprimento Máximo', + 'options': 'Opções', + 'addOption': 'Adicionar opção', + 'apiBasedVar': 'Variável Baseada em API', }, vision: { name: 'Visão', diff --git a/web/i18n/lang/app-debug.uk.ts b/web/i18n/lang/app-debug.uk.ts index d9543e03cf..8db3b9b1ee 100644 --- a/web/i18n/lang/app-debug.uk.ts +++ b/web/i18n/lang/app-debug.uk.ts @@ -260,18 +260,19 @@ const translation = { queryNoBeEmpty: 'Запит має бути встановлений у підказці', // Query must be set in the prompt }, variableConig: { - modalTitle: 'Налаштування поля', // Field settings - description: 'Налаштування для змінної {{varName}}', // Setting for variable {{varName}} - fieldType: 'Тип поля', // Field type - string: 'Короткий текст', // Short Text - paragraph: 'Абзац', // Paragraph - select: 'Вибрати', // Select - notSet: 'Не налаштовано, спробуйте ввести {{input}} у префіксну підказку', // Not set, try typing {{input}} in the prefix prompt - stringTitle: 'Опції текстового поля форми', // Form text box options - maxLength: 'Максимальна довжина', // Max length - options: 'Опції', // Options - addOption: 'Додати опцію', // Add option - apiBasedVar: 'Змінна на основі API', // API-based Variable + 'addModalTitle': 'Налаштування поля', // Field settings + 'description': 'Налаштування для змінної {{varName}}', // Setting for variable {{varName}} + 'fieldType': 'Тип поля', // Field type + 'string': 'Короткий текст', // Short Text + 'text-input': 'Короткий текст', // Short Text + 'paragraph': 'Абзац', // Paragraph + 'select': 'Вибрати', // Select + 'notSet': 'Не налаштовано, спробуйте ввести {{input}} у префіксну підказку', // Not set, try typing {{input}} in the prefix prompt + 'stringTitle': 'Опції текстового поля форми', // Form text box options + 'maxLength': 'Максимальна довжина', // Max length + 'options': 'Опції', // Options + 'addOption': 'Додати опцію', // Add option + 'apiBasedVar': 'Змінна на основі API', // API-based Variable }, vision: { name: 'Зображення', // Vision diff --git a/web/i18n/lang/app-debug.zh.ts b/web/i18n/lang/app-debug.zh.ts index 6df5d833d1..d41cac24b0 100644 --- a/web/i18n/lang/app-debug.zh.ts +++ b/web/i18n/lang/app-debug.zh.ts @@ -248,6 +248,9 @@ const translation = { action: '操作', typeString: '文本', typeSelect: '下拉选项', + varName: '变量名称', + labelName: '显示名称', + required: '必填', }, varKeyError: { canNoBeEmpty: '变量不能为空', @@ -262,18 +265,20 @@ const translation = { queryNoBeEmpty: '提示词中必须设置查询内容', }, variableConig: { - modalTitle: '变量设置', - description: '设置变量 {{varName}}', - fieldType: '字段类型', - string: '文本', - paragraph: '段落', - select: '下拉选项', - notSet: '未设置,在 Prompt 中输入 {{input}} 试试', - stringTitle: '文本框设置', - maxLength: '最大长度', - options: '选项', - addOption: '添加选项', - apiBasedVar: '基于 API 的变量', + 'addModalTitle': '添加变量', + 'editModalTitle': '编辑变量', + 'description': '设置变量 {{varName}}', + 'fieldType': '字段类型', + 'string': '文本', + 'text-input': '文本', + 'paragraph': '段落', + 'select': '下拉选项', + 'notSet': '未设置,在 Prompt 中输入 {{input}} 试试', + 'stringTitle': '文本框设置', + 'maxLength': '最大长度', + 'options': '选项', + 'addOption': '添加选项', + 'apiBasedVar': '基于 API 的变量', }, vision: { name: '视觉', diff --git a/web/utils/var.ts b/web/utils/var.ts index b2a7871ad1..384689af7b 100644 --- a/web/utils/var.ts +++ b/web/utils/var.ts @@ -1,5 +1,6 @@ -import { MAX_VAR_KEY_LENGHT, VAR_ITEM_TEMPLATE, getMaxVarNameLength } from '@/config' +import { MAX_VAR_KEY_LENGHT, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW, getMaxVarNameLength } from '@/config' import { CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT } from '@/app/components/base/prompt-editor/constants' +import { InputVarType } from '@/app/components/workflow/types' const otherAllowedRegex = /^[a-zA-Z0-9_]+$/ @@ -21,6 +22,24 @@ export const getNewVar = (key: string, type: string) => { } } +export const getNewVarInWorkflow = (key: string, type = InputVarType.textInput) => { + const { max_length, ...rest } = VAR_ITEM_TEMPLATE_IN_WORKFLOW + if (type !== InputVarType.textInput) { + return { + ...rest, + type, + variable: key, + label: key.slice(0, getMaxVarNameLength(key)), + } + } + return { + ...VAR_ITEM_TEMPLATE_IN_WORKFLOW, + type, + variable: key, + label: key.slice(0, getMaxVarNameLength(key)), + } +} + const checkKey = (key: string, canBeEmpty?: boolean) => { if (key.length === 0 && !canBeEmpty) return 'canNoBeEmpty'