mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 09:28:04 +08:00
Merge branch 'feat/rag-2' into feat/workflow-draft-var-optimize
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,16 +188,16 @@ const FormItem: FC<Props> = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{ type === InputVarType.jsonObject && (
|
||||
{type === InputVarType.jsonObject && (
|
||||
<CodeEditor
|
||||
value={value}
|
||||
language={CodeLanguage.json}
|
||||
onChange={onChange}
|
||||
noWrapper
|
||||
className='bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1'
|
||||
placeholder={
|
||||
<div className='whitespace-pre'>{payload.json_schema}</div>
|
||||
}
|
||||
className='bg h-[80px] overflow-y-auto rounded-[10px] bg-components-input-bg-normal p-1'
|
||||
placeholder={
|
||||
<div className='whitespace-pre'>{payload.json_schema}</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{(type === InputVarType.singleFile) && (
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -192,6 +196,8 @@ const FormInputItem: FC<Props> = ({
|
||||
onChange={handleValueChange}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
showManageInputField={showManageInputField}
|
||||
onManageInputField={onManageInputField}
|
||||
/>
|
||||
)}
|
||||
{isNumber && isConstant && (
|
||||
|
||||
@ -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
|
||||
@ -53,9 +54,11 @@ const Editor: FC<Props> = ({
|
||||
|
||||
useEffect(() => {
|
||||
onFocusChange?.(isFocus)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isFocus])
|
||||
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
|
||||
return (
|
||||
<div className={cn(className, 'relative')}>
|
||||
<>
|
||||
@ -103,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,18 +1,15 @@
|
||||
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,
|
||||
@ -20,9 +17,6 @@ import {
|
||||
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 +31,6 @@ const PanelOperatorPopup = ({
|
||||
showHelpLink,
|
||||
}: PanelOperatorPopupProps) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const edges = useEdges()
|
||||
const {
|
||||
handleNodeDelete,
|
||||
@ -48,41 +41,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 nodeMetaData = useNodeMetaData({ id, data } as Node)
|
||||
const showChangeBlock = data.type !== BlockEnum.Start && !nodesReadOnly && data.type !== BlockEnum.Iteration && data.type !== BlockEnum.Loop
|
||||
|
||||
const link = useNodeHelpLink(data.type)
|
||||
|
||||
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
||||
|
||||
return (
|
||||
@ -149,28 +110,34 @@ const PanelOperatorPopup = ({
|
||||
</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.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-red-500
|
||||
`}
|
||||
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 +153,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>
|
||||
|
||||
@ -148,6 +148,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}>
|
||||
@ -261,7 +263,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,
|
||||
@ -278,6 +280,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,17 @@
|
||||
import { useSchemaTypeDefinitions } from '@/service/use-common'
|
||||
import type { AnyObj } from './match-schema-type'
|
||||
import matchTheSchemaType from './match-schema-type'
|
||||
|
||||
const useMatchSchemaType = () => {
|
||||
const { data: schemaTypeDefinitions } = useSchemaTypeDefinitions()
|
||||
const getMatchedSchemaType = (obj: AnyObj): string => {
|
||||
if(!schemaTypeDefinitions) return ''
|
||||
const matched = schemaTypeDefinitions.find(def => matchTheSchemaType(obj, def.schema))
|
||||
return matched ? matched.name : ''
|
||||
}
|
||||
return {
|
||||
getMatchedSchemaType,
|
||||
}
|
||||
}
|
||||
|
||||
export default useMatchSchemaType
|
||||
@ -19,9 +19,10 @@ import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants'
|
||||
import type { DocExtractorNodeType } from '../../../document-extractor/types'
|
||||
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
||||
import type { ConversationVariable, EnvironmentVariable, Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { ConversationVariable, EnvironmentVariable, Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types'
|
||||
import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { RAGPipelineVariable } from '@/models/pipeline'
|
||||
|
||||
import {
|
||||
AGENT_OUTPUT_STRUCT,
|
||||
@ -34,6 +35,9 @@ import {
|
||||
TEMPLATE_TRANSFORM_OUTPUT_STRUCT,
|
||||
TOOL_OUTPUT_STRUCT,
|
||||
} from '@/app/components/workflow/constants'
|
||||
import ToolNodeDefault from '@/app/components/workflow/nodes/tool/default'
|
||||
import DataSourceNodeDefault from '@/app/components/workflow/nodes/data-source/default'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import type { PromptItem } from '@/models/debug'
|
||||
import { VAR_REGEX } from '@/config'
|
||||
import type { AgentNodeType } from '../../../agent/types'
|
||||
@ -50,6 +54,16 @@ export const isConversationVar = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'conversation'
|
||||
}
|
||||
|
||||
export const isRagVariableVar = (valueSelector: ValueSelector) => {
|
||||
if (!valueSelector)
|
||||
return false
|
||||
return valueSelector[0] === 'rag'
|
||||
}
|
||||
|
||||
export const isSpecialVar = (prefix: string): boolean => {
|
||||
return ['sys', 'env', 'conversation', 'rag'].includes(prefix)
|
||||
}
|
||||
|
||||
export const hasValidChildren = (children: any): boolean => {
|
||||
return children && (
|
||||
(Array.isArray(children) && children.length > 0)
|
||||
@ -207,6 +221,7 @@ const findExceptVarInObject = (obj: any, filterVar: (payload: Var, selector: Val
|
||||
variable: obj.variable,
|
||||
type: isFile ? VarType.file : VarType.object,
|
||||
children: childrenResult,
|
||||
schemaType: obj.schemaType,
|
||||
}
|
||||
|
||||
return res
|
||||
@ -216,6 +231,9 @@ const formatItem = (
|
||||
item: any,
|
||||
isChatMode: boolean,
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean,
|
||||
allPluginInfoList: Record<string, ToolWithProvider[]>,
|
||||
ragVars?: Var[],
|
||||
getMatchedSchemaType = (_obj: any) => '',
|
||||
): NodeOutPutVar => {
|
||||
const { id, data } = item
|
||||
|
||||
@ -397,36 +415,8 @@ const formatItem = (
|
||||
}
|
||||
|
||||
case BlockEnum.Tool: {
|
||||
const {
|
||||
output_schema,
|
||||
} = data as ToolNodeType
|
||||
if (!output_schema) {
|
||||
res.vars = TOOL_OUTPUT_STRUCT
|
||||
}
|
||||
else {
|
||||
const outputSchema: any[] = []
|
||||
Object.keys(output_schema.properties).forEach((outputKey) => {
|
||||
const output = output_schema.properties[outputKey]
|
||||
const dataType = output.type
|
||||
outputSchema.push({
|
||||
variable: outputKey,
|
||||
type: dataType === 'array'
|
||||
? `array[${output.items?.type.slice(0, 1).toLocaleLowerCase()}${output.items?.type.slice(1)}]`
|
||||
: `${output.type.slice(0, 1).toLocaleLowerCase()}${output.type.slice(1)}`,
|
||||
description: output.description,
|
||||
children: output.type === 'object' ? {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: output.properties,
|
||||
},
|
||||
} : undefined,
|
||||
})
|
||||
})
|
||||
res.vars = [
|
||||
...TOOL_OUTPUT_STRUCT,
|
||||
...outputSchema,
|
||||
]
|
||||
}
|
||||
const toolOutputVars = ToolNodeDefault.getOutputVars?.(data as ToolNodeType, allPluginInfoList, [], { getMatchedSchemaType }) || []
|
||||
res.vars = toolOutputVars
|
||||
break
|
||||
}
|
||||
|
||||
@ -519,6 +509,13 @@ const formatItem = (
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.DataSource: {
|
||||
const payload = data as DataSourceNodeType
|
||||
const dataSourceVars = DataSourceNodeDefault.getOutputVars?.(payload, allPluginInfoList, ragVars, { getMatchedSchemaType }) || []
|
||||
res.vars = dataSourceVars
|
||||
break
|
||||
}
|
||||
|
||||
case 'env': {
|
||||
res.vars = data.envList.map((env: EnvironmentVariable) => {
|
||||
return {
|
||||
@ -540,6 +537,18 @@ const formatItem = (
|
||||
}) as Var[]
|
||||
break
|
||||
}
|
||||
|
||||
case 'rag': {
|
||||
res.vars = data.ragVariables.map((ragVar: RAGPipelineVariable) => {
|
||||
return {
|
||||
variable: `rag.shared.${ragVar.variable}`,
|
||||
type: inputVarTypeToVarType(ragVar.type as any),
|
||||
des: ragVar.label,
|
||||
isRagVariable: true,
|
||||
}
|
||||
}) as Var[]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const { error_strategy } = data
|
||||
@ -565,7 +574,7 @@ const formatItem = (
|
||||
const isCurrentMatched = filterVar(v, (() => {
|
||||
const variableArr = v.variable.split('.')
|
||||
const [first] = variableArr
|
||||
if (first === 'sys' || first === 'env' || first === 'conversation')
|
||||
if (isSpecialVar(first))
|
||||
return variableArr
|
||||
|
||||
return [...selector, ...variableArr]
|
||||
@ -592,7 +601,6 @@ const formatItem = (
|
||||
return obj?.children && ((obj?.children as Var[]).length > 0 || Object.keys((obj?.children as StructuredOutput)?.schema?.properties || {}).length > 0)
|
||||
}).map((v) => {
|
||||
const isFile = v.type === VarType.file
|
||||
|
||||
const { children } = (() => {
|
||||
if (isFile) {
|
||||
return {
|
||||
@ -615,12 +623,25 @@ const formatItem = (
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export const removeFileVars = (nodeWithVars: NodeOutPutVar[]) => {
|
||||
return nodeWithVars.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
vars: item.vars.filter(v => v.type !== VarType.file && v.type !== VarType.arrayFile),
|
||||
}
|
||||
}).filter(item => item.vars.length > 0)
|
||||
}
|
||||
|
||||
export const toNodeOutputVars = (
|
||||
nodes: any[],
|
||||
isChatMode: boolean,
|
||||
filterVar = (_payload: Var, _selector: ValueSelector) => true,
|
||||
environmentVariables: EnvironmentVariable[] = [],
|
||||
conversationVariables: ConversationVariable[] = [],
|
||||
ragVariables: RAGPipelineVariable[] = [],
|
||||
allPluginInfoList: Record<string, ToolWithProvider[]>,
|
||||
getMatchedSchemaType = (_obj: any) => '',
|
||||
): NodeOutPutVar[] => {
|
||||
// ENV_NODE data format
|
||||
const ENV_NODE = {
|
||||
@ -640,6 +661,15 @@ export const toNodeOutputVars = (
|
||||
chatVarList: conversationVariables,
|
||||
},
|
||||
}
|
||||
// RAG_PIPELINE_NODE data format
|
||||
const RAG_PIPELINE_NODE = {
|
||||
id: 'rag',
|
||||
data: {
|
||||
title: 'SHARED INPUTS',
|
||||
type: 'rag',
|
||||
ragVariables: ragVariables.filter(ragVariable => ragVariable.belong_to_node_id === 'shared'),
|
||||
},
|
||||
}
|
||||
// Sort nodes in reverse chronological order (most recent first)
|
||||
const sortedNodes = [...nodes].sort((a, b) => {
|
||||
if (a.data.type === BlockEnum.Start) return 1
|
||||
@ -656,9 +686,20 @@ export const toNodeOutputVars = (
|
||||
...sortedNodes.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node?.data?.type)),
|
||||
...(environmentVariables.length > 0 ? [ENV_NODE] : []),
|
||||
...((isChatMode && conversationVariables.length > 0) ? [CHAT_VAR_NODE] : []),
|
||||
...(RAG_PIPELINE_NODE.data.ragVariables.length > 0 ? [RAG_PIPELINE_NODE] : []),
|
||||
].map((node) => {
|
||||
let ragVariablesInDataSource: RAGPipelineVariable[] = []
|
||||
if (node.data.type === BlockEnum.DataSource)
|
||||
ragVariablesInDataSource = ragVariables.filter(ragVariable => ragVariable.belong_to_node_id === node.id)
|
||||
return {
|
||||
...formatItem(node, isChatMode, filterVar),
|
||||
...formatItem(node, isChatMode, filterVar, allPluginInfoList, ragVariablesInDataSource.map(
|
||||
(ragVariable: RAGPipelineVariable) => ({
|
||||
variable: `rag.${node.id}.${ragVariable.variable}`,
|
||||
type: inputVarTypeToVarType(ragVariable.type as any),
|
||||
description: ragVariable.label,
|
||||
isRagVariable: true,
|
||||
} as Var),
|
||||
), getMatchedSchemaType),
|
||||
isStartNode: node.data.type === BlockEnum.Start,
|
||||
}
|
||||
}).filter(item => item.vars.length > 0)
|
||||
@ -694,9 +735,9 @@ const getIterationItemType = ({
|
||||
curr = Array.isArray(curr) ? curr.find(v => v.variable === key) : []
|
||||
|
||||
if (isLast)
|
||||
arrayType = curr?.type
|
||||
arrayType = curr?.type
|
||||
else if (curr?.type === VarType.object || curr?.type === VarType.file)
|
||||
curr = curr.children || []
|
||||
curr = curr.children || []
|
||||
}
|
||||
}
|
||||
|
||||
@ -781,6 +822,10 @@ export const getVarType = ({
|
||||
isConstant,
|
||||
environmentVariables = [],
|
||||
conversationVariables = [],
|
||||
ragVariables = [],
|
||||
allPluginInfoList,
|
||||
getMatchedSchemaType,
|
||||
preferSchemaType,
|
||||
}: {
|
||||
valueSelector: ValueSelector
|
||||
parentNode?: Node | null
|
||||
@ -791,6 +836,10 @@ export const getVarType = ({
|
||||
isConstant?: boolean
|
||||
environmentVariables?: EnvironmentVariable[]
|
||||
conversationVariables?: ConversationVariable[]
|
||||
ragVariables?: RAGPipelineVariable[]
|
||||
allPluginInfoList: Record<string, ToolWithProvider[]>
|
||||
getMatchedSchemaType: (obj: any) => string
|
||||
preferSchemaType?: boolean
|
||||
}): VarType => {
|
||||
if (isConstant)
|
||||
return VarType.string
|
||||
@ -801,6 +850,9 @@ export const getVarType = ({
|
||||
undefined,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
allPluginInfoList,
|
||||
getMatchedSchemaType,
|
||||
)
|
||||
|
||||
const isIterationInnerVar = parentNode?.data.type === BlockEnum.Iteration
|
||||
@ -844,11 +896,20 @@ export const getVarType = ({
|
||||
const isSystem = isSystemVar(valueSelector)
|
||||
const isEnv = isENV(valueSelector)
|
||||
const isChatVar = isConversationVar(valueSelector)
|
||||
const isSharedRagVariable = isRagVariableVar(valueSelector) && valueSelector[1] === 'shared'
|
||||
const isInNodeRagVariable = isRagVariableVar(valueSelector) && valueSelector[1] !== 'shared'
|
||||
|
||||
const startNode = availableNodes.find((node: any) => {
|
||||
return node?.data.type === BlockEnum.Start
|
||||
})
|
||||
|
||||
const targetVarNodeId = isSystem ? startNode?.id : valueSelector[0]
|
||||
const targetVarNodeId = (() => {
|
||||
if (isSystem)
|
||||
return startNode?.id
|
||||
if (isInNodeRagVariable)
|
||||
return valueSelector[1]
|
||||
return valueSelector[0]
|
||||
})()
|
||||
const targetVar = beforeNodesOutputVars.find(v => v.nodeId === targetVarNodeId)
|
||||
|
||||
if (!targetVar)
|
||||
@ -857,18 +918,25 @@ export const getVarType = ({
|
||||
let type: VarType = VarType.string
|
||||
let curr: any = targetVar.vars
|
||||
|
||||
if (isSystem || isEnv || isChatVar) {
|
||||
if (isSystem || isEnv || isChatVar || isSharedRagVariable) {
|
||||
return curr.find((v: any) => v.variable === (valueSelector as ValueSelector).join('.'))?.type
|
||||
}
|
||||
else {
|
||||
const targetVar = curr.find((v: any) => v.variable === valueSelector[1])
|
||||
const targetVar = curr.find((v: any) => {
|
||||
if (isInNodeRagVariable)
|
||||
return v.variable === valueSelector.join('.')
|
||||
return v.variable === valueSelector[1]
|
||||
})
|
||||
if (!targetVar)
|
||||
return VarType.string
|
||||
|
||||
if (isInNodeRagVariable)
|
||||
return targetVar.type
|
||||
|
||||
const isStructuredOutputVar = !!targetVar.children?.schema?.properties
|
||||
if (isStructuredOutputVar) {
|
||||
if (valueSelector.length === 2) { // root
|
||||
return VarType.object
|
||||
return (preferSchemaType && targetVar.schemaType) ? targetVar.schemaType : VarType.object
|
||||
}
|
||||
let currProperties = targetVar.children.schema;
|
||||
(valueSelector as ValueSelector).slice(2).forEach((key, i) => {
|
||||
@ -889,7 +957,7 @@ export const getVarType = ({
|
||||
curr = curr?.find((v: any) => v.variable === key)
|
||||
|
||||
if (isLast) {
|
||||
type = curr?.type
|
||||
type = (preferSchemaType && curr?.schemaType) ? curr?.schemaType : curr?.type
|
||||
}
|
||||
else {
|
||||
if (curr?.type === VarType.object || curr?.type === VarType.file)
|
||||
@ -908,7 +976,10 @@ export const toNodeAvailableVars = ({
|
||||
isChatMode,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
filterVar,
|
||||
allPluginInfoList,
|
||||
getMatchedSchemaType,
|
||||
}: {
|
||||
parentNode?: Node | null
|
||||
t?: any
|
||||
@ -919,7 +990,11 @@ export const toNodeAvailableVars = ({
|
||||
environmentVariables?: EnvironmentVariable[]
|
||||
// chat var
|
||||
conversationVariables?: ConversationVariable[]
|
||||
// rag variables
|
||||
ragVariables?: RAGPipelineVariable[]
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
allPluginInfoList: Record<string, ToolWithProvider[]>
|
||||
getMatchedSchemaType: (obj: any) => string
|
||||
}): NodeOutPutVar[] => {
|
||||
const beforeNodesOutputVars = toNodeOutputVars(
|
||||
beforeNodes,
|
||||
@ -927,6 +1002,9 @@ export const toNodeAvailableVars = ({
|
||||
filterVar,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
allPluginInfoList,
|
||||
getMatchedSchemaType,
|
||||
)
|
||||
const isInIteration = parentNode?.data.type === BlockEnum.Iteration
|
||||
if (isInIteration) {
|
||||
@ -939,6 +1017,8 @@ export const toNodeAvailableVars = ({
|
||||
isChatMode,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
allPluginInfoList,
|
||||
getMatchedSchemaType,
|
||||
})
|
||||
const itemChildren = itemType === VarType.file
|
||||
? {
|
||||
@ -1088,6 +1168,13 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
|
||||
res = [...(mixVars as ValueSelector[]), ...(vars as any)]
|
||||
break
|
||||
}
|
||||
case BlockEnum.DataSource: {
|
||||
const payload = data as DataSourceNodeType
|
||||
const mixVars = matchNotSystemVars(Object.keys(payload.datasource_parameters)?.filter(key => payload.datasource_parameters[key].type === ToolVarType.mixed).map(key => payload.datasource_parameters[key].value) as string[])
|
||||
const vars = Object.keys(payload.datasource_parameters).filter(key => payload.datasource_parameters[key].type === ToolVarType.variable).map(key => payload.datasource_parameters[key].value as string) || []
|
||||
res = [...(mixVars as ValueSelector[]), ...(vars as any)]
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.VariableAssigner: {
|
||||
res = (data as VariableAssignerNodeType)?.variables
|
||||
@ -1381,6 +1468,30 @@ export const updateNodeVars = (oldNode: Node, oldVarSelector: ValueSelector, new
|
||||
}
|
||||
break
|
||||
}
|
||||
case BlockEnum.DataSource: {
|
||||
const payload = data as DataSourceNodeType
|
||||
const hasShouldRenameVar = Object.keys(payload.datasource_parameters)?.filter(key => payload.datasource_parameters[key].type !== ToolVarType.constant)
|
||||
if (hasShouldRenameVar) {
|
||||
Object.keys(payload.datasource_parameters).forEach((key) => {
|
||||
const value = payload.datasource_parameters[key]
|
||||
const { type } = value
|
||||
if (type === ToolVarType.variable) {
|
||||
payload.datasource_parameters[key] = {
|
||||
...value,
|
||||
value: newVarSelector,
|
||||
}
|
||||
}
|
||||
|
||||
if (type === ToolVarType.mixed) {
|
||||
payload.datasource_parameters[key] = {
|
||||
...value,
|
||||
value: replaceOldVarInText(payload.datasource_parameters[key].value as string, oldVarSelector, newVarSelector),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case BlockEnum.VariableAssigner: {
|
||||
const payload = data as VariableAssignerNodeType
|
||||
if (payload.variables) {
|
||||
|
||||
@ -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,10 +279,18 @@ 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(
|
||||
<div className={cn(
|
||||
'relative mr-1 h-full',
|
||||
)}>
|
||||
<div
|
||||
@ -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: {
|
||||
@ -356,7 +420,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
>
|
||||
{
|
||||
isSingleRunning ? <Stop className='h-4 w-4 text-text-tertiary' />
|
||||
: <RiPlayLargeLine className='h-4 w-4 text-text-tertiary' />
|
||||
: <RiPlayLargeLine className='h-4 w-4 text-text-tertiary' />
|
||||
}
|
||||
</div>
|
||||
</Tooltip>
|
||||
@ -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,
|
||||
|
||||
@ -4,7 +4,11 @@ import {
|
||||
useWorkflow,
|
||||
useWorkflowVariables,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import type { Node, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { NodeOutPutVar } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, type Node, type ValueSelector, type Var } from '@/app/components/workflow/types'
|
||||
import { useStore as useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { inputVarTypeToVarType } from '../../data-source/utils'
|
||||
|
||||
type Params = {
|
||||
onlyLeafNodeVar?: boolean
|
||||
hideEnv?: boolean
|
||||
@ -24,29 +28,57 @@ const useAvailableVarList = (nodeId: string, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: () => true,
|
||||
}) => {
|
||||
const { getTreeLeafNodes, getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
|
||||
const { getTreeLeafNodes, getNodeById, getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
|
||||
const { getNodeAvailableVars } = useWorkflowVariables()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const availableNodes = passedInAvailableNodes || (onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranchIncludeParent(nodeId))
|
||||
|
||||
const {
|
||||
parentNode: iterationNode,
|
||||
} = useNodeInfo(nodeId)
|
||||
|
||||
const availableVars = getNodeAvailableVars({
|
||||
const currNode = getNodeById(nodeId)
|
||||
const ragPipelineVariables = useWorkflowStore(s => s.ragPipelineVariables)
|
||||
const isDataSourceNode = currNode?.data?.type === BlockEnum.DataSource
|
||||
const dataSourceRagVars: NodeOutPutVar[] = []
|
||||
if(isDataSourceNode) {
|
||||
const ragVariablesInDataSource = ragPipelineVariables?.filter(ragVariable => ragVariable.belong_to_node_id === nodeId)
|
||||
const filterVars = ragVariablesInDataSource?.filter(v => filterVar({
|
||||
variable: v.variable,
|
||||
type: inputVarTypeToVarType(v.type),
|
||||
nodeId,
|
||||
isRagVariable: true,
|
||||
}, ['rag', nodeId, v.variable]))
|
||||
if(filterVars?.length) {
|
||||
dataSourceRagVars.push({
|
||||
nodeId,
|
||||
title: currNode.data?.title,
|
||||
vars: filterVars.map((v) => {
|
||||
return {
|
||||
variable: `rag.${nodeId}.${v.variable}`,
|
||||
type: inputVarTypeToVarType(v.type),
|
||||
description: v.label,
|
||||
isRagVariable: true,
|
||||
} as Var
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
const availableVars = [...getNodeAvailableVars({
|
||||
parentNode: iterationNode,
|
||||
beforeNodes: availableNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
hideEnv,
|
||||
hideChatVar,
|
||||
})
|
||||
}), ...dataSourceRagVars]
|
||||
|
||||
return {
|
||||
availableVars,
|
||||
availableNodes,
|
||||
availableNodesWithParent: availableNodes,
|
||||
availableNodesWithParent: [
|
||||
...availableNodes,
|
||||
...(isDataSourceNode ? [currNode] : []),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,67 +1,15 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useDocLink, useGetLanguage } from '@/context/i18n'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { useNodesMetaData } from '@/app/components/workflow/hooks'
|
||||
|
||||
export const useNodeHelpLink = (nodeType: BlockEnum) => {
|
||||
const language = useGetLanguage()
|
||||
const docLink = useDocLink()
|
||||
const prefixLink = useMemo(() => {
|
||||
return docLink('/guides/workflow/node/')
|
||||
}, [language])
|
||||
const linkMap = useMemo(() => {
|
||||
if (language === 'zh_Hans') {
|
||||
return {
|
||||
[BlockEnum.Start]: 'start',
|
||||
[BlockEnum.End]: 'end',
|
||||
[BlockEnum.Answer]: 'answer',
|
||||
[BlockEnum.LLM]: 'llm',
|
||||
[BlockEnum.KnowledgeRetrieval]: 'knowledge-retrieval',
|
||||
[BlockEnum.QuestionClassifier]: 'question-classifier',
|
||||
[BlockEnum.IfElse]: 'ifelse',
|
||||
[BlockEnum.Code]: 'code',
|
||||
[BlockEnum.TemplateTransform]: 'template',
|
||||
[BlockEnum.VariableAssigner]: 'variable-assigner',
|
||||
[BlockEnum.VariableAggregator]: 'variable-aggregator',
|
||||
[BlockEnum.Assigner]: 'variable-assigner',
|
||||
[BlockEnum.Iteration]: 'iteration',
|
||||
[BlockEnum.Loop]: 'loop',
|
||||
[BlockEnum.ParameterExtractor]: 'parameter-extractor',
|
||||
[BlockEnum.HttpRequest]: 'http-request',
|
||||
[BlockEnum.Tool]: 'tools',
|
||||
[BlockEnum.DocExtractor]: 'doc-extractor',
|
||||
[BlockEnum.ListFilter]: 'list-operator',
|
||||
[BlockEnum.Agent]: 'agent',
|
||||
}
|
||||
}
|
||||
const availableNodesMetaData = useNodesMetaData()
|
||||
|
||||
return {
|
||||
[BlockEnum.Start]: 'start',
|
||||
[BlockEnum.End]: 'end',
|
||||
[BlockEnum.Answer]: 'answer',
|
||||
[BlockEnum.LLM]: 'llm',
|
||||
[BlockEnum.KnowledgeRetrieval]: 'knowledge-retrieval',
|
||||
[BlockEnum.QuestionClassifier]: 'question-classifier',
|
||||
[BlockEnum.IfElse]: 'ifelse',
|
||||
[BlockEnum.Code]: 'code',
|
||||
[BlockEnum.TemplateTransform]: 'template',
|
||||
[BlockEnum.VariableAssigner]: 'variable-assigner',
|
||||
[BlockEnum.VariableAggregator]: 'variable-aggregator',
|
||||
[BlockEnum.Assigner]: 'variable-assigner',
|
||||
[BlockEnum.Iteration]: 'iteration',
|
||||
[BlockEnum.Loop]: 'loop',
|
||||
[BlockEnum.ParameterExtractor]: 'parameter-extractor',
|
||||
[BlockEnum.HttpRequest]: 'http-request',
|
||||
[BlockEnum.Tool]: 'tools',
|
||||
[BlockEnum.DocExtractor]: 'doc-extractor',
|
||||
[BlockEnum.ListFilter]: 'list-operator',
|
||||
[BlockEnum.Agent]: 'agent',
|
||||
}
|
||||
}, [language]) as Record<string, string>
|
||||
const link = useMemo(() => {
|
||||
const result = availableNodesMetaData?.nodesMap?.[nodeType]?.metaData.helpLinkUri || ''
|
||||
|
||||
const link = linkMap[nodeType]
|
||||
return result
|
||||
}, [availableNodesMetaData, nodeType])
|
||||
|
||||
if (!link)
|
||||
return ''
|
||||
|
||||
return `${prefixLink}${link}`
|
||||
return link
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import { getNodeInfoById, isConversationVar, isENV, isSystemVar, toNodeOutputVar
|
||||
|
||||
import type { CommonNodeType, InputVar, ValueSelector, Var, Variable } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, InputVarType, NodeRunningStatus, VarType } from '@/app/components/workflow/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { fetchNodeInspectVars, getIterationSingleNodeRunUrl, getLoopSingleNodeRunUrl, singleNodeRun } from '@/service/workflow'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
@ -52,6 +51,7 @@ import {
|
||||
} from 'reactflow'
|
||||
import { useInvalidLastRun } from '@/service/use-workflow'
|
||||
import useInspectVarsCrud from '../../../hooks/use-inspect-vars-crud'
|
||||
import type { FlowType } from '@/types/common'
|
||||
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||
const checkValidFns: Record<BlockEnum, Function> = {
|
||||
[BlockEnum.LLM]: checkLLMValid,
|
||||
@ -72,6 +72,8 @@ const checkValidFns: Record<BlockEnum, Function> = {
|
||||
|
||||
export type Params<T> = {
|
||||
id: string
|
||||
flowId: string
|
||||
flowType: FlowType
|
||||
data: CommonNodeType<T>
|
||||
defaultRunInputData: Record<string, any>
|
||||
moreDataForCheckValid?: any
|
||||
@ -108,6 +110,8 @@ const varTypeToInputVarType = (type: VarType, {
|
||||
|
||||
const useOneStepRun = <T>({
|
||||
id,
|
||||
flowId,
|
||||
flowType,
|
||||
data,
|
||||
defaultRunInputData,
|
||||
moreDataForCheckValid,
|
||||
@ -126,7 +130,19 @@ const useOneStepRun = <T>({
|
||||
|
||||
const availableNodes = getBeforeNodesInSameBranch(id)
|
||||
const availableNodesIncludeParent = getBeforeNodesInSameBranchIncludeParent(id)
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables)
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
const mcpTools = useStore(s => s.mcpTools)
|
||||
const dataSourceList = useStore(s => s.dataSourceList)
|
||||
const allPluginInfoList = {
|
||||
buildInTools,
|
||||
customTools,
|
||||
workflowTools,
|
||||
mcpTools,
|
||||
dataSourceList: dataSourceList ?? [],
|
||||
}
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables, [], allPluginInfoList)
|
||||
const getVar = (valueSelector: ValueSelector): Var | undefined => {
|
||||
const isSystem = valueSelector[0] === 'sys'
|
||||
const targetVar = allOutputVars.find(item => isSystem ? !!item.isStartNode : item.nodeId === valueSelector[0])
|
||||
@ -155,7 +171,6 @@ const useOneStepRun = <T>({
|
||||
|
||||
const checkValid = checkValidFns[data.type]
|
||||
|
||||
const appId = useAppStore.getState().appDetail?.id
|
||||
const [runInputData, setRunInputData] = useState<Record<string, any>>(defaultRunInputData || {})
|
||||
const runInputDataRef = useRef(runInputData)
|
||||
const handleSetRunInputData = useCallback((data: Record<string, any>) => {
|
||||
@ -170,7 +185,7 @@ const useOneStepRun = <T>({
|
||||
const {
|
||||
setShowSingleRunPanel,
|
||||
} = workflowStore.getState()
|
||||
const invalidLastRun = useInvalidLastRun(appId!, id)
|
||||
const invalidLastRun = useInvalidLastRun(flowType, flowId!, id)
|
||||
const [runResult, doSetRunResult] = useState<NodeRunResult | null>(null)
|
||||
const {
|
||||
appendNodeInspectVars,
|
||||
@ -197,7 +212,7 @@ const useOneStepRun = <T>({
|
||||
}
|
||||
|
||||
// run fail may also update the inspect vars when the node set the error default output.
|
||||
const vars = await fetchNodeInspectVars(appId!, id)
|
||||
const vars = await fetchNodeInspectVars(flowType, flowId!, id)
|
||||
const { getNodes } = store.getState()
|
||||
const nodes = getNodes()
|
||||
appendNodeInspectVars(id, vars, nodes)
|
||||
@ -207,7 +222,7 @@ const useOneStepRun = <T>({
|
||||
invalidateSysVarValues()
|
||||
invalidateConversationVarValues() // loop, iteration, variable assigner node can update the conversation variables, but to simple the logic(some nodes may also can update in the future), all nodes refresh.
|
||||
}
|
||||
}, [isRunAfterSingleRun, runningStatus, appId, id, store, appendNodeInspectVars, invalidLastRun, isStartNode, invalidateSysVarValues, invalidateConversationVarValues])
|
||||
}, [isRunAfterSingleRun, runningStatus, flowId, id, store, appendNodeInspectVars, invalidLastRun, isStartNode, invalidateSysVarValues, invalidateConversationVarValues])
|
||||
|
||||
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
||||
const setNodeRunning = () => {
|
||||
@ -306,14 +321,14 @@ const useOneStepRun = <T>({
|
||||
else {
|
||||
postData.inputs = submitData
|
||||
}
|
||||
res = await singleNodeRun(appId!, id, postData) as any
|
||||
res = await singleNodeRun(flowType, flowId!, id, postData) as any
|
||||
}
|
||||
else if (isIteration) {
|
||||
setIterationRunResult([])
|
||||
let _iterationResult: NodeTracing[] = []
|
||||
let _runResult: any = null
|
||||
ssePost(
|
||||
getIterationSingleNodeRunUrl(isChatMode, appId!, id),
|
||||
getIterationSingleNodeRunUrl(flowType, isChatMode, flowId!, id),
|
||||
{ body: { inputs: submitData } },
|
||||
{
|
||||
onWorkflowStarted: noop,
|
||||
@ -416,7 +431,7 @@ const useOneStepRun = <T>({
|
||||
let _loopResult: NodeTracing[] = []
|
||||
let _runResult: any = null
|
||||
ssePost(
|
||||
getLoopSingleNodeRunUrl(isChatMode, appId!, id),
|
||||
getLoopSingleNodeRunUrl(flowType, isChatMode, flowId!, id),
|
||||
{ body: { inputs: submitData } },
|
||||
{
|
||||
onWorkflowStarted: noop,
|
||||
@ -634,7 +649,6 @@ const useOneStepRun = <T>({
|
||||
}
|
||||
|
||||
return {
|
||||
appId,
|
||||
isShowSingleRun,
|
||||
hideSingleRun,
|
||||
showSingleRun,
|
||||
@ -649,6 +663,7 @@ const useOneStepRun = <T>({
|
||||
runInputDataRef,
|
||||
setRunInputData: handleSetRunInputData,
|
||||
runResult,
|
||||
setRunResult: doSetRunResult,
|
||||
iterationRunResult,
|
||||
loopRunResult,
|
||||
setNodeRunning,
|
||||
|
||||
@ -139,9 +139,8 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex rounded-2xl border-[2px]',
|
||||
'relative flex rounded-2xl border',
|
||||
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
|
||||
!showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
|
||||
data._waitingRun && 'opacity-70',
|
||||
data._dimmed && 'opacity-30',
|
||||
)}
|
||||
@ -151,6 +150,15 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
height: (data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop) ? data.height : 'auto',
|
||||
}}
|
||||
>
|
||||
{
|
||||
data.type === BlockEnum.DataSource && (
|
||||
<div className='absolute inset-[-2px] top-[-22px] z-[-1] rounded-[18px] bg-node-data-source-bg p-0.5 backdrop-blur-[6px]'>
|
||||
<div className='system-2xs-semibold-uppercase flex h-5 items-center px-2.5 text-text-tertiary'>
|
||||
{t('workflow.blocks.datasource')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div
|
||||
className={cn(
|
||||
'group relative pb-1 shadow-xs',
|
||||
@ -165,13 +173,6 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
data._isBundled && '!shadow-lg',
|
||||
)}
|
||||
>
|
||||
{
|
||||
data._inParallelHovering && (
|
||||
<div className='top system-2xs-medium-uppercase absolute -top-2.5 left-2 z-10 text-text-tertiary'>
|
||||
{t('workflow.common.parallelRun')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
data._showAddVariablePopup && (
|
||||
<AddVariablePopupWithPosition
|
||||
|
||||
Reference in New Issue
Block a user