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:
@@ -17,7 +17,6 @@ import Textarea from '@/app/components/base/textarea'
|
||||
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
|
||||
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
|
||||
import { Resolution, TransferMethod } from '@/types/app'
|
||||
import { useFeatures } from '@/app/components/base/features/hooks'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
@@ -26,6 +25,7 @@ import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import BoolInput from './bool-input'
|
||||
import { useHooksStore } from '@/app/components/workflow/hooks-store'
|
||||
|
||||
type Props = {
|
||||
payload: InputVar
|
||||
@@ -46,7 +46,8 @@ const FormItem: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { type } = payload
|
||||
const fileSettings = useFeatures(s => s.features.file)
|
||||
const fileSettings = useHooksStore(s => s.configsMap?.fileSettings)
|
||||
|
||||
const handleArrayItemChange = useCallback((index: number) => {
|
||||
return (newValue: any) => {
|
||||
const newValues = produce(value, (draft: any) => {
|
||||
@@ -187,7 +188,7 @@ const FormItem: FC<Props> = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{ type === InputVarType.jsonObject && (
|
||||
{type === InputVarType.jsonObject && (
|
||||
<CodeEditor
|
||||
value={value}
|
||||
language={CodeLanguage.json}
|
||||
|
||||
@@ -39,6 +39,7 @@ type Props = {
|
||||
tip?: React.JSX.Element
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
footer?: React.ReactNode
|
||||
}
|
||||
|
||||
const Base: FC<Props> = ({
|
||||
@@ -57,6 +58,7 @@ const Base: FC<Props> = ({
|
||||
showFileList,
|
||||
showCodeGenerator = false,
|
||||
tip,
|
||||
footer,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const {
|
||||
@@ -128,6 +130,7 @@ const Base: FC<Props> = ({
|
||||
{showFileList && fileList.length > 0 && (
|
||||
<FileListInLog fileList={fileList} />
|
||||
)}
|
||||
{footer}
|
||||
</div>
|
||||
</Wrap>
|
||||
)
|
||||
|
||||
@@ -39,6 +39,7 @@ export type Props = {
|
||||
showCodeGenerator?: boolean
|
||||
className?: string
|
||||
tip?: React.JSX.Element
|
||||
footer?: React.ReactNode
|
||||
}
|
||||
|
||||
export const languageMap = {
|
||||
@@ -67,6 +68,7 @@ const CodeEditor: FC<Props> = ({
|
||||
showCodeGenerator = false,
|
||||
className,
|
||||
tip,
|
||||
footer,
|
||||
}) => {
|
||||
const [isFocus, setIsFocus] = React.useState(false)
|
||||
const [isMounted, setIsMounted] = React.useState(false)
|
||||
@@ -191,6 +193,7 @@ const CodeEditor: FC<Props> = ({
|
||||
showFileList={showFileList}
|
||||
showCodeGenerator={showCodeGenerator}
|
||||
tip={tip}
|
||||
footer={footer}
|
||||
>
|
||||
{main}
|
||||
</Base>
|
||||
|
||||
@@ -17,7 +17,7 @@ const ErrorHandleTip = ({
|
||||
|
||||
if (type === ErrorHandleTypeEnum.defaultValue)
|
||||
return t('workflow.nodes.common.errorHandle.defaultValue.inLog')
|
||||
}, [])
|
||||
}, [t, type])
|
||||
|
||||
if (!type)
|
||||
return null
|
||||
|
||||
@@ -47,7 +47,7 @@ const FileTypeItem: FC<Props> = ({
|
||||
? (
|
||||
<div>
|
||||
<div className='flex items-center border-b border-divider-subtle p-3 pb-2'>
|
||||
<FileTypeIcon className='shrink-0' type={type} size='md' />
|
||||
<FileTypeIcon className='shrink-0' type={type} size='lg' />
|
||||
<div className='system-sm-medium mx-2 grow text-text-primary'>{t(`appDebug.variableConfig.file.${type}.name`)}</div>
|
||||
<Checkbox className='shrink-0' checked={selected} />
|
||||
</div>
|
||||
@@ -62,7 +62,7 @@ const FileTypeItem: FC<Props> = ({
|
||||
)
|
||||
: (
|
||||
<div className='flex items-center'>
|
||||
<FileTypeIcon className='shrink-0' type={type} size='md' />
|
||||
<FileTypeIcon className='shrink-0' type={type} size='lg' />
|
||||
<div className='mx-2 grow'>
|
||||
<div className='system-sm-medium text-text-primary'>{t(`appDebug.variableConfig.file.${type}.name`)}</div>
|
||||
<div className='system-2xs-regular-uppercase mt-1 text-text-tertiary'>{type !== SupportUploadFileTypes.custom ? FILE_EXTS[type].join(', ') : t('appDebug.variableConfig.file.custom.description')}</div>
|
||||
|
||||
@@ -31,6 +31,8 @@ type Props = {
|
||||
inPanel?: boolean
|
||||
currentTool?: Tool
|
||||
currentProvider?: ToolWithProvider
|
||||
showManageInputField?: boolean
|
||||
onManageInputField?: () => void
|
||||
}
|
||||
|
||||
const FormInputItem: FC<Props> = ({
|
||||
@@ -42,6 +44,8 @@ const FormInputItem: FC<Props> = ({
|
||||
inPanel,
|
||||
currentTool,
|
||||
currentProvider,
|
||||
showManageInputField,
|
||||
onManageInputField,
|
||||
}) => {
|
||||
const language = useLanguage()
|
||||
|
||||
@@ -64,7 +68,7 @@ const FormInputItem: FC<Props> = ({
|
||||
const isSelect = type === FormTypeEnum.select || type === FormTypeEnum.dynamicSelect
|
||||
const isAppSelector = type === FormTypeEnum.appSelector
|
||||
const isModelSelector = type === FormTypeEnum.modelSelector
|
||||
const showTypeSwitch = isNumber || isBoolean || isObject || isArray
|
||||
const showTypeSwitch = isNumber || isBoolean || isObject || isArray || isSelect
|
||||
const isConstant = varInput?.type === VarKindType.constant || !varInput?.type
|
||||
const showVariableSelector = isFile || varInput?.type === VarKindType.variable
|
||||
|
||||
@@ -84,8 +88,8 @@ const FormInputItem: FC<Props> = ({
|
||||
return VarType.arrayFile
|
||||
else if (type === FormTypeEnum.file)
|
||||
return VarType.file
|
||||
// else if (isSelect)
|
||||
// return VarType.select
|
||||
else if (isSelect)
|
||||
return VarType.string
|
||||
// else if (isAppSelector)
|
||||
// return VarType.appSelector
|
||||
// else if (isModelSelector)
|
||||
@@ -192,6 +196,8 @@ const FormInputItem: FC<Props> = ({
|
||||
onChange={handleValueChange}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
showManageInputField={showManageInputField}
|
||||
onManageInputField={onManageInputField}
|
||||
/>
|
||||
)}
|
||||
{isNumber && isConstant && (
|
||||
@@ -209,7 +215,7 @@ const FormInputItem: FC<Props> = ({
|
||||
onChange={handleValueChange}
|
||||
/>
|
||||
)}
|
||||
{isSelect && (
|
||||
{isSelect && isConstant && (
|
||||
<SimpleSelect
|
||||
wrapperClassName='h-8 grow'
|
||||
disabled={readOnly}
|
||||
@@ -271,6 +277,7 @@ const FormInputItem: FC<Props> = ({
|
||||
valueTypePlaceHolder={targetVarType()}
|
||||
currentTool={currentTool}
|
||||
currentProvider={currentProvider}
|
||||
isFilterFileVar={isBoolean}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
|
||||
const Add = () => {
|
||||
return (
|
||||
<ActionButton>
|
||||
<RiAddLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
|
||||
export default Add
|
||||
@@ -0,0 +1,24 @@
|
||||
import { BoxGroupField } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||
import Add from './add'
|
||||
|
||||
const InputField = () => {
|
||||
return (
|
||||
<BoxGroupField
|
||||
fieldProps={{
|
||||
supportCollapse: true,
|
||||
fieldTitleProps: {
|
||||
title: 'input field',
|
||||
operation: <Add />,
|
||||
},
|
||||
}}
|
||||
boxGroupProps={{
|
||||
boxProps: {
|
||||
withBorderBottom: true,
|
||||
},
|
||||
}}
|
||||
>
|
||||
input field
|
||||
</BoxGroupField>
|
||||
)
|
||||
}
|
||||
export default InputField
|
||||
@@ -3,7 +3,7 @@ import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
|
||||
type Props = {
|
||||
export type InputNumberWithSliderProps = {
|
||||
value: number
|
||||
defaultValue?: number
|
||||
min?: number
|
||||
@@ -12,7 +12,7 @@ type Props = {
|
||||
onChange: (value: number) => void
|
||||
}
|
||||
|
||||
const InputNumberWithSlider: FC<Props> = ({
|
||||
const InputNumberWithSlider: FC<InputNumberWithSliderProps> = ({
|
||||
value,
|
||||
defaultValue = 0,
|
||||
min,
|
||||
|
||||
@@ -13,6 +13,7 @@ import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
|
||||
type Props = {
|
||||
instanceId?: string
|
||||
@@ -55,6 +56,9 @@ const Editor: FC<Props> = ({
|
||||
onFocusChange?.(isFocus)
|
||||
}, [isFocus])
|
||||
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
|
||||
return (
|
||||
<div className={cn(className, 'relative')}>
|
||||
<>
|
||||
@@ -102,6 +106,8 @@ const Editor: FC<Props> = ({
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
showManageInputField: !!pipelineId,
|
||||
onManageInputField: () => setShowInputFieldPanel?.(true),
|
||||
}}
|
||||
onChange={onChange}
|
||||
editable={!readOnly}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { RiAlignLeft, RiBracesLine, RiCheckboxLine, RiCheckboxMultipleLine, RiFileCopy2Line, RiFileList2Line, RiHashtag, RiTextSnippet } from '@remixicon/react'
|
||||
import {
|
||||
RiAlignLeft,
|
||||
RiBracesLine,
|
||||
RiCheckboxLine,
|
||||
RiCheckboxMultipleLine,
|
||||
RiFileCopy2Line,
|
||||
RiFileList2Line,
|
||||
RiHashtag,
|
||||
RiTextSnippet,
|
||||
} from '@remixicon/react'
|
||||
import { InputVarType } from '../../../types'
|
||||
|
||||
type Props = {
|
||||
@@ -19,6 +28,7 @@ const getIcon = (type: InputVarType) => {
|
||||
[InputVarType.jsonObject]: RiBracesLine,
|
||||
[InputVarType.singleFile]: RiFileList2Line,
|
||||
[InputVarType.multiFiles]: RiFileCopy2Line,
|
||||
[InputVarType.checkbox]: RiCheckboxLine,
|
||||
} as any)[type] || RiTextSnippet
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type {
|
||||
BoxGroupProps,
|
||||
FieldProps,
|
||||
} from '.'
|
||||
import {
|
||||
BoxGroup,
|
||||
Field,
|
||||
} from '.'
|
||||
|
||||
type BoxGroupFieldProps = {
|
||||
children?: ReactNode
|
||||
boxGroupProps?: Omit<BoxGroupProps, 'children'>
|
||||
fieldProps?: Omit<FieldProps, 'children'>
|
||||
}
|
||||
export const BoxGroupField = memo(({
|
||||
children,
|
||||
fieldProps,
|
||||
boxGroupProps,
|
||||
}: BoxGroupFieldProps) => {
|
||||
return (
|
||||
<BoxGroup {...boxGroupProps}>
|
||||
<Field {...fieldProps}>
|
||||
{children}
|
||||
</Field>
|
||||
</BoxGroup>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import {
|
||||
Box,
|
||||
Group,
|
||||
} from '.'
|
||||
import type {
|
||||
BoxProps,
|
||||
GroupProps,
|
||||
} from '.'
|
||||
|
||||
export type BoxGroupProps = {
|
||||
children?: ReactNode
|
||||
boxProps?: Omit<BoxProps, 'children'>
|
||||
groupProps?: Omit<GroupProps, 'children'>
|
||||
}
|
||||
export const BoxGroup = memo(({
|
||||
children,
|
||||
boxProps,
|
||||
groupProps,
|
||||
}: BoxGroupProps) => {
|
||||
return (
|
||||
<Box {...boxProps}>
|
||||
<Group {...groupProps}>
|
||||
{children}
|
||||
</Group>
|
||||
</Box>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type BoxProps = {
|
||||
className?: string
|
||||
children?: ReactNode
|
||||
withBorderBottom?: boolean
|
||||
}
|
||||
export const Box = memo(({
|
||||
className,
|
||||
children,
|
||||
withBorderBottom,
|
||||
}: BoxProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'py-2',
|
||||
withBorderBottom && 'border-b border-divider-subtle',
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,72 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type FieldTitleProps = {
|
||||
title?: string
|
||||
operation?: ReactNode
|
||||
subTitle?: string | ReactNode
|
||||
tooltip?: string
|
||||
showArrow?: boolean
|
||||
disabled?: boolean
|
||||
collapsed?: boolean
|
||||
onCollapse?: (collapsed: boolean) => void
|
||||
}
|
||||
export const FieldTitle = memo(({
|
||||
title,
|
||||
operation,
|
||||
subTitle,
|
||||
tooltip,
|
||||
showArrow,
|
||||
disabled,
|
||||
collapsed,
|
||||
onCollapse,
|
||||
}: FieldTitleProps) => {
|
||||
const [collapsedLocal, setCollapsedLocal] = useState(true)
|
||||
const collapsedMerged = collapsed !== undefined ? collapsed : collapsedLocal
|
||||
|
||||
return (
|
||||
<div className={cn('mb-0.5', !!subTitle && 'mb-1')}>
|
||||
<div
|
||||
className='group/collapse flex items-center justify-between py-1'
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
setCollapsedLocal(!collapsedMerged)
|
||||
onCollapse?.(!collapsedMerged)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className='system-sm-semibold-uppercase flex items-center text-text-secondary'>
|
||||
{title}
|
||||
{
|
||||
showArrow && (
|
||||
<ArrowDownRoundFill
|
||||
className={cn(
|
||||
'h-4 w-4 cursor-pointer text-text-quaternary group-hover/collapse:text-text-secondary',
|
||||
collapsedMerged && 'rotate-[270deg]',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
tooltip && (
|
||||
<Tooltip
|
||||
popupContent={tooltip}
|
||||
triggerClassName='w-4 h-4 ml-1'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{operation}
|
||||
</div>
|
||||
{
|
||||
subTitle
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type { FieldTitleProps } from '.'
|
||||
import { FieldTitle } from '.'
|
||||
|
||||
export type FieldProps = {
|
||||
fieldTitleProps?: FieldTitleProps
|
||||
children?: ReactNode
|
||||
disabled?: boolean
|
||||
supportCollapse?: boolean
|
||||
}
|
||||
export const Field = memo(({
|
||||
fieldTitleProps,
|
||||
children,
|
||||
supportCollapse,
|
||||
disabled,
|
||||
}: FieldProps) => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FieldTitle
|
||||
{...fieldTitleProps}
|
||||
collapsed={collapsed}
|
||||
onCollapse={setCollapsed}
|
||||
showArrow={supportCollapse}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{supportCollapse && !collapsed && children}
|
||||
{!supportCollapse && children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type {
|
||||
FieldProps,
|
||||
GroupProps,
|
||||
} from '.'
|
||||
import {
|
||||
Field,
|
||||
Group,
|
||||
} from '.'
|
||||
|
||||
type GroupFieldProps = {
|
||||
children?: ReactNode
|
||||
groupProps?: Omit<GroupProps, 'children'>
|
||||
fieldProps?: Omit<FieldProps, 'children'>
|
||||
}
|
||||
export const GroupField = memo(({
|
||||
children,
|
||||
fieldProps,
|
||||
groupProps,
|
||||
}: GroupFieldProps) => {
|
||||
return (
|
||||
<Group {...groupProps}>
|
||||
<Field {...fieldProps}>
|
||||
{children}
|
||||
</Field>
|
||||
</Group>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type GroupProps = {
|
||||
className?: string
|
||||
children?: ReactNode
|
||||
withBorderBottom?: boolean
|
||||
}
|
||||
export const Group = memo(({
|
||||
className,
|
||||
children,
|
||||
withBorderBottom,
|
||||
}: GroupProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'px-4 py-2',
|
||||
withBorderBottom && 'border-b border-divider-subtle',
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './box'
|
||||
export * from './group'
|
||||
export * from './box-group'
|
||||
export * from './field-title'
|
||||
export * from './field'
|
||||
export * from './group-field'
|
||||
export * from './box-group-field'
|
||||
@@ -38,7 +38,7 @@ const Add = ({
|
||||
const [open, setOpen] = useState(false)
|
||||
const { handleNodeAdd } = useNodesInteractions()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { availableNextBlocks } = useAvailableBlocks(nodeData.type, nodeData.isInIteration, nodeData.isInLoop)
|
||||
const { availableNextBlocks } = useAvailableBlocks(nodeData.type, nodeData.isInIteration || nodeData.isInLoop)
|
||||
const { checkParallelLimit } = useWorkflow()
|
||||
|
||||
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
||||
@@ -80,7 +80,7 @@ const Add = ({
|
||||
${nodesReadOnly && '!cursor-not-allowed'}
|
||||
`}
|
||||
>
|
||||
<div className='bg-background-default-dimm mr-1.5 flex h-5 w-5 items-center justify-center rounded-[5px]'>
|
||||
<div className='mr-1.5 flex h-5 w-5 items-center justify-center rounded-[5px] bg-background-default-dimmed'>
|
||||
<RiAddLine className='h-3 w-3' />
|
||||
</div>
|
||||
<div className='flex items-center uppercase'>
|
||||
|
||||
@@ -36,7 +36,7 @@ const ChangeItem = ({
|
||||
const {
|
||||
availablePrevBlocks,
|
||||
availableNextBlocks,
|
||||
} = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop)
|
||||
} = useAvailableBlocks(data.type, data.isInIteration || data.isInLoop)
|
||||
|
||||
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
||||
handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
|
||||
|
||||
@@ -47,7 +47,7 @@ export const NodeTargetHandle = memo(({
|
||||
const { handleNodeAdd } = useNodesInteractions()
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const connected = data._connectedTargetHandleIds?.includes(handleId)
|
||||
const { availablePrevBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop)
|
||||
const { availablePrevBlocks } = useAvailableBlocks(data.type, data.isInIteration || data.isInLoop)
|
||||
const isConnectable = !!availablePrevBlocks.length
|
||||
|
||||
const handleOpenChange = useCallback((v: boolean) => {
|
||||
@@ -129,7 +129,7 @@ export const NodeSourceHandle = memo(({
|
||||
const [open, setOpen] = useState(false)
|
||||
const { handleNodeAdd } = useNodesInteractions()
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop)
|
||||
const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration || data.isInLoop)
|
||||
const isConnectable = !!availableNextBlocks.length
|
||||
const isChatMode = useIsChatMode()
|
||||
const { checkParallelLimit } = useWorkflow()
|
||||
|
||||
@@ -30,7 +30,7 @@ const ChangeBlock = ({
|
||||
const {
|
||||
availablePrevBlocks,
|
||||
availableNextBlocks,
|
||||
} = useAvailableBlocks(nodeData.type, nodeData.isInIteration, nodeData.isInLoop)
|
||||
} = useAvailableBlocks(nodeData.type, nodeData.isInIteration || nodeData.isInLoop)
|
||||
|
||||
const availableNodes = useMemo(() => {
|
||||
if (availablePrevBlocks.length && availableNextBlocks.length)
|
||||
|
||||
@@ -31,7 +31,6 @@ const PanelOperator = ({
|
||||
crossAxis: 53,
|
||||
},
|
||||
onOpenChange,
|
||||
inNode,
|
||||
showHelpLink = true,
|
||||
}: PanelOperatorProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEdges } from 'reactflow'
|
||||
import { useNodeHelpLink } from '../../hooks/use-node-help-link'
|
||||
import ChangeBlock from './change-block'
|
||||
import {
|
||||
canRunBySingle,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import {
|
||||
useNodeDataUpdate,
|
||||
useNodesExtraData,
|
||||
useNodeMetaData,
|
||||
useNodesInteractions,
|
||||
useNodesReadOnly,
|
||||
useNodesSyncDraft,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { canFindTool } from '@/utils'
|
||||
|
||||
type PanelOperatorPopupProps = {
|
||||
id: string
|
||||
@@ -37,7 +30,6 @@ const PanelOperatorPopup = ({
|
||||
showHelpLink,
|
||||
}: PanelOperatorPopupProps) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const edges = useEdges()
|
||||
const {
|
||||
handleNodeDelete,
|
||||
@@ -48,41 +40,9 @@ const PanelOperatorPopup = ({
|
||||
const { handleNodeDataUpdate } = useNodeDataUpdate()
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
const edge = edges.find(edge => edge.target === id)
|
||||
const author = useMemo(() => {
|
||||
if (data.type !== BlockEnum.Tool)
|
||||
return nodesExtraData[data.type].author
|
||||
|
||||
if (data.provider_type === CollectionType.builtIn)
|
||||
return buildInTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.author
|
||||
|
||||
if (data.provider_type === CollectionType.workflow)
|
||||
return workflowTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.author
|
||||
|
||||
return customTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.author
|
||||
}, [data, nodesExtraData, buildInTools, customTools, workflowTools])
|
||||
|
||||
const about = useMemo(() => {
|
||||
if (data.type !== BlockEnum.Tool)
|
||||
return nodesExtraData[data.type].about
|
||||
|
||||
if (data.provider_type === CollectionType.builtIn)
|
||||
return buildInTools.find(toolWithProvider => canFindTool(toolWithProvider.id, data.provider_id))?.description[language]
|
||||
|
||||
if (data.provider_type === CollectionType.workflow)
|
||||
return workflowTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.description[language]
|
||||
|
||||
return customTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.description[language]
|
||||
}, [data, nodesExtraData, language, buildInTools, customTools, workflowTools])
|
||||
|
||||
const showChangeBlock = data.type !== BlockEnum.Start && !nodesReadOnly && data.type !== BlockEnum.Iteration && data.type !== BlockEnum.Loop
|
||||
|
||||
const link = useNodeHelpLink(data.type)
|
||||
|
||||
const nodeMetaData = useNodeMetaData({ id, data } as Node)
|
||||
const showChangeBlock = !nodeMetaData.isTypeFixed && !nodesReadOnly
|
||||
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
||||
|
||||
return (
|
||||
@@ -124,53 +84,65 @@ const PanelOperatorPopup = ({
|
||||
)
|
||||
}
|
||||
{
|
||||
data.type !== BlockEnum.Start && !nodesReadOnly && (
|
||||
!nodesReadOnly && (
|
||||
<>
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
onClosePopup()
|
||||
handleNodesCopy(id)
|
||||
}}
|
||||
>
|
||||
{t('workflow.common.copy')}
|
||||
<ShortcutsName keys={['ctrl', 'c']} />
|
||||
</div>
|
||||
<div
|
||||
className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
onClosePopup()
|
||||
handleNodesDuplicate(id)
|
||||
}}
|
||||
>
|
||||
{t('workflow.common.duplicate')}
|
||||
<ShortcutsName keys={['ctrl', 'd']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-px bg-divider-regular'></div>
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className={`
|
||||
flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary
|
||||
hover:bg-state-destructive-hover hover:text-red-500
|
||||
`}
|
||||
onClick={() => handleNodeDelete(id)}
|
||||
>
|
||||
{t('common.operation.delete')}
|
||||
<ShortcutsName keys={['del']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-px bg-divider-regular'></div>
|
||||
{
|
||||
!nodeMetaData.isSingleton && (
|
||||
<>
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
onClosePopup()
|
||||
handleNodesCopy(id)
|
||||
}}
|
||||
>
|
||||
{t('workflow.common.copy')}
|
||||
<ShortcutsName keys={['ctrl', 'c']} />
|
||||
</div>
|
||||
<div
|
||||
className='flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
onClosePopup()
|
||||
handleNodesDuplicate(id)
|
||||
}}
|
||||
>
|
||||
{t('workflow.common.duplicate')}
|
||||
<ShortcutsName keys={['ctrl', 'd']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-px bg-divider-regular'></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
!nodeMetaData.isUndeletable && (
|
||||
<>
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className={`
|
||||
flex h-8 cursor-pointer items-center justify-between rounded-lg px-3 text-sm text-text-secondary
|
||||
hover:bg-state-destructive-hover hover:text-text-destructive
|
||||
`}
|
||||
onClick={() => handleNodeDelete(id)}
|
||||
>
|
||||
{t('common.operation.delete')}
|
||||
<ShortcutsName keys={['del']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-px bg-divider-regular'></div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
showHelpLink && link && (
|
||||
showHelpLink && nodeMetaData.helpLinkUri && (
|
||||
<>
|
||||
<div className='p-1'>
|
||||
<a
|
||||
href={link}
|
||||
href={nodeMetaData.helpLinkUri}
|
||||
target='_blank'
|
||||
className='flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||
>
|
||||
@@ -186,9 +158,9 @@ const PanelOperatorPopup = ({
|
||||
<div className='mb-1 flex h-[22px] items-center font-medium'>
|
||||
{t('workflow.panel.about').toLocaleUpperCase()}
|
||||
</div>
|
||||
<div className='mb-1 leading-[18px] text-text-secondary'>{about}</div>
|
||||
<div className='mb-1 leading-[18px] text-text-secondary'>{nodeMetaData.description}</div>
|
||||
<div className='leading-[18px]'>
|
||||
{t('workflow.panel.createdBy')} {author}
|
||||
{t('workflow.panel.createdBy')} {nodeMetaData.author}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -150,6 +150,8 @@ const Editor: FC<Props> = ({
|
||||
}
|
||||
|
||||
const getVarType = useWorkflowVariableType()
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
|
||||
return (
|
||||
<Wrap className={cn(className, wrapClassName)} style={wrapStyle} isInNode isExpand={isExpand}>
|
||||
@@ -264,7 +266,7 @@ const Editor: FC<Props> = ({
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: nodesOutputVars || [],
|
||||
getVarType,
|
||||
getVarType: getVarType as any,
|
||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
@@ -281,6 +283,8 @@ const Editor: FC<Props> = ({
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
showManageInputField: !!pipelineId,
|
||||
onManageInputField: () => setShowInputFieldPanel?.(true),
|
||||
}}
|
||||
onChange={onChange}
|
||||
onBlur={setBlur}
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
VarType,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { getNodeInfoById, isConversationVar, isENV, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
import {
|
||||
VariableLabelInSelect,
|
||||
@@ -27,18 +27,19 @@ const VariableTag = ({
|
||||
availableNodes,
|
||||
}: VariableTagProps) => {
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const isRagVar = isRagVariableVar(valueSelector)
|
||||
const node = useMemo(() => {
|
||||
if (isSystemVar(valueSelector)) {
|
||||
const startNode = availableNodes?.find(n => n.data.type === BlockEnum.Start)
|
||||
if (startNode)
|
||||
return startNode
|
||||
}
|
||||
return getNodeInfoById(availableNodes || nodes, valueSelector[0])
|
||||
}, [nodes, valueSelector, availableNodes])
|
||||
return getNodeInfoById(availableNodes || nodes, isRagVar ? valueSelector[1] : valueSelector[0])
|
||||
}, [nodes, valueSelector, availableNodes, isRagVar])
|
||||
|
||||
const isEnv = isENV(valueSelector)
|
||||
const isChatVar = isConversationVar(valueSelector)
|
||||
const isValid = Boolean(node) || isEnv || isChatVar
|
||||
const isValid = Boolean(node) || isEnv || isChatVar || isRagVar
|
||||
|
||||
const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.')
|
||||
const isException = isExceptionVariable(variableName, node?.data.type)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
|
||||
type ManageInputFieldProps = {
|
||||
onManage: () => void
|
||||
}
|
||||
|
||||
const ManageInputField = ({
|
||||
onManage,
|
||||
}: ManageInputFieldProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex items-center border-t border-divider-subtle pt-1'>
|
||||
<div
|
||||
className='flex h-8 grow cursor-pointer items-center px-3'
|
||||
onClick={onManage}
|
||||
>
|
||||
<RiAddLine className='mr-1 h-4 w-4 text-text-tertiary' />
|
||||
<div
|
||||
className='system-xs-medium truncate text-text-tertiary'
|
||||
title='Create user input field'
|
||||
>
|
||||
{t('pipeline.inputField.create')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mx-1 h-3 w-[1px] shrink-0 bg-divider-regular'></div>
|
||||
<div
|
||||
className='system-xs-medium flex h-8 shrink-0 cursor-pointer items-center justify-center px-3 text-text-tertiary'
|
||||
onClick={onManage}
|
||||
>
|
||||
{t('pipeline.inputField.manage')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ManageInputField
|
||||
@@ -0,0 +1,162 @@
|
||||
import matchTheSchemaType from './match-schema-type'
|
||||
|
||||
describe('match the schema type', () => {
|
||||
it('should return true for identical primitive types', () => {
|
||||
expect(matchTheSchemaType({ type: 'string' }, { type: 'string' })).toBe(true)
|
||||
expect(matchTheSchemaType({ type: 'number' }, { type: 'number' })).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false for different primitive types', () => {
|
||||
expect(matchTheSchemaType({ type: 'string' }, { type: 'number' })).toBe(false)
|
||||
})
|
||||
|
||||
it('should ignore values and only compare types', () => {
|
||||
expect(matchTheSchemaType({ type: 'string', value: 'hello' }, { type: 'string', value: 'world' })).toBe(true)
|
||||
expect(matchTheSchemaType({ type: 'number', value: 42 }, { type: 'number', value: 100 })).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true for structural differences but no types', () => {
|
||||
expect(matchTheSchemaType({ type: 'string', other: { b: 'xxx' } }, { type: 'string', other: 'xxx' })).toBe(true)
|
||||
expect(matchTheSchemaType({ type: 'string', other: { b: 'xxx' } }, { type: 'string' })).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle nested objects with same structure and types', () => {
|
||||
const obj1 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
age: { type: 'number' },
|
||||
address: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
street: { type: 'string' },
|
||||
city: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
const obj2 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', value: 'Alice' },
|
||||
age: { type: 'number', value: 30 },
|
||||
address: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
street: { type: 'string', value: '123 Main St' },
|
||||
city: { type: 'string', value: 'Wonderland' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expect(matchTheSchemaType(obj1, obj2)).toBe(true)
|
||||
})
|
||||
it('should return false for nested objects with different structures', () => {
|
||||
const obj1 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
age: { type: 'number' },
|
||||
},
|
||||
}
|
||||
const obj2 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
address: { type: 'string' },
|
||||
},
|
||||
}
|
||||
expect(matchTheSchemaType(obj1, obj2)).toBe(false)
|
||||
})
|
||||
|
||||
it('file struct should match file type', () => {
|
||||
const fileSchema = {
|
||||
$id: 'https://dify.ai/schemas/v1/file.json',
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
version: '1.0.0',
|
||||
type: 'object',
|
||||
title: 'File Schema',
|
||||
description: 'Schema for file objects (v1)',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'file name',
|
||||
},
|
||||
size: {
|
||||
type: 'number',
|
||||
description: 'file size',
|
||||
},
|
||||
extension: {
|
||||
type: 'string',
|
||||
description: 'file extension',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'file type',
|
||||
},
|
||||
mime_type: {
|
||||
type: 'string',
|
||||
description: 'file mime type',
|
||||
},
|
||||
transfer_method: {
|
||||
type: 'string',
|
||||
description: 'file transfer method',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'file url',
|
||||
},
|
||||
related_id: {
|
||||
type: 'string',
|
||||
description: 'file related id',
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'name',
|
||||
],
|
||||
}
|
||||
const file = {
|
||||
type: 'object',
|
||||
title: 'File',
|
||||
description: 'Schema for file objects (v1)',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'file name',
|
||||
},
|
||||
size: {
|
||||
type: 'number',
|
||||
description: 'file size',
|
||||
},
|
||||
extension: {
|
||||
type: 'string',
|
||||
description: 'file extension',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
description: 'file type',
|
||||
},
|
||||
mime_type: {
|
||||
type: 'string',
|
||||
description: 'file mime type',
|
||||
},
|
||||
transfer_method: {
|
||||
type: 'string',
|
||||
description: 'file transfer method',
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'file url',
|
||||
},
|
||||
related_id: {
|
||||
type: 'string',
|
||||
description: 'file related id',
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'name',
|
||||
],
|
||||
}
|
||||
expect(matchTheSchemaType(fileSchema, file)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
export type AnyObj = Record<string, any> | null
|
||||
|
||||
const isObj = (x: any): x is object => x !== null && typeof x === 'object'
|
||||
|
||||
// only compare type in object
|
||||
function matchTheSchemaType(scheme: AnyObj, target: AnyObj): boolean {
|
||||
const isMatch = (schema: AnyObj, t: AnyObj): boolean => {
|
||||
const oSchema = isObj(schema)
|
||||
const oT = isObj(t)
|
||||
if(!oSchema)
|
||||
return true
|
||||
if (!oT) { // ignore the object without type
|
||||
// deep find oSchema has type
|
||||
for (const key in schema) {
|
||||
if (key === 'type')
|
||||
return false
|
||||
if (isObj((schema as any)[key]) && !isMatch((schema as any)[key], null))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
// check current `type`
|
||||
const tx = (schema as any).type
|
||||
const ty = (t as any).type
|
||||
const isTypeValueObj = isObj(tx)
|
||||
|
||||
if(!isTypeValueObj) // caution: type can be object, so that it would not be compare by value
|
||||
if (tx !== ty) return false
|
||||
|
||||
// recurse into all keys
|
||||
const keys = new Set([...Object.keys(schema as object), ...Object.keys(t as object)])
|
||||
for (const k of keys) {
|
||||
if (k === 'type' && !isTypeValueObj) continue // already checked
|
||||
if (!isMatch((schema as any)[k], (t as any)[k])) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return isMatch(scheme, target)
|
||||
}
|
||||
|
||||
export default matchTheSchemaType
|
||||
@@ -9,7 +9,7 @@ import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
root: { nodeId?: string, nodeName?: string, attrName: string }
|
||||
root: { nodeId?: string, nodeName?: string, attrName: string, attrAlias?: string }
|
||||
payload: StructuredOutput
|
||||
readonly?: boolean
|
||||
onSelect?: (valueSelector: ValueSelector) => void
|
||||
@@ -52,8 +52,7 @@ export const PickerPanelMain: FC<Props> = ({
|
||||
)}
|
||||
<div className='system-sm-medium text-text-secondary'>{root.attrName}</div>
|
||||
</div>
|
||||
{/* It must be object */}
|
||||
<div className='system-xs-regular ml-2 shrink-0 text-text-tertiary'>object</div>
|
||||
<div className='system-xs-regular ml-2 truncate text-text-tertiary' title={root.attrAlias || 'object'}>{root.attrAlias || 'object'}</div>
|
||||
</div>
|
||||
{fieldNames.map(name => (
|
||||
<Field
|
||||
|
||||
@@ -44,7 +44,7 @@ const Field: FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
<div className={cn('system-sm-medium ml-[7px] h-6 truncate leading-6 text-text-secondary', isRoot && rootClassName)}>{name}</div>
|
||||
<div className='system-xs-regular ml-3 shrink-0 leading-6 text-text-tertiary'>{getFieldType(payload)}</div>
|
||||
<div className='system-xs-regular ml-3 shrink-0 leading-6 text-text-tertiary'>{getFieldType(payload)}{(payload.schemaType && payload.schemaType !== 'file' && ` (${payload.schemaType})`)}</div>
|
||||
{required && <div className='system-2xs-medium-uppercase ml-3 leading-6 text-text-warning'>{t('app.structOutput.required')}</div>}
|
||||
</div>
|
||||
{payload.description && (
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { SchemaTypeDefinition } from '@/service/use-common'
|
||||
import { useSchemaTypeDefinitions } from '@/service/use-common'
|
||||
import type { AnyObj } from './match-schema-type'
|
||||
import matchTheSchemaType from './match-schema-type'
|
||||
|
||||
export const getMatchedSchemaType = (obj: AnyObj, schemaTypeDefinitions?: SchemaTypeDefinition[]): string => {
|
||||
if(!schemaTypeDefinitions) return ''
|
||||
const matched = schemaTypeDefinitions.find(def => matchTheSchemaType(obj, def.schema))
|
||||
return matched ? matched.name : ''
|
||||
}
|
||||
|
||||
const useMatchSchemaType = () => {
|
||||
const { data: schemaTypeDefinitions, isLoading } = useSchemaTypeDefinitions()
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
schemaTypeDefinitions,
|
||||
}
|
||||
}
|
||||
|
||||
export default useMatchSchemaType
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,14 +10,18 @@ import {
|
||||
RiMoreLine,
|
||||
} from '@remixicon/react'
|
||||
import produce from 'immer'
|
||||
import { useReactFlow, useStoreApi } from 'reactflow'
|
||||
import {
|
||||
useNodes,
|
||||
useReactFlow,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import RemoveButton from '../remove-button'
|
||||
import useAvailableVarList from '../../hooks/use-available-var-list'
|
||||
import VarReferencePopup from './var-reference-popup'
|
||||
import { getNodeInfoById, isConversationVar, isENV, isSystemVar, varTypeToStructType } from './utils'
|
||||
import { getNodeInfoById, isConversationVar, isENV, isRagVariableVar, isSystemVar, removeFileVars, varTypeToStructType } from './utils'
|
||||
import ConstantField from './constant-field'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { CommonNodeType, Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { type CredentialFormSchema, type FormOption, FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
@@ -59,6 +63,7 @@ type Props = {
|
||||
defaultVarKindType?: VarKindType
|
||||
onlyLeafNodeVar?: boolean
|
||||
filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean
|
||||
isFilterFileVar?: boolean
|
||||
availableNodes?: Node[]
|
||||
availableVars?: NodeOutPutVar[]
|
||||
isAddBtnTrigger?: boolean
|
||||
@@ -74,6 +79,7 @@ type Props = {
|
||||
zIndex?: number
|
||||
currentTool?: Tool
|
||||
currentProvider?: ToolWithProvider
|
||||
preferSchemaType?: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_VALUE_SELECTOR: Props['value'] = []
|
||||
@@ -90,6 +96,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
defaultVarKindType = VarKindType.constant,
|
||||
onlyLeafNodeVar,
|
||||
filterVar = () => true,
|
||||
isFilterFileVar,
|
||||
availableNodes: passedInAvailableNodes,
|
||||
availableVars: passedInAvailableVars,
|
||||
isAddBtnTrigger,
|
||||
@@ -105,14 +112,12 @@ const VarReferencePicker: FC<Props> = ({
|
||||
zIndex,
|
||||
currentTool,
|
||||
currentProvider,
|
||||
preferSchemaType,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const store = useStoreApi()
|
||||
const {
|
||||
getNodes,
|
||||
} = store.getState()
|
||||
const nodes = useNodes<CommonNodeType>()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const { getCurrentVariableType } = useWorkflowVariables()
|
||||
const { availableVars, availableNodesWithParent: availableNodes } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar,
|
||||
@@ -126,12 +131,12 @@ const VarReferencePicker: FC<Props> = ({
|
||||
return node.data.type === BlockEnum.Start
|
||||
})
|
||||
|
||||
const node = getNodes().find(n => n.id === nodeId)
|
||||
const isInIteration = !!node?.data.isInIteration
|
||||
const iterationNode = isInIteration ? getNodes().find(n => n.id === node.parentId) : null
|
||||
const node = nodes.find(n => n.id === nodeId)
|
||||
const isInIteration = !!(node?.data as any)?.isInIteration
|
||||
const iterationNode = isInIteration ? nodes.find(n => n.id === node?.parentId) : null
|
||||
|
||||
const isInLoop = !!node?.data.isInLoop
|
||||
const loopNode = isInLoop ? getNodes().find(n => n.id === node.parentId) : null
|
||||
const isInLoop = !!(node?.data as any)?.isInLoop
|
||||
const loopNode = isInLoop ? nodes.find(n => n.id === node?.parentId) : null
|
||||
|
||||
const triggerRef = useRef<HTMLDivElement>(null)
|
||||
const [triggerWidth, setTriggerWidth] = useState(TRIGGER_DEFAULT_WIDTH)
|
||||
@@ -143,7 +148,10 @@ const VarReferencePicker: FC<Props> = ({
|
||||
const [varKindType, setVarKindType] = useState<VarKindType>(defaultVarKindType)
|
||||
const isConstant = isSupportConstantValue && varKindType === VarKindType.constant
|
||||
|
||||
const outputVars = useMemo(() => (passedInAvailableVars || availableVars), [passedInAvailableVars, availableVars])
|
||||
const outputVars = useMemo(() => {
|
||||
const results = passedInAvailableVars || availableVars
|
||||
return isFilterFileVar ? removeFileVars(results) : results
|
||||
}, [passedInAvailableVars, availableVars, isFilterFileVar])
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
useEffect(() => {
|
||||
@@ -190,7 +198,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
}
|
||||
}, [value, hasValue, isConstant, isIterationVar, iterationNode, availableNodes, outputVarNodeId, startNode, isLoopVar, loopNode])
|
||||
|
||||
const isShowAPart = (value as ValueSelector).length > 2
|
||||
const isShowAPart = (value as ValueSelector).length > 2 && !isRagVariableVar((value as ValueSelector))
|
||||
|
||||
const varName = useMemo(() => {
|
||||
if (!hasValue)
|
||||
@@ -275,21 +283,24 @@ const VarReferencePicker: FC<Props> = ({
|
||||
}, [availableNodes, reactflow, store])
|
||||
|
||||
const type = getCurrentVariableType({
|
||||
parentNode: isInIteration ? iterationNode : loopNode,
|
||||
parentNode: (isInIteration ? iterationNode : loopNode) as any,
|
||||
valueSelector: value as ValueSelector,
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
isConstant: !!isConstant,
|
||||
preferSchemaType,
|
||||
})
|
||||
|
||||
const { isEnv, isChatVar, isValidVar, isException } = useMemo(() => {
|
||||
const { isEnv, isChatVar, isRagVar, isValidVar, isException } = useMemo(() => {
|
||||
const isEnv = isENV(value as ValueSelector)
|
||||
const isChatVar = isConversationVar(value as ValueSelector)
|
||||
const isValidVar = Boolean(outputVarNode) || isEnv || isChatVar
|
||||
const isRagVar = isRagVariableVar(value as ValueSelector)
|
||||
const isValidVar = Boolean(outputVarNode) || isEnv || isChatVar || isRagVar
|
||||
const isException = isExceptionVariable(varName, outputVarNode?.type)
|
||||
return {
|
||||
isEnv,
|
||||
isChatVar,
|
||||
isRagVar,
|
||||
isValidVar,
|
||||
isException,
|
||||
}
|
||||
@@ -382,8 +393,9 @@ const VarReferencePicker: FC<Props> = ({
|
||||
if (isEnv) return 'environment'
|
||||
if (isChatVar) return 'conversation'
|
||||
if (isLoopVar) return 'loop'
|
||||
if (isRagVar) return 'rag'
|
||||
return 'system'
|
||||
}, [isEnv, isChatVar, isLoopVar])
|
||||
}, [isEnv, isChatVar, isLoopVar, isRagVar])
|
||||
|
||||
return (
|
||||
<div className={cn(className, !readonly && 'cursor-pointer')}>
|
||||
@@ -455,7 +467,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
{hasValue
|
||||
? (
|
||||
<>
|
||||
{isShowNodeName && !isEnv && !isChatVar && (
|
||||
{isShowNodeName && !isEnv && !isChatVar && !isRagVar && (
|
||||
<div className='flex items-center' onClick={(e) => {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
e.stopPropagation()
|
||||
@@ -553,6 +565,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
itemWidth={isAddBtnTrigger ? 260 : (minWidth || triggerWidth)}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
zIndex={zIndex}
|
||||
preferSchemaType={preferSchemaType}
|
||||
/>
|
||||
)}
|
||||
</PortalToFollowElemContent>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import VarReferenceVars from './var-reference-vars'
|
||||
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import ListEmpty from '@/app/components/base/list-empty'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
|
||||
type Props = {
|
||||
@@ -14,6 +15,7 @@ type Props = {
|
||||
itemWidth?: number
|
||||
isSupportFileVar?: boolean
|
||||
zIndex?: number
|
||||
preferSchemaType?: boolean
|
||||
}
|
||||
const VarReferencePopup: FC<Props> = ({
|
||||
vars,
|
||||
@@ -22,8 +24,12 @@ const VarReferencePopup: FC<Props> = ({
|
||||
itemWidth,
|
||||
isSupportFileVar = true,
|
||||
zIndex,
|
||||
preferSchemaType,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const showManageRagInputFields = useMemo(() => !!pipelineId, [pipelineId])
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
const docLink = useDocLink()
|
||||
// max-h-[300px] overflow-y-auto todo: use portal to handle long list
|
||||
return (
|
||||
@@ -63,6 +69,9 @@ const VarReferencePopup: FC<Props> = ({
|
||||
itemWidth={itemWidth}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
zIndex={zIndex}
|
||||
showManageInputField={showManageRagInputFields}
|
||||
onManageInputField={() => setShowInputFieldPanel?.(true)}
|
||||
preferSchemaType={preferSchemaType}
|
||||
/>
|
||||
}
|
||||
</div >
|
||||
|
||||
@@ -16,11 +16,12 @@ import { checkKeys } from '@/utils/var'
|
||||
import type { StructuredOutput } from '../../../llm/types'
|
||||
import { Type } from '../../../llm/types'
|
||||
import PickerStructurePanel from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker'
|
||||
import { varTypeToStructType } from './utils'
|
||||
import { isSpecialVar, varTypeToStructType } from './utils'
|
||||
import type { Field } from '@/app/components/workflow/nodes/llm/types'
|
||||
import { FILE_STRUCT } from '@/app/components/workflow/constants'
|
||||
import { noop } from 'lodash-es'
|
||||
import { CodeAssistant, MagicEdit } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import ManageInputField from './manage-input-field'
|
||||
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
|
||||
@@ -33,6 +34,7 @@ type ObjectChildrenProps = {
|
||||
onHovering?: (value: boolean) => void
|
||||
itemWidth?: number
|
||||
isSupportFileVar?: boolean
|
||||
preferSchemaType?: boolean
|
||||
}
|
||||
|
||||
type ItemProps = {
|
||||
@@ -50,6 +52,7 @@ type ItemProps = {
|
||||
isInCodeGeneratorInstructionEditor?: boolean
|
||||
zIndex?: number
|
||||
className?: string
|
||||
preferSchemaType?: boolean
|
||||
}
|
||||
|
||||
const objVarTypes = [VarType.object, VarType.file]
|
||||
@@ -68,6 +71,7 @@ const Item: FC<ItemProps> = ({
|
||||
isInCodeGeneratorInstructionEditor,
|
||||
zIndex,
|
||||
className,
|
||||
preferSchemaType,
|
||||
}) => {
|
||||
const isStructureOutput = itemData.type === VarType.object && (itemData.children as StructuredOutput)?.schema?.properties
|
||||
const isFile = itemData.type === VarType.file && !isStructureOutput
|
||||
@@ -75,6 +79,7 @@ const Item: FC<ItemProps> = ({
|
||||
const isSys = itemData.variable.startsWith('sys.')
|
||||
const isEnv = itemData.variable.startsWith('env.')
|
||||
const isChatVar = itemData.variable.startsWith('conversation.')
|
||||
const isRagVariable = itemData.isRagVariable
|
||||
const flatVarIcon = useMemo(() => {
|
||||
if (!isFlat)
|
||||
return null
|
||||
@@ -156,7 +161,7 @@ const Item: FC<ItemProps> = ({
|
||||
if (isFlat) {
|
||||
onChange([itemData.variable], itemData)
|
||||
}
|
||||
else if (isSys || isEnv || isChatVar) { // system variable | environment variable | conversation variable
|
||||
else if (isSys || isEnv || isChatVar || isRagVariable) { // system variable | environment variable | conversation variable
|
||||
onChange([...objPath, ...itemData.variable.split('.')], itemData)
|
||||
}
|
||||
else {
|
||||
@@ -167,8 +172,9 @@ const Item: FC<ItemProps> = ({
|
||||
if (isEnv) return 'environment'
|
||||
if (isChatVar) return 'conversation'
|
||||
if (isLoopVar) return 'loop'
|
||||
if (isRagVariable) return 'rag'
|
||||
return 'system'
|
||||
}, [isEnv, isChatVar, isSys, isLoopVar])
|
||||
}, [isEnv, isChatVar, isSys, isLoopVar, isRagVariable])
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
@@ -195,7 +201,7 @@ const Item: FC<ItemProps> = ({
|
||||
/>}
|
||||
{isFlat && flatVarIcon}
|
||||
|
||||
{!isEnv && !isChatVar && (
|
||||
{!isEnv && !isChatVar && !isRagVariable && (
|
||||
<div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{varName}</div>
|
||||
)}
|
||||
{isEnv && (
|
||||
@@ -204,8 +210,11 @@ const Item: FC<ItemProps> = ({
|
||||
{isChatVar && (
|
||||
<div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('conversation.', '')}</div>
|
||||
)}
|
||||
{isRagVariable && (
|
||||
<div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.split('.').slice(-1)[0]}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary'>{itemData.type}</div>
|
||||
<div className='ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary'>{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : itemData.type}</div>
|
||||
{
|
||||
(isObj || isStructureOutput) && (
|
||||
<ChevronRight className={cn('ml-0.5 h-3 w-3 text-text-quaternary', isHovering && 'text-text-tertiary')} />
|
||||
@@ -218,7 +227,7 @@ const Item: FC<ItemProps> = ({
|
||||
}}>
|
||||
{(isStructureOutput || isObj) && (
|
||||
<PickerStructurePanel
|
||||
root={{ nodeId, nodeName: title, attrName: itemData.variable }}
|
||||
root={{ nodeId, nodeName: title, attrName: itemData.variable, attrAlias: itemData.schemaType }}
|
||||
payload={structuredOutput!}
|
||||
onHovering={setIsChildrenHovering}
|
||||
onSelect={(valueSelector) => {
|
||||
@@ -240,6 +249,7 @@ const ObjectChildren: FC<ObjectChildrenProps> = ({
|
||||
onHovering,
|
||||
itemWidth,
|
||||
isSupportFileVar,
|
||||
preferSchemaType,
|
||||
}) => {
|
||||
const currObjPath = objPath
|
||||
const itemRef = useRef<HTMLDivElement>(null)
|
||||
@@ -284,6 +294,7 @@ const ObjectChildren: FC<ObjectChildrenProps> = ({
|
||||
onHovering={setIsChildrenHovering}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isException={v.isException}
|
||||
preferSchemaType={preferSchemaType}
|
||||
/>
|
||||
))
|
||||
}
|
||||
@@ -303,7 +314,10 @@ type Props = {
|
||||
onBlur?: () => void
|
||||
zIndex?: number
|
||||
isInCodeGeneratorInstructionEditor?: boolean
|
||||
showManageInputField?: boolean
|
||||
onManageInputField?: () => void
|
||||
autoFocus?: boolean
|
||||
preferSchemaType?: boolean
|
||||
}
|
||||
const VarReferenceVars: FC<Props> = ({
|
||||
hideSearch,
|
||||
@@ -317,7 +331,10 @@ const VarReferenceVars: FC<Props> = ({
|
||||
onBlur,
|
||||
zIndex,
|
||||
isInCodeGeneratorInstructionEditor,
|
||||
showManageInputField,
|
||||
onManageInputField,
|
||||
autoFocus = true,
|
||||
preferSchemaType,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [searchText, setSearchText] = useState('')
|
||||
@@ -330,7 +347,7 @@ const VarReferenceVars: FC<Props> = ({
|
||||
}
|
||||
|
||||
const filteredVars = vars.filter((v) => {
|
||||
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.') || v.variable.startsWith('conversation.'))
|
||||
const children = v.vars.filter(v => checkKeys([v.variable], false).isValid || isSpecialVar(v.variable.split('.')[0]))
|
||||
return children.length > 0
|
||||
}).filter((node) => {
|
||||
if (!searchText)
|
||||
@@ -341,7 +358,7 @@ const VarReferenceVars: FC<Props> = ({
|
||||
})
|
||||
return children.length > 0
|
||||
}).map((node) => {
|
||||
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || v.variable.startsWith('sys.') || v.variable.startsWith('env.') || v.variable.startsWith('conversation.'))
|
||||
let vars = node.vars.filter(v => checkKeys([v.variable], false).isValid || isSpecialVar(v.variable.split('.')[0]))
|
||||
if (searchText) {
|
||||
const searchTextLower = searchText.toLowerCase()
|
||||
if (!node.title.toLowerCase().includes(searchTextLower))
|
||||
@@ -407,6 +424,7 @@ const VarReferenceVars: FC<Props> = ({
|
||||
isFlat={item.isFlat}
|
||||
isInCodeGeneratorInstructionEditor={isInCodeGeneratorInstructionEditor}
|
||||
zIndex={zIndex}
|
||||
preferSchemaType={preferSchemaType}
|
||||
/>
|
||||
))}
|
||||
{item.isFlat && !filteredVars[i + 1]?.isFlat && !!filteredVars.find(item => !item.isFlat) && (
|
||||
@@ -420,6 +438,13 @@ const VarReferenceVars: FC<Props> = ({
|
||||
}
|
||||
</div>
|
||||
: <div className='mt-2 pl-3 text-xs font-medium uppercase leading-[18px] text-gray-500'>{t('workflow.common.noVar')}</div>}
|
||||
{
|
||||
showManageInputField && (
|
||||
<ManageInputField
|
||||
onManage={onManageInputField || noop}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ import { useMemo } from 'react'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { Loop } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import { InputField } from '@/app/components/base/icons/src/vender/pipeline'
|
||||
import {
|
||||
isConversationVar,
|
||||
isENV,
|
||||
isRagVariableVar,
|
||||
isSystemVar,
|
||||
} from '../utils'
|
||||
import { VarInInspectType } from '@/types/workflow'
|
||||
@@ -13,6 +15,9 @@ export const useVarIcon = (variables: string[], variableCategory?: VarInInspectT
|
||||
if (variableCategory === 'loop')
|
||||
return Loop
|
||||
|
||||
if (variableCategory === 'rag' || isRagVariableVar(variables))
|
||||
return InputField
|
||||
|
||||
if (isENV(variables) || variableCategory === VarInInspectType.environment || variableCategory === 'environment')
|
||||
return Env
|
||||
|
||||
@@ -41,7 +46,11 @@ export const useVarColor = (variables: string[], isExceptionVariable?: boolean,
|
||||
}
|
||||
|
||||
export const useVarName = (variables: string[], notShowFullPath?: boolean) => {
|
||||
const variableFullPathName = variables.slice(1).join('.')
|
||||
let variableFullPathName = variables.slice(1).join('.')
|
||||
|
||||
if (isRagVariableVar(variables))
|
||||
variableFullPathName = variables.slice(2).join('.')
|
||||
|
||||
const variablesLength = variables.length
|
||||
const varName = useMemo(() => {
|
||||
const isSystem = isSystemVar(variables)
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
FC,
|
||||
ReactNode,
|
||||
} from 'react'
|
||||
import {
|
||||
import React, {
|
||||
cloneElement,
|
||||
memo,
|
||||
useCallback,
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
useAvailableBlocks,
|
||||
useNodeDataUpdate,
|
||||
useNodesInteractions,
|
||||
useNodesMetaData,
|
||||
useNodesReadOnly,
|
||||
useToolIcon,
|
||||
useWorkflowHistory,
|
||||
@@ -44,6 +45,7 @@ import {
|
||||
canRunBySingle,
|
||||
hasErrorHandleNode,
|
||||
hasRetryNode,
|
||||
isSupportCustomRunForm,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { BlockEnum, type Node, NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
@@ -54,18 +56,35 @@ import LastRun from './last-run'
|
||||
import useLastRun from './last-run/use-last-run'
|
||||
import BeforeRunForm from '../before-run-form'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { NODES_EXTRA_DATA } from '@/app/components/workflow/constants'
|
||||
import { useLogs } from '@/app/components/workflow/run/hooks'
|
||||
import PanelWrap from '../before-run-form/panel-wrap'
|
||||
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
|
||||
import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import { useHooksStore } from '@/app/components/workflow/hooks-store'
|
||||
import { FlowType } from '@/types/common'
|
||||
import {
|
||||
AuthorizedInDataSourceNode,
|
||||
AuthorizedInNode,
|
||||
PluginAuth,
|
||||
PluginAuthInDataSourceNode,
|
||||
} from '@/app/components/plugins/plugin-auth'
|
||||
import { AuthCategory } from '@/app/components/plugins/plugin-auth'
|
||||
import { canFindTool } from '@/utils'
|
||||
import type { CustomRunFormProps } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { DataSourceClassification } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import DataSourceBeforeRunForm from '@/app/components/workflow/nodes/data-source/before-run-form'
|
||||
import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud'
|
||||
|
||||
const getCustomRunForm = (params: CustomRunFormProps): React.JSX.Element => {
|
||||
const nodeType = params.payload.type
|
||||
switch (nodeType) {
|
||||
case BlockEnum.DataSource:
|
||||
return <DataSourceBeforeRunForm {...params} />
|
||||
default:
|
||||
return <div>Custom Run Form: {nodeType} not found</div>
|
||||
}
|
||||
}
|
||||
type BasePanelProps = {
|
||||
children: ReactNode
|
||||
id: Node['id']
|
||||
@@ -142,7 +161,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
|
||||
const { handleNodeSelect } = useNodesInteractions()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop)
|
||||
const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration || data.isInLoop)
|
||||
const toolIcon = useToolIcon(data)
|
||||
|
||||
const { saveStateToHistory } = useWorkflowHistory()
|
||||
@@ -169,11 +188,11 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
const [isPaused, setIsPaused] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if(data._singleRunningStatus === NodeRunningStatus.Running) {
|
||||
if (data._singleRunningStatus === NodeRunningStatus.Running) {
|
||||
hasClickRunning.current = true
|
||||
setIsPaused(false)
|
||||
}
|
||||
else if(data._isSingleRun && data._singleRunningStatus === undefined && hasClickRunning) {
|
||||
else if (data._isSingleRun && data._singleRunningStatus === undefined && hasClickRunning) {
|
||||
setIsPaused(true)
|
||||
hasClickRunning.current = false
|
||||
}
|
||||
@@ -190,10 +209,13 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
}, [handleNodeDataUpdate, id, data])
|
||||
|
||||
useEffect(() => {
|
||||
// console.log(`id changed: ${id}, hasClickRunning: ${hasClickRunning.current}`)
|
||||
hasClickRunning.current = false
|
||||
}, [id])
|
||||
const {
|
||||
nodesMap,
|
||||
} = useNodesMetaData()
|
||||
|
||||
const configsMap = useHooksStore(s => s.configsMap)
|
||||
const {
|
||||
isShowSingleRun,
|
||||
hideSingleRun,
|
||||
@@ -201,11 +223,14 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
runInputData,
|
||||
runInputDataRef,
|
||||
runResult,
|
||||
setRunResult,
|
||||
getInputVars,
|
||||
toVarInputs,
|
||||
tabType,
|
||||
isRunAfterSingleRun,
|
||||
setIsRunAfterSingleRun,
|
||||
setTabType,
|
||||
handleAfterCustomSingleRun,
|
||||
singleRunParams,
|
||||
nodeInfo,
|
||||
setRunInputData,
|
||||
@@ -215,8 +240,10 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
getFilteredExistVarForms,
|
||||
} = useLastRun<typeof data>({
|
||||
id,
|
||||
flowId: configsMap?.flowId || '',
|
||||
flowType: configsMap?.flowType || FlowType.appFlow,
|
||||
data,
|
||||
defaultRunInputData: NODES_EXTRA_DATA[data.type]?.defaultRunInputData || {},
|
||||
defaultRunInputData: nodesMap?.[data.type]?.defaultRunInputData || {},
|
||||
isPaused,
|
||||
})
|
||||
|
||||
@@ -239,6 +266,11 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
const showPluginAuth = useMemo(() => {
|
||||
return data.type === BlockEnum.Tool && currCollection?.allow_delete
|
||||
}, [currCollection, data.type])
|
||||
const dataSourceList = useStore(s => s.dataSourceList)
|
||||
const currentDataSource = useMemo(() => {
|
||||
if (data.type === BlockEnum.DataSource && data.provider_type !== DataSourceClassification.localFile)
|
||||
return dataSourceList?.find(item => item.plugin_id === data.plugin_id)
|
||||
}, [dataSourceList, data.plugin_id, data.type, data.provider_type])
|
||||
const handleAuthorizationItemClick = useCallback((credential_id: string) => {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
@@ -247,8 +279,16 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
},
|
||||
})
|
||||
}, [handleNodeDataUpdateWithSyncDraft, id])
|
||||
const { setShowAccountSettingModal } = useModalContext()
|
||||
const handleJumpToDataSourcePage = useCallback(() => {
|
||||
setShowAccountSettingModal({ payload: 'data-source' })
|
||||
}, [setShowAccountSettingModal])
|
||||
|
||||
if(logParams.showSpecialResultPanel) {
|
||||
const {
|
||||
appendNodeInspectVars,
|
||||
} = useInspectVarsCrud()
|
||||
|
||||
if (logParams.showSpecialResultPanel) {
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative mr-1 h-full',
|
||||
@@ -274,6 +314,20 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
}
|
||||
|
||||
if (isShowSingleRun) {
|
||||
const form = getCustomRunForm({
|
||||
nodeId: id,
|
||||
flowId: configsMap?.flowId || '',
|
||||
flowType: configsMap?.flowType || FlowType.appFlow,
|
||||
payload: data,
|
||||
setRunResult,
|
||||
setIsRunAfterSingleRun,
|
||||
isPaused,
|
||||
isRunAfterSingleRun,
|
||||
onSuccess: handleAfterCustomSingleRun,
|
||||
onCancel: hideSingleRun,
|
||||
appendNodeInspectVars,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative mr-1 h-full',
|
||||
@@ -285,26 +339,36 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
width: `${nodePanelWidth}px`,
|
||||
}}
|
||||
>
|
||||
<BeforeRunForm
|
||||
nodeName={data.title}
|
||||
nodeType={data.type}
|
||||
onHide={hideSingleRun}
|
||||
onRun={handleRunWithParams}
|
||||
{...singleRunParams!}
|
||||
{...passedLogParams}
|
||||
existVarValuesInForms={getExistVarValuesInForms(singleRunParams?.forms as any)}
|
||||
filteredExistVarForms={getFilteredExistVarForms(singleRunParams?.forms as any)}
|
||||
/>
|
||||
{isSupportCustomRunForm(data.type) ? (
|
||||
form
|
||||
) : (
|
||||
<BeforeRunForm
|
||||
nodeName={data.title}
|
||||
nodeType={data.type}
|
||||
onHide={hideSingleRun}
|
||||
onRun={handleRunWithParams}
|
||||
{...singleRunParams!}
|
||||
{...passedLogParams}
|
||||
existVarValuesInForms={getExistVarValuesInForms(singleRunParams?.forms as any)}
|
||||
filteredExistVarForms={getFilteredExistVarForms(singleRunParams?.forms as any)}
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative mr-1 h-full',
|
||||
showMessageLogModal && '!absolute -top-[5px] right-[416px] z-0 !mr-0 w-[384px] overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border shadow-lg transition-all',
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
'relative mr-1 h-full',
|
||||
showMessageLogModal && 'absolute z-0 mr-2 w-[400px] overflow-hidden rounded-2xl border-[0.5px] border-components-panel-border shadow-lg transition-all',
|
||||
)}
|
||||
style={{
|
||||
right: !showMessageLogModal ? '0' : `${otherPanelWidth}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={triggerRef}
|
||||
className='absolute -left-1 top-0 flex h-full w-1 cursor-col-resize resize-x items-center justify-center'>
|
||||
@@ -312,7 +376,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
</div>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
||||
className={cn('flex h-full flex-col rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg transition-[width] ease-linear', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
|
||||
style={{
|
||||
width: `${nodePanelWidth}px`,
|
||||
}}
|
||||
@@ -340,7 +404,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
<div
|
||||
className='mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
if(isSingleRunning) {
|
||||
if (isSingleRunning) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
@@ -407,7 +471,26 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
)
|
||||
}
|
||||
{
|
||||
!showPluginAuth && (
|
||||
!!currentDataSource && (
|
||||
<PluginAuthInDataSourceNode
|
||||
onJumpToDataSourcePage={handleJumpToDataSourcePage}
|
||||
isAuthorized={currentDataSource.is_authorized}
|
||||
>
|
||||
<div className='flex items-center justify-between pl-4 pr-3'>
|
||||
<Tab
|
||||
value={tabType}
|
||||
onChange={setTabType}
|
||||
/>
|
||||
<AuthorizedInDataSourceNode
|
||||
onJumpToDataSourcePage={handleJumpToDataSourcePage}
|
||||
authorizationsNum={3}
|
||||
/>
|
||||
</div>
|
||||
</PluginAuthInDataSourceNode>
|
||||
)
|
||||
}
|
||||
{
|
||||
!showPluginAuth && !currentDataSource && (
|
||||
<div className='flex items-center justify-between pl-4 pr-3'>
|
||||
<Tab
|
||||
value={tabType}
|
||||
|
||||
@@ -8,6 +8,8 @@ import NoData from './no-data'
|
||||
import { useLastRun } from '@/service/use-workflow'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import { useHooksStore } from '@/app/components/workflow/hooks-store'
|
||||
import { FlowType } from '@/types/common'
|
||||
|
||||
type Props = {
|
||||
appId: string
|
||||
@@ -35,6 +37,7 @@ const LastRun: FC<Props> = ({
|
||||
isPaused,
|
||||
...otherResultPanelProps
|
||||
}) => {
|
||||
const configsMap = useHooksStore(s => s.configsMap)
|
||||
const isOneStepRunSucceed = oneStepRunRunningStatus === NodeRunningStatus.Succeeded
|
||||
const isOneStepRunFailed = oneStepRunRunningStatus === NodeRunningStatus.Failed
|
||||
// hide page and return to page would lost the oneStepRunRunningStatus
|
||||
@@ -44,7 +47,7 @@ const LastRun: FC<Props> = ({
|
||||
|
||||
const hidePageOneStepRunFinished = [NodeRunningStatus.Succeeded, NodeRunningStatus.Failed].includes(hidePageOneStepFinishedStatus!)
|
||||
const canRunLastRun = !isRunAfterSingleRun || isOneStepRunSucceed || isOneStepRunFailed || (pageHasHide && hidePageOneStepRunFinished)
|
||||
const { data: lastRunResult, isFetching, error } = useLastRun(appId, nodeId, canRunLastRun)
|
||||
const { data: lastRunResult, isFetching, error } = useLastRun(configsMap?.flowType || FlowType.appFlow, configsMap?.flowId || '', nodeId, canRunLastRun)
|
||||
const isRunning = useMemo(() => {
|
||||
if(isPaused)
|
||||
return false
|
||||
|
||||
@@ -19,6 +19,7 @@ import useLoopSingleRunFormParams from '@/app/components/workflow/nodes/loop/use
|
||||
import useIfElseSingleRunFormParams from '@/app/components/workflow/nodes/if-else/use-single-run-form-params'
|
||||
import useVariableAggregatorSingleRunFormParams from '@/app/components/workflow/nodes/variable-assigner/use-single-run-form-params'
|
||||
import useVariableAssignerSingleRunFormParams from '@/app/components/workflow/nodes/assigner/use-single-run-form-params'
|
||||
import useKnowledgeBaseSingleRunFormParams from '@/app/components/workflow/nodes/knowledge-base/use-single-run-form-params'
|
||||
|
||||
import useToolGetDataForCheckMore from '@/app/components/workflow/nodes/tool/use-get-data-for-check-more'
|
||||
import { VALUE_SELECTOR_DELIMITER as DELIMITER } from '@/config'
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
import useInspectVarsCrud from '@/app/components/workflow/hooks/use-inspect-vars-crud'
|
||||
import { useInvalidLastRun } from '@/service/use-workflow'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { isSupportCustomRunForm } from '@/app/components/workflow/utils'
|
||||
|
||||
const singleRunFormParamsHooks: Record<BlockEnum, any> = {
|
||||
[BlockEnum.LLM]: useLLMSingleRunFormParams,
|
||||
@@ -50,6 +52,7 @@ const singleRunFormParamsHooks: Record<BlockEnum, any> = {
|
||||
[BlockEnum.IfElse]: useIfElseSingleRunFormParams,
|
||||
[BlockEnum.VariableAggregator]: useVariableAggregatorSingleRunFormParams,
|
||||
[BlockEnum.Assigner]: useVariableAssignerSingleRunFormParams,
|
||||
[BlockEnum.KnowledgeBase]: useKnowledgeBaseSingleRunFormParams,
|
||||
[BlockEnum.VariableAssigner]: undefined,
|
||||
[BlockEnum.End]: undefined,
|
||||
[BlockEnum.Answer]: undefined,
|
||||
@@ -57,6 +60,8 @@ const singleRunFormParamsHooks: Record<BlockEnum, any> = {
|
||||
[BlockEnum.IterationStart]: undefined,
|
||||
[BlockEnum.LoopStart]: undefined,
|
||||
[BlockEnum.LoopEnd]: undefined,
|
||||
[BlockEnum.DataSource]: undefined,
|
||||
[BlockEnum.DataSourceEmpty]: undefined,
|
||||
}
|
||||
|
||||
const useSingleRunFormParamsHooks = (nodeType: BlockEnum) => {
|
||||
@@ -89,6 +94,9 @@ const getDataForCheckMoreHooks: Record<BlockEnum, any> = {
|
||||
[BlockEnum.Assigner]: undefined,
|
||||
[BlockEnum.LoopStart]: undefined,
|
||||
[BlockEnum.LoopEnd]: undefined,
|
||||
[BlockEnum.DataSource]: undefined,
|
||||
[BlockEnum.DataSourceEmpty]: undefined,
|
||||
[BlockEnum.KnowledgeBase]: undefined,
|
||||
}
|
||||
|
||||
const useGetDataForCheckMoreHooks = <T>(nodeType: BlockEnum) => {
|
||||
@@ -111,6 +119,7 @@ const useLastRun = <T>({
|
||||
const isIterationNode = blockType === BlockEnum.Iteration
|
||||
const isLoopNode = blockType === BlockEnum.Loop
|
||||
const isAggregatorNode = blockType === BlockEnum.VariableAggregator
|
||||
const isCustomRunNode = isSupportCustomRunForm(blockType)
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const {
|
||||
getData: getDataForCheckMore,
|
||||
@@ -119,6 +128,8 @@ const useLastRun = <T>({
|
||||
|
||||
const {
|
||||
id,
|
||||
flowId,
|
||||
flowType,
|
||||
data,
|
||||
} = oneStepRunParams
|
||||
const oneStepRunRes = useOneStepRun({
|
||||
@@ -129,7 +140,6 @@ const useLastRun = <T>({
|
||||
})
|
||||
|
||||
const {
|
||||
appId,
|
||||
hideSingleRun,
|
||||
handleRun: doCallRunApi,
|
||||
getInputVars,
|
||||
@@ -164,7 +174,7 @@ const useLastRun = <T>({
|
||||
})
|
||||
|
||||
const toSubmitData = useCallback((data: Record<string, any>) => {
|
||||
if(!isIterationNode && !isLoopNode)
|
||||
if (!isIterationNode && !isLoopNode)
|
||||
return data
|
||||
|
||||
const allVarObject = singleRunParams?.allVarObject || {}
|
||||
@@ -173,7 +183,7 @@ const useLastRun = <T>({
|
||||
const [varSectorStr, nodeId] = key.split(DELIMITER)
|
||||
formattedData[`${nodeId}.${allVarObject[key].inSingleRunPassedKey}`] = data[varSectorStr]
|
||||
})
|
||||
if(isIterationNode) {
|
||||
if (isIterationNode) {
|
||||
const iteratorInputKey = `${id}.input_selector`
|
||||
formattedData[iteratorInputKey] = data[iteratorInputKey]
|
||||
}
|
||||
@@ -193,16 +203,16 @@ const useLastRun = <T>({
|
||||
const initShowLastRunTab = useStore(s => s.initShowLastRunTab)
|
||||
const [tabType, setTabType] = useState<TabType>(initShowLastRunTab ? TabType.lastRun : TabType.settings)
|
||||
useEffect(() => {
|
||||
if(initShowLastRunTab)
|
||||
if (initShowLastRunTab)
|
||||
setTabType(TabType.lastRun)
|
||||
|
||||
setInitShowLastRunTab(false)
|
||||
}, [initShowLastRunTab])
|
||||
const invalidLastRun = useInvalidLastRun(appId!, id)
|
||||
const invalidLastRun = useInvalidLastRun(flowType, flowId, id)
|
||||
|
||||
const handleRunWithParams = async (data: Record<string, any>) => {
|
||||
const { isValid } = checkValid()
|
||||
if(!isValid)
|
||||
if (!isValid)
|
||||
return
|
||||
setNodeRunning()
|
||||
setIsRunAfterSingleRun(true)
|
||||
@@ -226,14 +236,14 @@ const useLastRun = <T>({
|
||||
const values: Record<string, boolean> = {}
|
||||
form.inputs.forEach(({ variable, getVarValueFromDependent }) => {
|
||||
const isGetValueFromDependent = getVarValueFromDependent || !variable.includes('.')
|
||||
if(isGetValueFromDependent && !singleRunParams?.getDependentVar)
|
||||
if (isGetValueFromDependent && !singleRunParams?.getDependentVar)
|
||||
return
|
||||
|
||||
const selector = isGetValueFromDependent ? (singleRunParams?.getDependentVar(variable) || []) : variable.slice(1, -1).split('.')
|
||||
if(!selector || selector.length === 0)
|
||||
if (!selector || selector.length === 0)
|
||||
return
|
||||
const [nodeId, varName] = selector.slice(0, 2)
|
||||
if(!isStartNode && nodeId === id) { // inner vars like loop vars
|
||||
if (!isStartNode && nodeId === id) { // inner vars like loop vars
|
||||
values[variable] = true
|
||||
return
|
||||
}
|
||||
@@ -247,7 +257,7 @@ const useLastRun = <T>({
|
||||
}
|
||||
|
||||
const isAllVarsHasValue = (vars?: ValueSelector[]) => {
|
||||
if(!vars || vars.length === 0)
|
||||
if (!vars || vars.length === 0)
|
||||
return true
|
||||
return vars.every((varItem) => {
|
||||
const [nodeId, varName] = varItem.slice(0, 2)
|
||||
@@ -257,7 +267,7 @@ const useLastRun = <T>({
|
||||
}
|
||||
|
||||
const isSomeVarsHasValue = (vars?: ValueSelector[]) => {
|
||||
if(!vars || vars.length === 0)
|
||||
if (!vars || vars.length === 0)
|
||||
return true
|
||||
return vars.some((varItem) => {
|
||||
const [nodeId, varName] = varItem.slice(0, 2)
|
||||
@@ -284,7 +294,7 @@ const useLastRun = <T>({
|
||||
}
|
||||
|
||||
const checkAggregatorVarsSet = (vars: ValueSelector[][]) => {
|
||||
if(!vars || vars.length === 0)
|
||||
if (!vars || vars.length === 0)
|
||||
return true
|
||||
// in each group, at last one set is ok
|
||||
return vars.every((varItem) => {
|
||||
@@ -292,10 +302,20 @@ const useLastRun = <T>({
|
||||
})
|
||||
}
|
||||
|
||||
const handleAfterCustomSingleRun = () => {
|
||||
invalidLastRun()
|
||||
setTabType(TabType.lastRun)
|
||||
hideSingleRun()
|
||||
}
|
||||
|
||||
const handleSingleRun = () => {
|
||||
const { isValid } = checkValid()
|
||||
if(!isValid)
|
||||
if (!isValid)
|
||||
return
|
||||
if (isCustomRunNode) {
|
||||
showSingleRun()
|
||||
return
|
||||
}
|
||||
const vars = singleRunParams?.getDependentVars?.()
|
||||
// no need to input params
|
||||
if (isAggregatorNode ? checkAggregatorVarsSet(vars) : isAllVarsHasValue(vars)) {
|
||||
@@ -315,7 +335,9 @@ const useLastRun = <T>({
|
||||
...oneStepRunRes,
|
||||
tabType,
|
||||
isRunAfterSingleRun,
|
||||
setIsRunAfterSingleRun,
|
||||
setTabType: handleTabClicked,
|
||||
handleAfterCustomSingleRun,
|
||||
singleRunParams,
|
||||
nodeInfo,
|
||||
setRunInputData,
|
||||
|
||||
Reference in New Issue
Block a user