mirror of
https://github.com/langgenius/dify.git
synced 2026-03-11 10:17:50 +08:00
new mixed input
This commit is contained in:
@ -7,7 +7,7 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import MixedInput from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||
import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/components/mixed-variable-text-input'
|
||||
import Input from '@/app/components/base/input'
|
||||
import FormInputTypeSwitch from '@/app/components/workflow/nodes/_base/components/form-input-type-switch'
|
||||
import FormInputBoolean from '@/app/components/workflow/nodes/_base/components/form-input-boolean'
|
||||
@ -60,18 +60,6 @@ const ReasoningConfigForm: React.FC<Props> = ({
|
||||
return VarKindType.mixed
|
||||
}
|
||||
|
||||
const [inputsIsFocus, setInputsIsFocus] = useState<Record<string, boolean>>({})
|
||||
const handleInputFocus = useCallback((variable: string) => {
|
||||
return (value: boolean) => {
|
||||
setInputsIsFocus((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[variable]: value,
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleAutomatic = (key: string, val: any, type: FormTypeEnum) => {
|
||||
onChange({
|
||||
...value,
|
||||
@ -259,15 +247,11 @@ const ReasoningConfigForm: React.FC<Props> = ({
|
||||
<FormInputTypeSwitch value={varInput?.type || VarKindType.constant} onChange={handleTypeChange(variable, defaultValue)}/>
|
||||
)}
|
||||
{isString && (
|
||||
<MixedInput
|
||||
className={cn(inputsIsFocus[variable] ? 'border-gray-300 bg-gray-50 shadow-xs' : 'border-gray-100 bg-gray-100', 'grow rounded-lg border px-3 py-[6px]')}
|
||||
<MixedVariableTextInput
|
||||
value={varInput?.value as string || ''}
|
||||
onChange={handleValueChange(variable, type)}
|
||||
nodesOutputVars={nodeOutputVars}
|
||||
availableNodes={availableNodes}
|
||||
onFocusChange={handleInputFocus(variable)}
|
||||
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
|
||||
placeholderClassName='!leading-[21px]'
|
||||
/>
|
||||
)}
|
||||
{isNumber && isConstant && (
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types'
|
||||
import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
@ -11,17 +9,17 @@ import { VarType } from '@/app/components/workflow/types'
|
||||
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import FormInputTypeSwitch from './form-input-type-switch'
|
||||
import MixedInput from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { SimpleSelect } from '@/app/components/base/select'
|
||||
import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/components/mixed-variable-text-input'
|
||||
import FormInputBoolean from './form-input-boolean'
|
||||
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
|
||||
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
|
||||
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
|
||||
import cn from '@/utils/classnames'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
readOnly: boolean
|
||||
@ -40,7 +38,6 @@ const FormInputItem: FC<Props> = ({
|
||||
onChange,
|
||||
inPanel,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useLanguage()
|
||||
|
||||
const {
|
||||
@ -178,34 +175,18 @@ const FormInputItem: FC<Props> = ({
|
||||
})
|
||||
}
|
||||
|
||||
const [inputsIsFocus, setInputsIsFocus] = useState<Record<string, boolean>>({})
|
||||
const handleInputFocus = useCallback((variable: string) => {
|
||||
return (value: boolean) => {
|
||||
setInputsIsFocus((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[variable]: value,
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={cn('gap-1', !(isShowJSONEditor && isConstant) && 'flex')}>
|
||||
{showTypeSwitch && (
|
||||
<FormInputTypeSwitch value={varInput?.type || VarKindType.constant} onChange={handleTypeChange}/>
|
||||
)}
|
||||
{isString && (
|
||||
<MixedInput
|
||||
className={cn(inputsIsFocus[variable] ? 'border-gray-300 bg-gray-50 shadow-xs' : 'border-gray-100 bg-gray-100', 'grow rounded-lg border px-3 py-[6px]')}
|
||||
<MixedVariableTextInput
|
||||
readOnly={readOnly}
|
||||
value={varInput?.value as string || ''}
|
||||
onChange={handleValueChange}
|
||||
readOnly={readOnly}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
onFocusChange={handleInputFocus(variable)}
|
||||
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
|
||||
placeholderClassName='!leading-[21px]'
|
||||
/>
|
||||
)}
|
||||
{isNumber && isConstant && (
|
||||
|
||||
@ -1,247 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import produce from 'immer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ToolVarInputs } from '../types'
|
||||
import { VarType as VarKindType } from '../types'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
|
||||
import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var'
|
||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
|
||||
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
|
||||
import { noop } from 'lodash-es'
|
||||
|
||||
type Props = {
|
||||
readOnly: boolean
|
||||
nodeId: string
|
||||
schema: CredentialFormSchema[]
|
||||
value: ToolVarInputs
|
||||
onChange: (value: ToolVarInputs) => void
|
||||
onOpen?: (index: number) => void
|
||||
isSupportConstantValue?: boolean
|
||||
filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean
|
||||
}
|
||||
|
||||
const InputVarList: FC<Props> = ({
|
||||
readOnly,
|
||||
nodeId,
|
||||
schema,
|
||||
value,
|
||||
onChange,
|
||||
onOpen = noop,
|
||||
isSupportConstantValue,
|
||||
filterVar,
|
||||
}) => {
|
||||
const language = useLanguage()
|
||||
const { t } = useTranslation()
|
||||
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: (varPayload: Var) => {
|
||||
return [VarType.string, VarType.number, VarType.secret].includes(varPayload.type)
|
||||
},
|
||||
})
|
||||
const paramType = (type: string) => {
|
||||
if (type === FormTypeEnum.textNumber)
|
||||
return 'Number'
|
||||
else if (type === FormTypeEnum.file || type === FormTypeEnum.files)
|
||||
return 'Files'
|
||||
else if (type === FormTypeEnum.appSelector)
|
||||
return 'AppSelector'
|
||||
else if (type === FormTypeEnum.modelSelector)
|
||||
return 'ModelSelector'
|
||||
else if (type === FormTypeEnum.toolSelector)
|
||||
return 'ToolSelector'
|
||||
else
|
||||
return 'String'
|
||||
}
|
||||
|
||||
const handleNotMixedTypeChange = useCallback((variable: string) => {
|
||||
return (varValue: ValueSelector | string, varKindType: VarKindType) => {
|
||||
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||
const target = draft[variable]
|
||||
if (target) {
|
||||
target.type = varKindType
|
||||
target.value = varValue
|
||||
}
|
||||
else {
|
||||
draft[variable] = {
|
||||
type: varKindType,
|
||||
value: varValue,
|
||||
}
|
||||
}
|
||||
})
|
||||
onChange(newValue)
|
||||
}
|
||||
}, [value, onChange])
|
||||
|
||||
const handleMixedTypeChange = useCallback((variable: string) => {
|
||||
return (itemValue: string) => {
|
||||
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||
const target = draft[variable]
|
||||
if (target) {
|
||||
target.value = itemValue
|
||||
}
|
||||
else {
|
||||
draft[variable] = {
|
||||
type: VarKindType.mixed,
|
||||
value: itemValue,
|
||||
}
|
||||
}
|
||||
})
|
||||
onChange(newValue)
|
||||
}
|
||||
}, [value, onChange])
|
||||
|
||||
const handleFileChange = useCallback((variable: string) => {
|
||||
return (varValue: ValueSelector | string) => {
|
||||
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||
draft[variable] = {
|
||||
type: VarKindType.variable,
|
||||
value: varValue,
|
||||
}
|
||||
})
|
||||
onChange(newValue)
|
||||
}
|
||||
}, [value, onChange])
|
||||
|
||||
const handleAppChange = useCallback((variable: string) => {
|
||||
return (app: {
|
||||
app_id: string
|
||||
inputs: Record<string, any>
|
||||
files?: any[]
|
||||
}) => {
|
||||
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||
draft[variable] = app as any
|
||||
})
|
||||
onChange(newValue)
|
||||
}
|
||||
}, [onChange, value])
|
||||
const handleModelChange = useCallback((variable: string) => {
|
||||
return (model: any) => {
|
||||
const newValue = produce(value, (draft: ToolVarInputs) => {
|
||||
draft[variable] = {
|
||||
...draft[variable],
|
||||
...model,
|
||||
} as any
|
||||
})
|
||||
onChange(newValue)
|
||||
}
|
||||
}, [onChange, value])
|
||||
|
||||
const [inputsIsFocus, setInputsIsFocus] = useState<Record<string, boolean>>({})
|
||||
const handleInputFocus = useCallback((variable: string) => {
|
||||
return (value: boolean) => {
|
||||
setInputsIsFocus((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
[variable]: value,
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
const handleOpen = useCallback((index: number) => {
|
||||
return () => onOpen(index)
|
||||
}, [onOpen])
|
||||
return (
|
||||
<div className='space-y-3'>
|
||||
{
|
||||
schema.map((schema, index) => {
|
||||
const {
|
||||
variable,
|
||||
label,
|
||||
type,
|
||||
required,
|
||||
tooltip,
|
||||
scope,
|
||||
} = schema
|
||||
const varInput = value[variable]
|
||||
const isNumber = type === FormTypeEnum.textNumber
|
||||
const isSelect = type === FormTypeEnum.select
|
||||
const isFile = type === FormTypeEnum.file || type === FormTypeEnum.files
|
||||
const isAppSelector = type === FormTypeEnum.appSelector
|
||||
const isModelSelector = type === FormTypeEnum.modelSelector
|
||||
// const isToolSelector = type === FormTypeEnum.toolSelector
|
||||
const isString = !isNumber && !isSelect && !isFile && !isAppSelector && !isModelSelector
|
||||
|
||||
return (
|
||||
<div key={variable} className='space-y-1'>
|
||||
<div className='flex h-[18px] items-center space-x-2'>
|
||||
<span className='code-sm-semibold text-text-secondary'>{label[language] || label.en_US}</span>
|
||||
<span className='system-xs-regular text-text-tertiary'>{paramType(type)}</span>
|
||||
{required && <span className='system-xs-regular text-util-colors-orange-dark-orange-dark-600'>Required</span>}
|
||||
</div>
|
||||
{isString && (
|
||||
<Input
|
||||
className={cn(inputsIsFocus[variable] ? 'border-gray-300 bg-gray-50 shadow-xs' : 'border-gray-100 bg-gray-100', 'rounded-lg border px-3 py-[6px]')}
|
||||
value={varInput?.value as string || ''}
|
||||
onChange={handleMixedTypeChange(variable)}
|
||||
readOnly={readOnly}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
onFocusChange={handleInputFocus(variable)}
|
||||
placeholder={t('workflow.nodes.http.insertVarPlaceholder')!}
|
||||
placeholderClassName='!leading-[21px]'
|
||||
/>
|
||||
)}
|
||||
{(isNumber || isSelect) && (
|
||||
<VarReferencePicker
|
||||
readonly={readOnly}
|
||||
isShowNodeName
|
||||
nodeId={nodeId}
|
||||
value={varInput?.type === VarKindType.constant ? (varInput?.value ?? '') : (varInput?.value ?? [])}
|
||||
onChange={handleNotMixedTypeChange(variable)}
|
||||
onOpen={handleOpen(index)}
|
||||
defaultVarKindType={varInput?.type || (isNumber ? VarKindType.constant : VarKindType.variable)}
|
||||
isSupportConstantValue={isSupportConstantValue}
|
||||
filterVar={isNumber ? filterVar : undefined}
|
||||
availableVars={isSelect ? availableVars : undefined}
|
||||
schema={schema}
|
||||
/>
|
||||
)}
|
||||
{isFile && (
|
||||
<VarReferencePicker
|
||||
readonly={readOnly}
|
||||
isShowNodeName
|
||||
nodeId={nodeId}
|
||||
value={varInput?.value || []}
|
||||
onChange={handleFileChange(variable)}
|
||||
onOpen={handleOpen(index)}
|
||||
defaultVarKindType={VarKindType.variable}
|
||||
filterVar={(varPayload: Var) => varPayload.type === VarType.file || varPayload.type === VarType.arrayFile}
|
||||
/>
|
||||
)}
|
||||
{isAppSelector && (
|
||||
<AppSelector
|
||||
disabled={readOnly}
|
||||
scope={scope || 'all'}
|
||||
value={varInput as any}
|
||||
onSelect={handleAppChange(variable)}
|
||||
/>
|
||||
)}
|
||||
{isModelSelector && (
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[387px]'
|
||||
isAdvancedMode
|
||||
isInWorkflow
|
||||
value={varInput as any}
|
||||
setModel={handleModelChange(variable)}
|
||||
readonly={readOnly}
|
||||
scope={scope}
|
||||
/>
|
||||
)}
|
||||
{tooltip && <div className='body-xs-regular text-text-tertiary'>{tooltip[language] || tooltip.en_US}</div>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(InputVarList)
|
||||
@ -1,34 +1,57 @@
|
||||
import {
|
||||
memo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import cn from '@/utils/classnames'
|
||||
import Placeholder from './placeholder'
|
||||
import type {
|
||||
Node,
|
||||
NodeOutPutVar,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type MixedVariableTextInputProps = {
|
||||
editable?: boolean
|
||||
readOnly?: boolean
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
value?: string
|
||||
onChange?: (text: string) => void
|
||||
}
|
||||
const MixedVariableTextInput = ({
|
||||
editable = true,
|
||||
readOnly = false,
|
||||
nodesOutputVars,
|
||||
availableNodes = [],
|
||||
value = '',
|
||||
onChange,
|
||||
}: MixedVariableTextInputProps) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<PromptEditor
|
||||
wrapperClassName={cn(
|
||||
'rounded-lg border border-transparent bg-components-input-bg-normal px-2 py-1',
|
||||
'w-full rounded-lg border border-transparent bg-components-input-bg-normal px-2 py-1',
|
||||
'hover:border-components-input-border-hover hover:bg-components-input-bg-hover',
|
||||
'focus-within:border-components-input-border-active focus-within:bg-components-input-bg-active focus-within:shadow-xs',
|
||||
)}
|
||||
className='caret:text-text-accent'
|
||||
editable={editable}
|
||||
editable={!readOnly}
|
||||
value={value}
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: [],
|
||||
workflowNodesMap: {},
|
||||
variables: nodesOutputVars || [],
|
||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
}}
|
||||
placeholder={<Placeholder />}
|
||||
onChange={onChange}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { FOCUS_COMMAND } from 'lexical'
|
||||
import { $insertNodes } from 'lexical'
|
||||
@ -6,6 +7,7 @@ import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/cust
|
||||
import Badge from '@/app/components/base/badge'
|
||||
|
||||
const Placeholder = () => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const handleInsert = useCallback((text: string) => {
|
||||
@ -25,7 +27,7 @@ const Placeholder = () => {
|
||||
}}
|
||||
>
|
||||
<div className='flex grow items-center'>
|
||||
Type or press
|
||||
{t('workflow.nodes.tool.insertPlaceholder1')}
|
||||
<div className='system-kbd mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder'>/</div>
|
||||
<div
|
||||
className='system-sm-regular cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto hover:text-text-tertiary'
|
||||
@ -34,7 +36,7 @@ const Placeholder = () => {
|
||||
handleInsert('/')
|
||||
})}
|
||||
>
|
||||
insert variable
|
||||
{t('workflow.nodes.tool.insertPlaceholder2')}
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
|
||||
@ -659,6 +659,8 @@ const translation = {
|
||||
toAuthorize: 'To authorize',
|
||||
inputVars: 'Input Variables',
|
||||
settings: 'Settings',
|
||||
insertPlaceholder1: 'Type or press',
|
||||
insertPlaceholder2: 'insert variable',
|
||||
outputVars: {
|
||||
text: 'tool generated content',
|
||||
files: {
|
||||
|
||||
@ -660,6 +660,8 @@ const translation = {
|
||||
toAuthorize: '授权',
|
||||
inputVars: '输入变量',
|
||||
settings: '设置',
|
||||
insertPlaceholder1: '键入',
|
||||
insertPlaceholder2: '插入变量',
|
||||
outputVars: {
|
||||
text: '工具生成的内容',
|
||||
files: {
|
||||
|
||||
Reference in New Issue
Block a user