feat: knowledge pipeline (#25360)

Signed-off-by: -LAN- <laipz8200@outlook.com>
Co-authored-by: twwu <twwu@dify.ai>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: jyong <718720800@qq.com>
Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com>
Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com>
Co-authored-by: lyzno1 <yuanyouhuilyz@gmail.com>
Co-authored-by: quicksand <quicksandzn@gmail.com>
Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com>
Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com>
Co-authored-by: zxhlyh <jasonapring2015@outlook.com>
Co-authored-by: Yongtao Huang <yongtaoh2022@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: nite-knite <nkCoding@gmail.com>
Co-authored-by: Hanqing Zhao <sherry9277@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Harry <xh001x@hotmail.com>
This commit is contained in:
-LAN-
2025-09-18 12:49:10 +08:00
committed by GitHub
parent 7dadb33003
commit 85cda47c70
1772 changed files with 102407 additions and 31710 deletions

View File

@@ -0,0 +1,41 @@
import cn from '@/utils/classnames'
import { useFieldContext } from '../..'
import type { CustomSelectProps, Option } from '../../../select/custom'
import CustomSelect from '../../../select/custom'
import type { LabelProps } from '../label'
import Label from '../label'
type CustomSelectFieldProps<T extends Option> = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
options: T[]
className?: string
} & Omit<CustomSelectProps<T>, 'options' | 'value' | 'onChange'>
const CustomSelectField = <T extends Option>({
label,
labelOptions,
options,
className,
...selectProps
}: CustomSelectFieldProps<T>) => {
const field = useFieldContext<string>()
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={field.name}
label={label}
{...(labelOptions ?? {})}
/>
<CustomSelect<T>
value={field.state.value}
options={options}
onChange={value => field.handleChange(value)}
{...selectProps}
/>
</div>
)
}
export default CustomSelectField

View File

@@ -0,0 +1,83 @@
import cn from '@/utils/classnames'
import type { LabelProps } from '../label'
import { useFieldContext } from '../..'
import Label from '../label'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import FileTypeItem from '@/app/components/workflow/nodes/_base/components/file-type-item'
import { useCallback } from 'react'
type FieldValue = {
allowedFileTypes: string[],
allowedFileExtensions: string[]
}
type FileTypesFieldProps = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string
}
const FileTypesField = ({
label,
labelOptions,
className,
}: FileTypesFieldProps) => {
const field = useFieldContext<FieldValue>()
const handleSupportFileTypeChange = useCallback((type: SupportUploadFileTypes) => {
let newAllowFileTypes = [...field.state.value.allowedFileTypes]
if (type === SupportUploadFileTypes.custom) {
if (!newAllowFileTypes.includes(SupportUploadFileTypes.custom))
newAllowFileTypes = [SupportUploadFileTypes.custom]
else
newAllowFileTypes = newAllowFileTypes.filter(v => v !== type)
}
else {
newAllowFileTypes = newAllowFileTypes.filter(v => v !== SupportUploadFileTypes.custom)
if (newAllowFileTypes.includes(type))
newAllowFileTypes = newAllowFileTypes.filter(v => v !== type)
else
newAllowFileTypes.push(type)
}
field.handleChange({
...field.state.value,
allowedFileTypes: newAllowFileTypes,
})
}, [field])
const handleCustomFileTypesChange = useCallback((customFileTypes: string[]) => {
field.handleChange({
...field.state.value,
allowedFileExtensions: customFileTypes,
})
}, [field])
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={field.name}
label={label}
{...(labelOptions ?? {})}
/>
{
[SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => (
<FileTypeItem
key={type}
type={type as SupportUploadFileTypes.image | SupportUploadFileTypes.document | SupportUploadFileTypes.audio | SupportUploadFileTypes.video}
selected={field.state.value.allowedFileTypes.includes(type)}
onToggle={handleSupportFileTypeChange}
/>
))
}
<FileTypeItem
type={SupportUploadFileTypes.custom}
selected={field.state.value.allowedFileTypes.includes(SupportUploadFileTypes.custom)}
onToggle={handleSupportFileTypeChange}
customFileTypes={field.state.value.allowedFileExtensions}
onCustomFileTypesChange={handleCustomFileTypesChange}
/>
</div>
)
}
export default FileTypesField

View File

@@ -0,0 +1,40 @@
import React from 'react'
import { useFieldContext } from '../..'
import type { LabelProps } from '../label'
import Label from '../label'
import cn from '@/utils/classnames'
import type { FileUploaderInAttachmentWrapperProps } from '../../../file-uploader/file-uploader-in-attachment'
import FileUploaderInAttachmentWrapper from '../../../file-uploader/file-uploader-in-attachment'
import type { FileEntity } from '../../../file-uploader/types'
type FileUploaderFieldProps = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string
} & Omit<FileUploaderInAttachmentWrapperProps, 'value' | 'onChange'>
const FileUploaderField = ({
label,
labelOptions,
className,
...inputProps
}: FileUploaderFieldProps) => {
const field = useFieldContext<FileEntity[]>()
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={field.name}
label={label}
{...(labelOptions ?? {})}
/>
<FileUploaderInAttachmentWrapper
value={field.state.value}
onChange={value => field.handleChange(value)}
{...inputProps}
/>
</div>
)
}
export default FileUploaderField

View File

@@ -0,0 +1,52 @@
import { InputTypeEnum } from './types'
import { PipelineInputVarType } from '@/models/pipeline'
import { useTranslation } from 'react-i18next'
import {
RiAlignLeft,
RiCheckboxLine,
RiFileCopy2Line,
RiFileTextLine,
RiHashtag,
RiListCheck3,
RiTextSnippet,
} from '@remixicon/react'
const i18nFileTypeMap: Record<string, string> = {
'number': 'number',
'file': 'single-file',
'file-list': 'multi-files',
}
const INPUT_TYPE_ICON = {
[PipelineInputVarType.textInput]: RiTextSnippet,
[PipelineInputVarType.paragraph]: RiAlignLeft,
[PipelineInputVarType.number]: RiHashtag,
[PipelineInputVarType.select]: RiListCheck3,
[PipelineInputVarType.checkbox]: RiCheckboxLine,
[PipelineInputVarType.singleFile]: RiFileTextLine,
[PipelineInputVarType.multiFiles]: RiFileCopy2Line,
}
const DATA_TYPE = {
[PipelineInputVarType.textInput]: 'string',
[PipelineInputVarType.paragraph]: 'string',
[PipelineInputVarType.number]: 'number',
[PipelineInputVarType.select]: 'string',
[PipelineInputVarType.checkbox]: 'boolean',
[PipelineInputVarType.singleFile]: 'file',
[PipelineInputVarType.multiFiles]: 'array[file]',
}
export const useInputTypeOptions = (supportFile: boolean) => {
const { t } = useTranslation()
const options = supportFile ? InputTypeEnum.options : InputTypeEnum.exclude(['file', 'file-list']).options
return options.map((value) => {
return {
value,
label: t(`appDebug.variableConfig.${i18nFileTypeMap[value] || value}`),
Icon: INPUT_TYPE_ICON[value],
type: DATA_TYPE[value],
}
})
}

View File

@@ -0,0 +1,64 @@
import cn from '@/utils/classnames'
import { useFieldContext } from '../../..'
import type { CustomSelectProps } from '../../../../select/custom'
import CustomSelect from '../../../../select/custom'
import type { LabelProps } from '../../label'
import Label from '../../label'
import { useCallback } from 'react'
import Trigger from './trigger'
import type { FileTypeSelectOption, InputType } from './types'
import { useInputTypeOptions } from './hooks'
import Option from './option'
type InputTypeSelectFieldProps = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
supportFile: boolean
className?: string
} & Omit<CustomSelectProps<FileTypeSelectOption>, 'options' | 'value' | 'onChange' | 'CustomTrigger' | 'CustomOption'>
const InputTypeSelectField = ({
label,
labelOptions,
supportFile,
className,
...customSelectProps
}: InputTypeSelectFieldProps) => {
const field = useFieldContext<InputType>()
const inputTypeOptions = useInputTypeOptions(supportFile)
const renderTrigger = useCallback((option: FileTypeSelectOption | undefined, open: boolean) => {
return <Trigger option={option} open={open} />
}, [])
const renderOption = useCallback((option: FileTypeSelectOption) => {
return <Option option={option} />
}, [])
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={field.name}
label={label}
{...(labelOptions ?? {})}
/>
<CustomSelect<FileTypeSelectOption>
value={field.state.value}
options={inputTypeOptions}
onChange={value => field.handleChange(value as InputType)}
triggerProps={{
className: 'gap-x-0.5',
}}
popupProps={{
className: 'w-[368px]',
wrapperClassName: 'z-[9999999]',
itemClassName: 'gap-x-1',
}}
CustomTrigger={renderTrigger}
CustomOption={renderOption}
{...customSelectProps}
/>
</div>
)
}
export default InputTypeSelectField

View File

@@ -0,0 +1,21 @@
import React from 'react'
import type { FileTypeSelectOption } from './types'
import Badge from '@/app/components/base/badge'
type OptionProps = {
option: FileTypeSelectOption
}
const Option = ({
option,
}: OptionProps) => {
return (
<>
<option.Icon className='h-4 w-4 shrink-0 text-text-tertiary' />
<span className='grow px-1'>{option.label}</span>
<Badge text={option.type} uppercase={false} />
</>
)
}
export default React.memo(Option)

View File

@@ -0,0 +1,42 @@
import React from 'react'
import Badge from '@/app/components/base/badge'
import cn from '@/utils/classnames'
import { RiArrowDownSLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import type { FileTypeSelectOption } from './types'
type TriggerProps = {
option: FileTypeSelectOption | undefined
open: boolean
}
const Trigger = ({
option,
open,
}: TriggerProps) => {
const { t } = useTranslation()
return (
<>
{option ? (
<>
<option.Icon className='h-4 w-4 shrink-0 text-text-tertiary' />
<span className='grow p-1'>{option.label}</span>
<div className='pr-0.5'>
<Badge text={option.type} uppercase={false} />
</div>
</>
) : (
<span className='grow p-1'>{t('common.placeholder.select')}</span>
)}
<RiArrowDownSLine
className={cn(
'h-4 w-4 shrink-0 text-text-quaternary group-hover:text-text-secondary',
open && 'text-text-secondary',
)}
/>
</>
)
}
export default React.memo(Trigger)

View File

@@ -0,0 +1,21 @@
import type { RemixiconComponentType } from '@remixicon/react'
import { z } from 'zod'
export const InputTypeEnum = z.enum([
'text-input',
'paragraph',
'number',
'select',
'checkbox',
'file',
'file-list',
])
export type InputType = z.infer<typeof InputTypeEnum>
export type FileTypeSelectOption = {
value: InputType
label: string
Icon: RemixiconComponentType
type: string
}

View File

@@ -0,0 +1,39 @@
import {
memo,
} from 'react'
import PromptEditor from '@/app/components/base/prompt-editor'
import cn from '@/utils/classnames'
import Placeholder from './placeholder'
type MixedVariableTextInputProps = {
editable?: boolean
value?: string
onChange?: (text: string) => void
}
const MixedVariableTextInput = ({
editable = true,
value = '',
onChange,
}: MixedVariableTextInputProps) => {
return (
<PromptEditor
wrapperClassName={cn(
'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}
value={value}
workflowVariableBlock={{
show: true,
variables: [],
workflowNodesMap: {},
}}
placeholder={<Placeholder />}
onChange={onChange}
/>
)
}
export default memo(MixedVariableTextInput)

View File

@@ -0,0 +1,49 @@
import { useCallback } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { FOCUS_COMMAND } from 'lexical'
import { $insertNodes } from 'lexical'
import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node'
import Badge from '@/app/components/base/badge'
const Placeholder = () => {
const [editor] = useLexicalComposerContext()
const handleInsert = useCallback((text: string) => {
editor.update(() => {
const textNode = new CustomTextNode(text)
$insertNodes([textNode])
})
editor.dispatchCommand(FOCUS_COMMAND, undefined as any)
}, [editor])
return (
<div
className='pointer-events-auto flex h-full w-full cursor-text items-center px-2'
onClick={(e) => {
e.stopPropagation()
handleInsert('')
}}
>
<div className='flex grow items-center'>
Type or press
<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'
onClick={((e) => {
e.stopPropagation()
handleInsert('/')
})}
>
insert variable
</div>
</div>
<Badge
className='shrink-0'
text='String'
uppercase={false}
/>
</div>
)
}
export default Placeholder

View File

@@ -1,5 +1,6 @@
import React from 'react'
import { useFieldContext } from '../..'
import type { LabelProps } from '../label'
import Label from '../label'
import cn from '@/utils/classnames'
import type { InputNumberProps } from '../../../input-number'
@@ -7,33 +8,24 @@ import { InputNumber } from '../../../input-number'
type TextFieldProps = {
label: string
isRequired?: boolean
showOptional?: boolean
tooltip?: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string
labelClassName?: string
} & Omit<InputNumberProps, 'id' | 'value' | 'onChange' | 'onBlur'>
const NumberInputField = ({
label,
isRequired,
showOptional,
tooltip,
labelOptions,
className,
labelClassName,
...inputProps
}: TextFieldProps) => {
const field = useFieldContext<number | undefined>()
const field = useFieldContext<number>()
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={field.name}
label={label}
isRequired={isRequired}
showOptional={showOptional}
tooltip={tooltip}
className={labelClassName}
{...(labelOptions ?? {})}
/>
<InputNumber
id={field.name}

View File

@@ -0,0 +1,47 @@
import cn from '@/utils/classnames'
import type { LabelProps } from '../label'
import { useFieldContext } from '../..'
import Label from '../label'
import type { InputNumberWithSliderProps } from '@/app/components/workflow/nodes/_base/components/input-number-with-slider'
import InputNumberWithSlider from '@/app/components/workflow/nodes/_base/components/input-number-with-slider'
type NumberSliderFieldProps = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
description?: string
className?: string
} & Omit<InputNumberWithSliderProps, 'value' | 'onChange'>
const NumberSliderField = ({
label,
labelOptions,
description,
className,
...InputNumberWithSliderProps
}: NumberSliderFieldProps) => {
const field = useFieldContext<number>()
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<div>
<Label
htmlFor={field.name}
label={label}
{...(labelOptions ?? {})}
/>
{description && (
<div className='body-xs-regular pb-0.5 text-text-tertiary'>
{description}
</div>
)}
</div>
<InputNumberWithSlider
value={field.state.value}
onChange={value => field.handleChange(value)}
{...InputNumberWithSliderProps}
/>
</div>
)
}
export default NumberSliderField

View File

@@ -1,27 +1,29 @@
import cn from '@/utils/classnames'
import { useFieldContext } from '../..'
import type { LabelProps } from '../label'
import Label from '../label'
import type { Options } from '@/app/components/app/configuration/config-var/config-select'
import ConfigSelect from '@/app/components/app/configuration/config-var/config-select'
type OptionsFieldProps = {
label: string;
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string;
labelClassName?: string;
}
const OptionsField = ({
label,
className,
labelClassName,
labelOptions,
}: OptionsFieldProps) => {
const field = useFieldContext<string[]>()
const field = useFieldContext<Options>()
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={field.name}
label={label}
className={labelClassName}
{...(labelOptions ?? {})}
/>
<ConfigSelect
options={field.state.value}

View File

@@ -1,31 +1,25 @@
import cn from '@/utils/classnames'
import { useFieldContext } from '../..'
import type { Option, PureSelectProps } from '../../../select/pure'
import PureSelect from '../../../select/pure'
import type { LabelProps } from '../label'
import Label from '../label'
type SelectOption = {
value: string
label: string
}
type SelectFieldProps = {
label: string
options: SelectOption[]
isRequired?: boolean
showOptional?: boolean
tooltip?: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
options: Option[]
onChange?: (value: string) => void
className?: string
labelClassName?: string
}
} & Omit<PureSelectProps, 'options' | 'value' | 'onChange'>
const SelectField = ({
label,
labelOptions,
options,
isRequired,
showOptional,
tooltip,
onChange,
className,
labelClassName,
...selectProps
}: SelectFieldProps) => {
const field = useFieldContext<string>()
@@ -34,15 +28,13 @@ const SelectField = ({
<Label
htmlFor={field.name}
label={label}
isRequired={isRequired}
showOptional={showOptional}
tooltip={tooltip}
className={labelClassName}
{...(labelOptions ?? {})}
/>
<PureSelect
value={field.state.value}
options={options}
onChange={value => field.handleChange(value)}
{...selectProps}
/>
</div>
)

View File

@@ -0,0 +1,41 @@
import React from 'react'
import { useFieldContext } from '../..'
import type { LabelProps } from '../label'
import Label from '../label'
import cn from '@/utils/classnames'
import type { TextareaProps } from '../../../textarea'
import Textarea from '../../../textarea'
type TextAreaFieldProps = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string
} & Omit<TextareaProps, 'className' | 'onChange' | 'onBlur' | 'value' | 'id'>
const TextAreaField = ({
label,
labelOptions,
className,
...inputProps
}: TextAreaFieldProps) => {
const field = useFieldContext<string>()
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={field.name}
label={label}
{...(labelOptions ?? {})}
/>
<Textarea
id={field.name}
value={field.state.value}
onChange={e => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
{...inputProps}
/>
</div>
)
}
export default TextAreaField

View File

@@ -1,25 +1,20 @@
import React from 'react'
import { useFieldContext } from '../..'
import Input, { type InputProps } from '../../../input'
import type { LabelProps } from '../label'
import Label from '../label'
import cn from '@/utils/classnames'
type TextFieldProps = {
label: string
isRequired?: boolean
showOptional?: boolean
tooltip?: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string
labelClassName?: string
} & Omit<InputProps, 'className' | 'onChange' | 'onBlur' | 'value' | 'id'>
const TextField = ({
label,
isRequired,
showOptional,
tooltip,
labelOptions,
className,
labelClassName,
...inputProps
}: TextFieldProps) => {
const field = useFieldContext<string>()
@@ -29,10 +24,7 @@ const TextField = ({
<Label
htmlFor={field.name}
label={label}
isRequired={isRequired}
showOptional={showOptional}
tooltip={tooltip}
className={labelClassName}
{...(labelOptions ?? {})}
/>
<Input
id={field.name}

View File

@@ -0,0 +1,58 @@
import cn from '@/utils/classnames'
import type { LabelProps } from '../label'
import { useFieldContext } from '../..'
import Label from '../label'
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
import { useTranslation } from 'react-i18next'
import { TransferMethod } from '@/types/app'
import { useCallback } from 'react'
type UploadMethodFieldProps = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string
}
const UploadMethodField = ({
label,
labelOptions,
className,
}: UploadMethodFieldProps) => {
const { t } = useTranslation()
const field = useFieldContext<TransferMethod[]>()
const { value } = field.state
const handleUploadMethodChange = useCallback((method: TransferMethod) => {
field.handleChange(method === TransferMethod.all ? [TransferMethod.local_file, TransferMethod.remote_url] : [method])
}, [field])
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={field.name}
label={label}
{...(labelOptions ?? {})}
/>
<div className='grid grid-cols-3 gap-2'>
<OptionCard
title={t('appDebug.variableConfig.localUpload')}
selected={value.length === 1 && value.includes(TransferMethod.local_file)}
onSelect={handleUploadMethodChange.bind(null, TransferMethod.local_file)}
/>
<OptionCard
title="URL"
selected={value.length === 1 && value.includes(TransferMethod.remote_url)}
onSelect={handleUploadMethodChange.bind(null, TransferMethod.remote_url)}
/>
<OptionCard
title={t('appDebug.variableConfig.both')}
selected={value.includes(TransferMethod.local_file) && value.includes(TransferMethod.remote_url)}
onSelect={handleUploadMethodChange.bind(null, TransferMethod.all)}
/>
</div>
</div>
)
}
export default UploadMethodField

View File

@@ -0,0 +1,86 @@
import type { ChangeEvent } from 'react'
import { useState } from 'react'
import { RiEditLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import SegmentedControl from '@/app/components/base/segmented-control'
import { VariableX } from '@/app/components/base/icons/src/vender/workflow'
import Input from '@/app/components/base/input'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
import type { LabelProps } from '../label'
import Label from '../label'
type VariableOrConstantInputFieldProps = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string
}
const VariableOrConstantInputField = ({
className,
label,
labelOptions,
}: VariableOrConstantInputFieldProps) => {
const [variableType, setVariableType] = useState('variable')
const options = [
{
Icon: VariableX,
value: 'variable',
},
{
Icon: RiEditLine,
value: 'constant',
},
]
const handleVariableOrConstantChange = (value: string) => {
setVariableType(value)
}
const handleVariableValueChange = () => {
console.log('Variable value changed')
}
const handleConstantValueChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log('Constant value changed:', e.target.value)
}
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={'variable-or-constant'}
label={label}
{...(labelOptions ?? {})}
/>
<div className='flex items-center'>
<SegmentedControl
className='mr-1 shrink-0'
value={variableType}
onChange={handleVariableOrConstantChange as any}
options={options as any}
/>
{
variableType === 'variable' && (
<VarReferencePicker
className='grow'
nodeId=''
readonly={false}
value={[]}
onChange={handleVariableValueChange}
/>
)
}
{
variableType === 'constant' && (
<Input
className='ml-1'
onChange={handleConstantValueChange}
/>
)
}
</div>
</div>
)
}
export default VariableOrConstantInputField

View File

@@ -0,0 +1,41 @@
import cn from '@/utils/classnames'
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
import type { LabelProps } from '../label'
import Label from '../label'
type VariableOrConstantInputFieldProps = {
label: string
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
className?: string
}
const VariableOrConstantInputField = ({
className,
label,
labelOptions,
}: VariableOrConstantInputFieldProps) => {
const handleVariableValueChange = () => {
console.log('Variable value changed')
}
return (
<div className={cn('flex flex-col gap-y-0.5', className)}>
<Label
htmlFor={'variable-or-constant'}
label={label}
{...(labelOptions ?? {})}
/>
<div className='flex items-center'>
<VarReferencePicker
className='grow'
nodeId=''
readonly={false}
value={[]}
onChange={handleVariableValueChange}
/>
</div>
</div>
)
}
export default VariableOrConstantInputField

View File

@@ -0,0 +1,43 @@
import { useStore } from '@tanstack/react-form'
import type { FormType } from '../..'
import { useFormContext } from '../..'
import Button from '../../../button'
import { useTranslation } from 'react-i18next'
export type CustomActionsProps = {
form: FormType
isSubmitting: boolean
canSubmit: boolean
}
type ActionsProps = {
CustomActions?: (props: CustomActionsProps) => React.ReactNode | React.JSX.Element
}
const Actions = ({
CustomActions,
}: ActionsProps) => {
const { t } = useTranslation()
const form = useFormContext()
const [isSubmitting, canSubmit] = useStore(form.store, state => [
state.isSubmitting,
state.canSubmit,
])
if (CustomActions)
return CustomActions({ form, isSubmitting, canSubmit })
return (
<Button
variant='primary'
disabled={isSubmitting || !canSubmit}
loading={isSubmitting}
onClick={() => form.handleSubmit()}
>
{t('common.operation.submit')}
</Button>
)
}
export default Actions

View File

@@ -1,25 +0,0 @@
import { useStore } from '@tanstack/react-form'
import { useFormContext } from '../..'
import Button, { type ButtonProps } from '../../../button'
type SubmitButtonProps = Omit<ButtonProps, 'disabled' | 'loading' | 'onClick'>
const SubmitButton = ({ ...buttonProps }: SubmitButtonProps) => {
const form = useFormContext()
const [isSubmitting, canSubmit] = useStore(form.store, state => [
state.isSubmitting,
state.canSubmit,
])
return (
<Button
disabled={isSubmitting || !canSubmit}
loading={isSubmitting}
onClick={() => form.handleSubmit()}
{...buttonProps}
/>
)
}
export default SubmitButton