mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-09 10:56:52 +08:00
feat: workflow continue on error (#11474)
This commit is contained in:
@@ -59,7 +59,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const isFinished = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
|
||||
const isFinished = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed || runningStatus === NodeRunningStatus.Exception
|
||||
const isRunning = runningStatus === NodeRunningStatus.Running
|
||||
const isFileLoaded = (() => {
|
||||
// system files
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import Collapse from '.'
|
||||
|
||||
type FieldCollapseProps = {
|
||||
title: string
|
||||
children: JSX.Element
|
||||
}
|
||||
const FieldCollapse = ({
|
||||
title,
|
||||
children,
|
||||
}: FieldCollapseProps) => {
|
||||
return (
|
||||
<div className='py-4'>
|
||||
<Collapse
|
||||
trigger={
|
||||
<div className='flex items-center h-6 system-sm-semibold-uppercase text-text-secondary cursor-pointer'>{title}</div>
|
||||
}
|
||||
>
|
||||
<div className='px-4'>
|
||||
{children}
|
||||
</div>
|
||||
</Collapse>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FieldCollapse
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useState } from 'react'
|
||||
import { RiArrowDropRightLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export { default as FieldCollapse } from './field-collapse'
|
||||
|
||||
type CollapseProps = {
|
||||
disabled?: boolean
|
||||
trigger: JSX.Element
|
||||
children: JSX.Element
|
||||
collapsed?: boolean
|
||||
onCollapse?: (collapsed: boolean) => void
|
||||
}
|
||||
const Collapse = ({
|
||||
disabled,
|
||||
trigger,
|
||||
children,
|
||||
collapsed,
|
||||
onCollapse,
|
||||
}: CollapseProps) => {
|
||||
const [collapsedLocal, setCollapsedLocal] = useState(true)
|
||||
const collapsedMerged = collapsed !== undefined ? collapsed : collapsedLocal
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className='flex items-center'
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
setCollapsedLocal(!collapsedMerged)
|
||||
onCollapse?.(!collapsedMerged)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className='shrink-0 w-4 h-4'>
|
||||
{
|
||||
!disabled && (
|
||||
<RiArrowDropRightLine
|
||||
className={cn(
|
||||
'w-4 h-4 text-text-tertiary',
|
||||
!collapsedMerged && 'transform rotate-90',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{trigger}
|
||||
</div>
|
||||
{
|
||||
!collapsedMerged && children
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Collapse
|
||||
@@ -33,6 +33,7 @@ type Props = {
|
||||
}[]
|
||||
showFileList?: boolean
|
||||
showCodeGenerator?: boolean
|
||||
tip?: JSX.Element
|
||||
}
|
||||
|
||||
const Base: FC<Props> = ({
|
||||
@@ -49,6 +50,7 @@ const Base: FC<Props> = ({
|
||||
fileList = [],
|
||||
showFileList,
|
||||
showCodeGenerator = false,
|
||||
tip,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const {
|
||||
@@ -100,6 +102,7 @@ const Base: FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tip && <div className='px-1 py-0.5'>{tip}</div>}
|
||||
<PromptEditorHeightResizeWrap
|
||||
height={isExpand ? editorExpandHeight : editorContentHeight}
|
||||
minHeight={editorContentMinHeight}
|
||||
|
||||
@@ -34,6 +34,7 @@ export type Props = {
|
||||
onGenerated?: (value: string) => void
|
||||
showCodeGenerator?: boolean
|
||||
className?: string
|
||||
tip?: JSX.Element
|
||||
}
|
||||
|
||||
export const languageMap = {
|
||||
@@ -69,6 +70,7 @@ const CodeEditor: FC<Props> = ({
|
||||
onGenerated,
|
||||
showCodeGenerator = false,
|
||||
className,
|
||||
tip,
|
||||
}) => {
|
||||
const [isFocus, setIsFocus] = React.useState(false)
|
||||
const [isMounted, setIsMounted] = React.useState(false)
|
||||
@@ -211,6 +213,7 @@ const CodeEditor: FC<Props> = ({
|
||||
fileList={fileList as any}
|
||||
showFileList={showFileList}
|
||||
showCodeGenerator={showCodeGenerator}
|
||||
tip={tip}
|
||||
>
|
||||
{main}
|
||||
</Base>
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { DefaultValueForm } from './types'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
|
||||
type DefaultValueProps = {
|
||||
forms: DefaultValueForm[]
|
||||
onFormChange: (form: DefaultValueForm) => void
|
||||
}
|
||||
const DefaultValue = ({
|
||||
forms,
|
||||
onFormChange,
|
||||
}: DefaultValueProps) => {
|
||||
const { t } = useTranslation()
|
||||
const getFormChangeHandler = useCallback(({ key, type }: DefaultValueForm) => {
|
||||
return (payload: any) => {
|
||||
let value
|
||||
if (type === VarType.string || type === VarType.number)
|
||||
value = payload.target.value
|
||||
|
||||
if (type === VarType.array || type === VarType.arrayNumber || type === VarType.arrayString || type === VarType.arrayObject || type === VarType.arrayFile || type === VarType.object)
|
||||
value = payload
|
||||
|
||||
onFormChange({ key, type, value })
|
||||
}
|
||||
}, [onFormChange])
|
||||
|
||||
return (
|
||||
<div className='px-4 pt-2'>
|
||||
<div className='mb-2 body-xs-regular text-text-tertiary'>
|
||||
{t('workflow.nodes.common.errorHandle.defaultValue.desc')}
|
||||
|
||||
<a
|
||||
href='https://docs.dify.ai/guides/workflow/error-handling'
|
||||
target='_blank'
|
||||
className='text-text-accent'
|
||||
>
|
||||
{t('workflow.common.learnMore')}
|
||||
</a>
|
||||
</div>
|
||||
<div className='space-y-1'>
|
||||
{
|
||||
forms.map((form, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='py-1'
|
||||
>
|
||||
<div className='flex items-center mb-1'>
|
||||
<div className='mr-1 system-sm-medium text-text-primary'>{form.key}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{form.type}</div>
|
||||
</div>
|
||||
{
|
||||
(form.type === VarType.string || form.type === VarType.number) && (
|
||||
<Input
|
||||
type={form.type}
|
||||
value={form.value || (form.type === VarType.string ? '' : 0)}
|
||||
onChange={getFormChangeHandler({ key: form.key, type: form.type })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
(
|
||||
form.type === VarType.array
|
||||
|| form.type === VarType.arrayNumber
|
||||
|| form.type === VarType.arrayString
|
||||
|| form.type === VarType.arrayObject
|
||||
|| form.type === VarType.object
|
||||
) && (
|
||||
<CodeEditor
|
||||
language={CodeLanguage.json}
|
||||
value={form.value}
|
||||
onChange={getFormChangeHandler({ key: form.key, type: form.type })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DefaultValue
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useUpdateNodeInternals } from 'reactflow'
|
||||
import { NodeSourceHandle } from '../node-handle'
|
||||
import { ErrorHandleTypeEnum } from './types'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type ErrorHandleOnNodeProps = Pick<Node, 'id' | 'data'>
|
||||
const ErrorHandleOnNode = ({
|
||||
id,
|
||||
data,
|
||||
}: ErrorHandleOnNodeProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { error_strategy } = data
|
||||
const updateNodeInternals = useUpdateNodeInternals()
|
||||
|
||||
useEffect(() => {
|
||||
if (error_strategy === ErrorHandleTypeEnum.failBranch)
|
||||
updateNodeInternals(id)
|
||||
}, [error_strategy, id, updateNodeInternals])
|
||||
|
||||
if (!error_strategy)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='relative pt-1 pb-2 px-3'>
|
||||
<div className={cn(
|
||||
'relative flex items-center justify-between px-[5px] h-6 bg-workflow-block-parma-bg rounded-md',
|
||||
data._runningStatus === NodeRunningStatus.Exception && 'border-[0.5px] border-components-badge-status-light-warning-halo bg-state-warning-hover',
|
||||
)}>
|
||||
<div className='system-xs-medium-uppercase text-text-tertiary'>
|
||||
{t('workflow.common.onFailure')}
|
||||
</div>
|
||||
<div className={cn(
|
||||
'system-xs-medium text-text-secondary',
|
||||
data._runningStatus === NodeRunningStatus.Exception && 'text-text-warning',
|
||||
)}>
|
||||
{
|
||||
error_strategy === ErrorHandleTypeEnum.defaultValue && (
|
||||
t('workflow.nodes.common.errorHandle.defaultValue.output')
|
||||
)
|
||||
}
|
||||
{
|
||||
error_strategy === ErrorHandleTypeEnum.failBranch && (
|
||||
t('workflow.nodes.common.errorHandle.failBranch.title')
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
error_strategy === ErrorHandleTypeEnum.failBranch && (
|
||||
<NodeSourceHandle
|
||||
id={id}
|
||||
data={data}
|
||||
handleId={ErrorHandleTypeEnum.failBranch}
|
||||
handleClassName='!top-1/2 !-right-[21px] !-translate-y-1/2 after:!bg-workflow-link-line-failure-button-bg'
|
||||
nodeSelectorClassName='!bg-workflow-link-line-failure-button-bg'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorHandleOnNode
|
||||
@@ -0,0 +1,90 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Collapse from '../collapse'
|
||||
import { ErrorHandleTypeEnum } from './types'
|
||||
import ErrorHandleTypeSelector from './error-handle-type-selector'
|
||||
import FailBranchCard from './fail-branch-card'
|
||||
import DefaultValue from './default-value'
|
||||
import {
|
||||
useDefaultValue,
|
||||
useErrorHandle,
|
||||
} from './hooks'
|
||||
import type { DefaultValueForm } from './types'
|
||||
import type {
|
||||
CommonNodeType,
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type ErrorHandleProps = Pick<Node, 'id' | 'data'>
|
||||
const ErrorHandle = ({
|
||||
id,
|
||||
data,
|
||||
}: ErrorHandleProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { error_strategy, default_value } = data
|
||||
const {
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
handleErrorHandleTypeChange,
|
||||
} = useErrorHandle(id, data)
|
||||
const { handleFormChange } = useDefaultValue(id)
|
||||
|
||||
const getHandleErrorHandleTypeChange = useCallback((data: CommonNodeType) => {
|
||||
return (value: ErrorHandleTypeEnum) => {
|
||||
handleErrorHandleTypeChange(value, data)
|
||||
}
|
||||
}, [handleErrorHandleTypeChange])
|
||||
|
||||
const getHandleFormChange = useCallback((data: CommonNodeType) => {
|
||||
return (v: DefaultValueForm) => {
|
||||
handleFormChange(v, data)
|
||||
}
|
||||
}, [handleFormChange])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Split />
|
||||
<div className='py-4'>
|
||||
<Collapse
|
||||
disabled={!error_strategy}
|
||||
collapsed={collapsed}
|
||||
onCollapse={setCollapsed}
|
||||
trigger={
|
||||
<div className='grow flex items-center justify-between pr-4'>
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-0.5 system-sm-semibold-uppercase text-text-secondary'>
|
||||
{t('workflow.nodes.common.errorHandle.title')}
|
||||
</div>
|
||||
<Tooltip popupContent={t('workflow.nodes.common.errorHandle.tip')} />
|
||||
</div>
|
||||
<ErrorHandleTypeSelector
|
||||
value={error_strategy || ErrorHandleTypeEnum.none}
|
||||
onSelected={getHandleErrorHandleTypeChange(data)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<>
|
||||
{
|
||||
error_strategy === ErrorHandleTypeEnum.failBranch && !collapsed && (
|
||||
<FailBranchCard />
|
||||
)
|
||||
}
|
||||
{
|
||||
error_strategy === ErrorHandleTypeEnum.defaultValue && !collapsed && !!default_value?.length && (
|
||||
<DefaultValue
|
||||
forms={default_value}
|
||||
onFormChange={getHandleFormChange(data)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
</Collapse>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorHandle
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAlertFill } from '@remixicon/react'
|
||||
import { ErrorHandleTypeEnum } from './types'
|
||||
|
||||
type ErrorHandleTipProps = {
|
||||
type?: ErrorHandleTypeEnum
|
||||
}
|
||||
const ErrorHandleTip = ({
|
||||
type,
|
||||
}: ErrorHandleTipProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const text = useMemo(() => {
|
||||
if (type === ErrorHandleTypeEnum.failBranch)
|
||||
return t('workflow.nodes.common.errorHandle.failBranch.inLog')
|
||||
|
||||
if (type === ErrorHandleTypeEnum.defaultValue)
|
||||
return t('workflow.nodes.common.errorHandle.defaultValue.inLog')
|
||||
}, [])
|
||||
|
||||
if (!type)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className='relative flex p-2 pr-[52px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xs'
|
||||
>
|
||||
<div
|
||||
className='absolute inset-0 opacity-40 rounded-lg'
|
||||
style={{
|
||||
background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)',
|
||||
}}
|
||||
></div>
|
||||
<RiAlertFill className='shrink-0 mr-1 w-4 h-4 text-text-warning-secondary' />
|
||||
<div className='grow system-xs-medium text-text-primary'>
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorHandleTip
|
||||
@@ -0,0 +1,95 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiCheckLine,
|
||||
} from '@remixicon/react'
|
||||
import { ErrorHandleTypeEnum } from './types'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type ErrorHandleTypeSelectorProps = {
|
||||
value: ErrorHandleTypeEnum
|
||||
onSelected: (value: ErrorHandleTypeEnum) => void
|
||||
}
|
||||
const ErrorHandleTypeSelector = ({
|
||||
value,
|
||||
onSelected,
|
||||
}: ErrorHandleTypeSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const options = [
|
||||
{
|
||||
value: ErrorHandleTypeEnum.none,
|
||||
label: t('workflow.nodes.common.errorHandle.none.title'),
|
||||
description: t('workflow.nodes.common.errorHandle.none.desc'),
|
||||
},
|
||||
{
|
||||
value: ErrorHandleTypeEnum.defaultValue,
|
||||
label: t('workflow.nodes.common.errorHandle.defaultValue.title'),
|
||||
description: t('workflow.nodes.common.errorHandle.defaultValue.desc'),
|
||||
},
|
||||
{
|
||||
value: ErrorHandleTypeEnum.failBranch,
|
||||
label: t('workflow.nodes.common.errorHandle.failBranch.title'),
|
||||
description: t('workflow.nodes.common.errorHandle.failBranch.desc'),
|
||||
},
|
||||
]
|
||||
const selectedOption = options.find(option => option.value === value)
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={4}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setOpen(v => !v)
|
||||
}}>
|
||||
<Button
|
||||
size='small'
|
||||
>
|
||||
{selectedOption?.label}
|
||||
<RiArrowDownSLine className='w-3.5 h-3.5' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<div className='p-1 w-[280px] border-[0.5px] border-components-panel-border rounded-xl bg-components-panel-bg-blur shadow-lg'>
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
className='flex p-2 pr-3 rounded-lg hover:bg-state-base-hover cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onSelected(option.value)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<div className='mr-1 w-4 shrink-0'>
|
||||
{
|
||||
value === option.value && (
|
||||
<RiCheckLine className='w-4 h-4 text-text-accent' />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='mb-0.5 system-sm-semibold text-text-secondary'>{option.label}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{option.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorHandleTypeSelector
|
||||
@@ -0,0 +1,32 @@
|
||||
import { RiMindMap } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const FailBranchCard = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='pt-2 px-4'>
|
||||
<div className='p-4 rounded-[10px] bg-workflow-process-bg'>
|
||||
<div className='flex items-center justify-center mb-2 w-8 h-8 rounded-[10px] border-[0.5px] bg-components-card-bg shadow-lg'>
|
||||
<RiMindMap className='w-5 h-5 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='mb-1 system-sm-medium text-text-secondary'>
|
||||
{t('workflow.nodes.common.errorHandle.failBranch.customize')}
|
||||
</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
{t('workflow.nodes.common.errorHandle.failBranch.customizeTip')}
|
||||
|
||||
<a
|
||||
href='https://docs.dify.ai/guides/workflow/error-handling'
|
||||
target='_blank'
|
||||
className='text-text-accent'
|
||||
>
|
||||
{t('workflow.common.learnMore')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FailBranchCard
|
||||
@@ -0,0 +1,123 @@
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { ErrorHandleTypeEnum } from './types'
|
||||
import type { DefaultValueForm } from './types'
|
||||
import { getDefaultValue } from './utils'
|
||||
import type {
|
||||
CommonNodeType,
|
||||
} from '@/app/components/workflow/types'
|
||||
import {
|
||||
useEdgesInteractions,
|
||||
useNodeDataUpdate,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
|
||||
export const useDefaultValue = (
|
||||
id: string,
|
||||
) => {
|
||||
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||
const handleFormChange = useCallback((
|
||||
{
|
||||
key,
|
||||
value,
|
||||
type,
|
||||
}: DefaultValueForm,
|
||||
data: CommonNodeType,
|
||||
) => {
|
||||
const default_value = data.default_value || []
|
||||
const index = default_value.findIndex(form => form.key === key)
|
||||
|
||||
if (index > -1) {
|
||||
const newDefaultValue = [...default_value]
|
||||
newDefaultValue[index].value = value
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data: {
|
||||
default_value: newDefaultValue,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data: {
|
||||
default_value: [
|
||||
...default_value,
|
||||
{
|
||||
key,
|
||||
value,
|
||||
type,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
}, [handleNodeDataUpdateWithSyncDraft, id])
|
||||
|
||||
return {
|
||||
handleFormChange,
|
||||
}
|
||||
}
|
||||
|
||||
export const useErrorHandle = (
|
||||
id: string,
|
||||
data: CommonNodeType,
|
||||
) => {
|
||||
const initCollapsed = useMemo(() => {
|
||||
if (data.error_strategy === ErrorHandleTypeEnum.none)
|
||||
return true
|
||||
|
||||
return false
|
||||
}, [data.error_strategy])
|
||||
const [collapsed, setCollapsed] = useState(initCollapsed)
|
||||
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||
const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions()
|
||||
|
||||
const handleErrorHandleTypeChange = useCallback((value: ErrorHandleTypeEnum, data: CommonNodeType) => {
|
||||
if (data.error_strategy === value)
|
||||
return
|
||||
|
||||
if (value === ErrorHandleTypeEnum.none) {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data: {
|
||||
error_strategy: undefined,
|
||||
default_value: undefined,
|
||||
},
|
||||
})
|
||||
setCollapsed(true)
|
||||
handleEdgeDeleteByDeleteBranch(id, ErrorHandleTypeEnum.failBranch)
|
||||
}
|
||||
|
||||
if (value === ErrorHandleTypeEnum.failBranch) {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data: {
|
||||
error_strategy: value,
|
||||
default_value: undefined,
|
||||
},
|
||||
})
|
||||
setCollapsed(false)
|
||||
}
|
||||
|
||||
if (value === ErrorHandleTypeEnum.defaultValue) {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data: {
|
||||
error_strategy: value,
|
||||
default_value: getDefaultValue(data),
|
||||
},
|
||||
})
|
||||
setCollapsed(false)
|
||||
handleEdgeDeleteByDeleteBranch(id, ErrorHandleTypeEnum.failBranch)
|
||||
}
|
||||
}, [id, handleNodeDataUpdateWithSyncDraft, handleEdgeDeleteByDeleteBranch])
|
||||
|
||||
return {
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
handleErrorHandleTypeChange,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { VarType } from '@/app/components/workflow/types'
|
||||
|
||||
export enum ErrorHandleTypeEnum {
|
||||
none = 'none',
|
||||
failBranch = 'fail-branch',
|
||||
defaultValue = 'default-value',
|
||||
}
|
||||
|
||||
export type DefaultValueForm = {
|
||||
key: string
|
||||
type: VarType
|
||||
value?: any
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import {
|
||||
BlockEnum,
|
||||
VarType,
|
||||
} from '@/app/components/workflow/types'
|
||||
import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types'
|
||||
|
||||
const getDefaultValueByType = (type: VarType) => {
|
||||
if (type === VarType.string)
|
||||
return ''
|
||||
|
||||
if (type === VarType.number)
|
||||
return 0
|
||||
|
||||
if (type === VarType.object)
|
||||
return '{}'
|
||||
|
||||
if (type === VarType.arrayObject || type === VarType.arrayString || type === VarType.arrayNumber || type === VarType.arrayFile)
|
||||
return '[]'
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
export const getDefaultValue = (data: CommonNodeType) => {
|
||||
const { type } = data
|
||||
|
||||
if (type === BlockEnum.LLM) {
|
||||
return [{
|
||||
key: 'text',
|
||||
type: VarType.string,
|
||||
value: getDefaultValueByType(VarType.string),
|
||||
}]
|
||||
}
|
||||
|
||||
if (type === BlockEnum.HttpRequest) {
|
||||
return [
|
||||
{
|
||||
key: 'body',
|
||||
type: VarType.string,
|
||||
value: getDefaultValueByType(VarType.string),
|
||||
},
|
||||
{
|
||||
key: 'status_code',
|
||||
type: VarType.number,
|
||||
value: getDefaultValueByType(VarType.number),
|
||||
},
|
||||
{
|
||||
key: 'headers',
|
||||
type: VarType.object,
|
||||
value: getDefaultValueByType(VarType.object),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (type === BlockEnum.Tool) {
|
||||
return [
|
||||
{
|
||||
key: 'text',
|
||||
type: VarType.string,
|
||||
value: getDefaultValueByType(VarType.string),
|
||||
},
|
||||
{
|
||||
key: 'json',
|
||||
type: VarType.arrayObject,
|
||||
value: getDefaultValueByType(VarType.arrayObject),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
if (type === BlockEnum.Code) {
|
||||
const { outputs } = data as CodeNodeType
|
||||
|
||||
return Object.keys(outputs).map((key) => {
|
||||
return {
|
||||
key,
|
||||
type: outputs[key].type,
|
||||
value: getDefaultValueByType(outputs[key].type),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -24,12 +25,14 @@ type AddProps = {
|
||||
nodeData: CommonNodeType
|
||||
sourceHandle: string
|
||||
isParallel?: boolean
|
||||
isFailBranch?: boolean
|
||||
}
|
||||
const Add = ({
|
||||
nodeId,
|
||||
nodeData,
|
||||
sourceHandle,
|
||||
isParallel,
|
||||
isFailBranch,
|
||||
}: AddProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -58,6 +61,15 @@ const Add = ({
|
||||
setOpen(newOpen)
|
||||
}, [checkParallelLimit, nodeId, sourceHandle])
|
||||
|
||||
const tip = useMemo(() => {
|
||||
if (isFailBranch)
|
||||
return t('workflow.common.addFailureBranch')
|
||||
|
||||
if (isParallel)
|
||||
return t('workflow.common.addParallelNode')
|
||||
|
||||
return t('workflow.panel.selectNextStep')
|
||||
}, [isFailBranch, isParallel, t])
|
||||
const renderTrigger = useCallback((open: boolean) => {
|
||||
return (
|
||||
<div
|
||||
@@ -72,15 +84,11 @@ const Add = ({
|
||||
<RiAddLine className='w-3 h-3' />
|
||||
</div>
|
||||
<div className='flex items-center uppercase'>
|
||||
{
|
||||
isParallel
|
||||
? t('workflow.common.addParallelNode')
|
||||
: t('workflow.panel.selectNextStep')
|
||||
}
|
||||
{tip}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [t, nodesReadOnly, isParallel])
|
||||
}, [nodesReadOnly, tip])
|
||||
|
||||
return (
|
||||
<BlockSelector
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
CommonNodeType,
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type ContainerProps = {
|
||||
nodeId: string
|
||||
@@ -11,6 +12,7 @@ type ContainerProps = {
|
||||
sourceHandle: string
|
||||
nextNodes: Node[]
|
||||
branchName?: string
|
||||
isFailBranch?: boolean
|
||||
}
|
||||
|
||||
const Container = ({
|
||||
@@ -19,13 +21,20 @@ const Container = ({
|
||||
sourceHandle,
|
||||
nextNodes,
|
||||
branchName,
|
||||
isFailBranch,
|
||||
}: ContainerProps) => {
|
||||
return (
|
||||
<div className='p-0.5 space-y-0.5 rounded-[10px] bg-background-section-burn'>
|
||||
<div className={cn(
|
||||
'p-0.5 space-y-0.5 rounded-[10px] bg-background-section-burn',
|
||||
isFailBranch && 'border-[0.5px] border-state-warning-hover-alt bg-state-warning-hover',
|
||||
)}>
|
||||
{
|
||||
branchName && (
|
||||
<div
|
||||
className='flex items-center px-2 system-2xs-semibold-uppercase text-text-tertiary truncate'
|
||||
className={cn(
|
||||
'flex items-center px-2 system-2xs-semibold-uppercase text-text-tertiary truncate',
|
||||
isFailBranch && 'text-text-warning',
|
||||
)}
|
||||
title={branchName}
|
||||
>
|
||||
{branchName}
|
||||
@@ -44,6 +53,7 @@ const Container = ({
|
||||
}
|
||||
<Add
|
||||
isParallel={!!nextNodes.length}
|
||||
isFailBranch={isFailBranch}
|
||||
nodeId={nodeId}
|
||||
nodeData={nodeData}
|
||||
sourceHandle={sourceHandle}
|
||||
|
||||
@@ -14,6 +14,8 @@ import type {
|
||||
import { BlockEnum } from '../../../../types'
|
||||
import Line from './line'
|
||||
import Container from './container'
|
||||
import { hasErrorHandleNode } from '@/app/components/workflow/utils'
|
||||
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||
|
||||
type NextStepProps = {
|
||||
selectedNode: Node
|
||||
@@ -28,25 +30,54 @@ const NextStep = ({
|
||||
const branches = useMemo(() => {
|
||||
return data._targetBranches || []
|
||||
}, [data])
|
||||
const nodeWithBranches = data.type === BlockEnum.IfElse || data.type === BlockEnum.QuestionClassifier
|
||||
const edges = useEdges()
|
||||
const outgoers = getOutgoers(selectedNode as Node, store.getState().getNodes(), edges)
|
||||
const connectedEdges = getConnectedEdges([selectedNode] as Node[], edges).filter(edge => edge.source === selectedNode!.id)
|
||||
|
||||
const branchesOutgoers = useMemo(() => {
|
||||
if (!branches?.length)
|
||||
return []
|
||||
const list = useMemo(() => {
|
||||
let items = []
|
||||
if (branches?.length) {
|
||||
items = branches.map((branch, index) => {
|
||||
const connected = connectedEdges.filter(edge => edge.sourceHandle === branch.id)
|
||||
const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!)
|
||||
|
||||
return branches.map((branch) => {
|
||||
const connected = connectedEdges.filter(edge => edge.sourceHandle === branch.id)
|
||||
return {
|
||||
branch: {
|
||||
...branch,
|
||||
name: data.type === BlockEnum.QuestionClassifier ? `${t('workflow.nodes.questionClassifiers.class')} ${index + 1}` : branch.name,
|
||||
},
|
||||
nextNodes,
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
const connected = connectedEdges.filter(edge => edge.sourceHandle === 'source')
|
||||
const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!)
|
||||
|
||||
return {
|
||||
branch,
|
||||
items = [{
|
||||
branch: {
|
||||
id: '',
|
||||
name: '',
|
||||
},
|
||||
nextNodes,
|
||||
}]
|
||||
|
||||
if (data.error_strategy === ErrorHandleTypeEnum.failBranch && hasErrorHandleNode(data.type)) {
|
||||
const connected = connectedEdges.filter(edge => edge.sourceHandle === ErrorHandleTypeEnum.failBranch)
|
||||
const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!)
|
||||
|
||||
items.push({
|
||||
branch: {
|
||||
id: ErrorHandleTypeEnum.failBranch,
|
||||
name: t('workflow.common.onFailure'),
|
||||
},
|
||||
nextNodes,
|
||||
})
|
||||
}
|
||||
})
|
||||
}, [branches, connectedEdges, outgoers])
|
||||
}
|
||||
|
||||
return items
|
||||
}, [branches, connectedEdges, data.error_strategy, data.type, outgoers, t])
|
||||
|
||||
return (
|
||||
<div className='flex py-1'>
|
||||
@@ -57,34 +88,23 @@ const NextStep = ({
|
||||
/>
|
||||
</div>
|
||||
<Line
|
||||
list={nodeWithBranches ? branchesOutgoers.map(item => item.nextNodes.length + 1) : [1]}
|
||||
list={list.length ? list.map(item => item.nextNodes.length + 1) : [1]}
|
||||
/>
|
||||
<div className='grow space-y-2'>
|
||||
{
|
||||
!nodeWithBranches && (
|
||||
<Container
|
||||
nodeId={selectedNode!.id}
|
||||
nodeData={selectedNode!.data}
|
||||
sourceHandle='source'
|
||||
nextNodes={outgoers}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
nodeWithBranches && (
|
||||
branchesOutgoers.map((item, index) => {
|
||||
return (
|
||||
<Container
|
||||
key={item.branch.id}
|
||||
nodeId={selectedNode!.id}
|
||||
nodeData={selectedNode!.data}
|
||||
sourceHandle={item.branch.id}
|
||||
nextNodes={item.nextNodes}
|
||||
branchName={item.branch.name || `${t('workflow.nodes.questionClassifiers.class')} ${index + 1}`}
|
||||
/>
|
||||
)
|
||||
})
|
||||
)
|
||||
list.map((item, index) => {
|
||||
return (
|
||||
<Container
|
||||
key={index}
|
||||
nodeId={selectedNode!.id}
|
||||
nodeData={selectedNode!.data}
|
||||
sourceHandle={item.branch.id}
|
||||
nextNodes={item.nextNodes}
|
||||
branchName={item.branch.name}
|
||||
isFailBranch={item.branch.id === ErrorHandleTypeEnum.failBranch}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,10 @@ import {
|
||||
Position,
|
||||
} from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BlockEnum } from '../../../types'
|
||||
import {
|
||||
BlockEnum,
|
||||
NodeRunningStatus,
|
||||
} from '../../../types'
|
||||
import type { Node } from '../../../types'
|
||||
import BlockSelector from '../../../block-selector'
|
||||
import type { ToolDefaultValue } from '../../../block-selector/types'
|
||||
@@ -24,11 +27,13 @@ import {
|
||||
import {
|
||||
useStore,
|
||||
} from '../../../store'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type NodeHandleProps = {
|
||||
handleId: string
|
||||
handleClassName?: string
|
||||
nodeSelectorClassName?: string
|
||||
showExceptionStatus?: boolean
|
||||
} & Pick<Node, 'id' | 'data'>
|
||||
|
||||
export const NodeTargetHandle = memo(({
|
||||
@@ -72,14 +77,17 @@ export const NodeTargetHandle = memo(({
|
||||
id={handleId}
|
||||
type='target'
|
||||
position={Position.Left}
|
||||
className={`
|
||||
!w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]
|
||||
after:absolute after:w-0.5 after:h-2 after:left-1.5 after:top-1 after:bg-primary-500
|
||||
hover:scale-125 transition-all
|
||||
${!connected && 'after:opacity-0'}
|
||||
${data.type === BlockEnum.Start && 'opacity-0'}
|
||||
${handleClassName}
|
||||
`}
|
||||
className={cn(
|
||||
'!w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]',
|
||||
'after:absolute after:w-0.5 after:h-2 after:left-1.5 after:top-1 after:bg-workflow-link-line-handle',
|
||||
'hover:scale-125 transition-all',
|
||||
data._runningStatus === NodeRunningStatus.Succeeded && 'after:bg-workflow-link-line-success-handle',
|
||||
data._runningStatus === NodeRunningStatus.Failed && 'after:bg-workflow-link-line-error-handle',
|
||||
data._runningStatus === NodeRunningStatus.Exception && 'after:bg-workflow-link-line-failure-handle',
|
||||
!connected && 'after:opacity-0',
|
||||
data.type === BlockEnum.Start && 'opacity-0',
|
||||
handleClassName,
|
||||
)}
|
||||
isConnectable={isConnectable}
|
||||
onClick={handleHandleClick}
|
||||
>
|
||||
@@ -114,6 +122,7 @@ export const NodeSourceHandle = memo(({
|
||||
handleId,
|
||||
handleClassName,
|
||||
nodeSelectorClassName,
|
||||
showExceptionStatus,
|
||||
}: NodeHandleProps) => {
|
||||
const { t } = useTranslation()
|
||||
const notInitialWorkflow = useStore(s => s.notInitialWorkflow)
|
||||
@@ -157,13 +166,16 @@ export const NodeSourceHandle = memo(({
|
||||
id={handleId}
|
||||
type='source'
|
||||
position={Position.Right}
|
||||
className={`
|
||||
group/handle !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]
|
||||
after:absolute after:w-0.5 after:h-2 after:right-1.5 after:top-1 after:bg-primary-500
|
||||
hover:scale-125 transition-all
|
||||
${!connected && 'after:opacity-0'}
|
||||
${handleClassName}
|
||||
`}
|
||||
className={cn(
|
||||
'group/handle !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]',
|
||||
'after:absolute after:w-0.5 after:h-2 after:right-1.5 after:top-1 after:bg-workflow-link-line-handle',
|
||||
'hover:scale-125 transition-all',
|
||||
data._runningStatus === NodeRunningStatus.Succeeded && 'after:bg-workflow-link-line-success-handle',
|
||||
data._runningStatus === NodeRunningStatus.Failed && 'after:bg-workflow-link-line-error-handle',
|
||||
showExceptionStatus && data._runningStatus === NodeRunningStatus.Exception && 'after:bg-workflow-link-line-failure-handle',
|
||||
!connected && 'after:opacity-0',
|
||||
handleClassName,
|
||||
)}
|
||||
isConnectable={isConnectable}
|
||||
onClick={handleHandleClick}
|
||||
>
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
} from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@@ -15,28 +11,14 @@ type Props = {
|
||||
}
|
||||
|
||||
const OutputVars: FC<Props> = ({
|
||||
className,
|
||||
title,
|
||||
children,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isFold, {
|
||||
toggle: toggleFold,
|
||||
}] = useBoolean(true)
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
onClick={toggleFold}
|
||||
className={cn(className, 'flex justify-between system-sm-semibold-uppercase text-text-secondary cursor-pointer')}>
|
||||
<div>{title || t('workflow.nodes.common.outputVars')}</div>
|
||||
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary transform transition-transform' style={{ transform: isFold ? 'rotate(-90deg)' : 'rotate(0deg)' }} />
|
||||
</div>
|
||||
{!isFold && (
|
||||
<div className='mt-2 space-y-1'>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<FieldCollapse title={title || t('workflow.nodes.common.outputVars')}>
|
||||
{children}
|
||||
</FieldCollapse>
|
||||
)
|
||||
}
|
||||
type VarItemProps = {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others
|
||||
import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import cn from '@/utils/classnames'
|
||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
|
||||
type VariableTagProps = {
|
||||
valueSelector: ValueSelector
|
||||
@@ -45,6 +46,7 @@ const VariableTag = ({
|
||||
const isValid = Boolean(node) || isEnv || isChatVar
|
||||
|
||||
const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.')
|
||||
const isException = isExceptionVariable(variableName, node?.data.type)
|
||||
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
@@ -67,12 +69,12 @@ const VariableTag = ({
|
||||
</>
|
||||
)}
|
||||
<Line3 className='shrink-0 mx-0.5' />
|
||||
<Variable02 className='shrink-0 mr-0.5 w-3.5 h-3.5 text-text-accent' />
|
||||
<Variable02 className={cn('shrink-0 mr-0.5 w-3.5 h-3.5 text-text-accent', isException && 'text-text-warning')} />
|
||||
</>)}
|
||||
{isEnv && <Env className='shrink-0 mr-0.5 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
<div
|
||||
className={cn('truncate ml-0.5 text-text-accent font-medium', (isEnv || isChatVar) && 'text-text-secondary')}
|
||||
className={cn('truncate ml-0.5 text-text-accent font-medium', (isEnv || isChatVar) && 'text-text-secondary', isException && 'text-text-warning')}
|
||||
title={variableName}
|
||||
>
|
||||
{variableName}
|
||||
|
||||
@@ -315,6 +315,24 @@ const formatItem = (
|
||||
}
|
||||
}
|
||||
|
||||
const { error_strategy } = data
|
||||
|
||||
if (error_strategy) {
|
||||
res.vars = [
|
||||
...res.vars,
|
||||
{
|
||||
variable: 'error_message',
|
||||
type: VarType.string,
|
||||
isException: true,
|
||||
},
|
||||
{
|
||||
variable: 'error_type',
|
||||
type: VarType.string,
|
||||
isException: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const selector = [id]
|
||||
res.vars = res.vars.filter((v) => {
|
||||
const isCurrentMatched = filterVar(v, (() => {
|
||||
|
||||
@@ -36,6 +36,7 @@ import TypeSelector from '@/app/components/workflow/nodes/_base/components/selec
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
|
||||
const TRIGGER_DEFAULT_WIDTH = 227
|
||||
|
||||
@@ -224,16 +225,18 @@ const VarReferencePicker: FC<Props> = ({
|
||||
isConstant: !!isConstant,
|
||||
})
|
||||
|
||||
const { isEnv, isChatVar, isValidVar } = useMemo(() => {
|
||||
const { isEnv, isChatVar, isValidVar, isException } = useMemo(() => {
|
||||
const isEnv = isENV(value as ValueSelector)
|
||||
const isChatVar = isConversationVar(value as ValueSelector)
|
||||
const isValidVar = Boolean(outputVarNode) || isEnv || isChatVar
|
||||
const isException = isExceptionVariable(varName, outputVarNode?.type)
|
||||
return {
|
||||
isEnv,
|
||||
isChatVar,
|
||||
isValidVar,
|
||||
isException,
|
||||
}
|
||||
}, [value, outputVarNode])
|
||||
}, [value, outputVarNode, varName])
|
||||
|
||||
// 8(left/right-padding) + 14(icon) + 4 + 14 + 2 = 42 + 17 buff
|
||||
const availableWidth = triggerWidth - 56
|
||||
@@ -335,7 +338,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
||||
{isEnv && <Env className='w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
<div className={cn('ml-0.5 text-xs font-medium truncate', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700')} title={varName} style={{
|
||||
<div className={cn('ml-0.5 text-xs font-medium truncate', isEnv && '!text-text-secondary', isChatVar && 'text-util-colors-teal-teal-700', isException && 'text-text-warning')} title={varName} style={{
|
||||
maxWidth: maxVarNameWidth,
|
||||
}}>{varName}</div>
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,7 @@ type ItemProps = {
|
||||
onHovering?: (value: boolean) => void
|
||||
itemWidth?: number
|
||||
isSupportFileVar?: boolean
|
||||
isException?: boolean
|
||||
}
|
||||
|
||||
const Item: FC<ItemProps> = ({
|
||||
@@ -48,6 +49,7 @@ const Item: FC<ItemProps> = ({
|
||||
onHovering,
|
||||
itemWidth,
|
||||
isSupportFileVar,
|
||||
isException,
|
||||
}) => {
|
||||
const isFile = itemData.type === VarType.file
|
||||
const isObj = ([VarType.object, VarType.file].includes(itemData.type) && itemData.children && itemData.children.length > 0)
|
||||
@@ -109,7 +111,7 @@ const Item: FC<ItemProps> = ({
|
||||
onClick={handleChosen}
|
||||
>
|
||||
<div className='flex items-center w-0 grow'>
|
||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5 text-text-accent' />}
|
||||
{!isEnv && !isChatVar && <Variable02 className={cn('shrink-0 w-3.5 h-3.5 text-text-accent', isException && 'text-text-warning')} />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
{!isEnv && !isChatVar && (
|
||||
@@ -216,6 +218,7 @@ const ObjectChildren: FC<ObjectChildrenProps> = ({
|
||||
onChange={onChange}
|
||||
onHovering={setIsChildrenHovering}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isException={v.isException}
|
||||
/>
|
||||
))
|
||||
}
|
||||
@@ -312,6 +315,7 @@ const VarReferenceVars: FC<Props> = ({
|
||||
onChange={onChange}
|
||||
itemWidth={itemWidth}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
isException={v.isException}
|
||||
/>
|
||||
))}
|
||||
</div>))
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import produce from 'immer'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { type OutputVar } from '../../code/types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import type {
|
||||
CodeNodeType,
|
||||
OutputVar,
|
||||
} from '../../code/types'
|
||||
import type {
|
||||
ValueSelector,
|
||||
} from '@/app/components/workflow/types'
|
||||
import {
|
||||
BlockEnum,
|
||||
VarType,
|
||||
} from '@/app/components/workflow/types'
|
||||
import {
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||
import { getDefaultValue } from '@/app/components/workflow/nodes/_base/components/error-handle/utils'
|
||||
|
||||
type Params<T> = {
|
||||
id: string
|
||||
@@ -29,6 +39,9 @@ function useOutputVarList<T>({
|
||||
const handleVarsChange = useCallback((newVars: OutputVar, changedIndex?: number, newKey?: string) => {
|
||||
const newInputs = produce(inputs, (draft: any) => {
|
||||
draft[varKey] = newVars
|
||||
|
||||
if ((inputs as CodeNodeType).type === BlockEnum.Code && (inputs as CodeNodeType).error_strategy === ErrorHandleTypeEnum.defaultValue && varKey === 'outputs')
|
||||
draft.default_value = getDefaultValue(draft as any)
|
||||
})
|
||||
setInputs(newInputs)
|
||||
|
||||
@@ -59,6 +72,9 @@ function useOutputVarList<T>({
|
||||
children: null,
|
||||
},
|
||||
}
|
||||
|
||||
if ((inputs as CodeNodeType).type === BlockEnum.Code && (inputs as CodeNodeType).error_strategy === ErrorHandleTypeEnum.defaultValue && varKey === 'outputs')
|
||||
draft.default_value = getDefaultValue(draft as any)
|
||||
})
|
||||
setInputs(newInputs)
|
||||
onOutputKeyOrdersChange([...outputKeyOrders, newKey])
|
||||
@@ -84,6 +100,9 @@ function useOutputVarList<T>({
|
||||
|
||||
const newInputs = produce(inputs, (draft: any) => {
|
||||
delete draft[varKey][key]
|
||||
|
||||
if ((inputs as CodeNodeType).type === BlockEnum.Code && (inputs as CodeNodeType).error_strategy === ErrorHandleTypeEnum.defaultValue && varKey === 'outputs')
|
||||
draft.default_value = getDefaultValue(draft as any)
|
||||
})
|
||||
setInputs(newInputs)
|
||||
onOutputKeyOrdersChange(outputKeyOrders.filter((_, i) => i !== index))
|
||||
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
useRef,
|
||||
} from 'react'
|
||||
import {
|
||||
RiCheckboxCircleLine,
|
||||
RiErrorWarningLine,
|
||||
RiAlertFill,
|
||||
RiCheckboxCircleFill,
|
||||
RiErrorWarningFill,
|
||||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
useNodesReadOnly,
|
||||
useToolIcon,
|
||||
} from '../../hooks'
|
||||
import { hasErrorHandleNode } from '../../utils'
|
||||
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
||||
import type { IterationNodeType } from '../iteration/types'
|
||||
import {
|
||||
@@ -32,6 +34,7 @@ import {
|
||||
} from './components/node-handle'
|
||||
import NodeResizer from './components/node-resizer'
|
||||
import NodeControl from './components/node-control'
|
||||
import ErrorHandleOnNode from './components/error-handle/error-handle-on-node'
|
||||
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
@@ -71,11 +74,13 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
showRunningBorder,
|
||||
showSuccessBorder,
|
||||
showFailedBorder,
|
||||
showExceptionBorder,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
|
||||
showSuccessBorder: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
|
||||
showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
|
||||
showExceptionBorder: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder,
|
||||
}
|
||||
}, [data._runningStatus, showSelectedBorder])
|
||||
|
||||
@@ -85,6 +90,7 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
'flex border-[2px] rounded-2xl',
|
||||
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
|
||||
!showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
|
||||
data._waitingRun && 'opacity-70',
|
||||
)}
|
||||
ref={nodeRef}
|
||||
style={{
|
||||
@@ -99,9 +105,10 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
data.type !== BlockEnum.Iteration && 'w-[240px] bg-workflow-block-bg',
|
||||
data.type === BlockEnum.Iteration && 'flex flex-col w-full h-full bg-[#fcfdff]/80',
|
||||
!data._runningStatus && 'hover:shadow-lg',
|
||||
showRunningBorder && '!border-primary-500',
|
||||
showSuccessBorder && '!border-[#12B76A]',
|
||||
showFailedBorder && '!border-[#F04438]',
|
||||
showRunningBorder && '!border-state-accent-solid',
|
||||
showSuccessBorder && '!border-state-success-solid',
|
||||
showFailedBorder && '!border-state-destructive-solid',
|
||||
showExceptionBorder && '!border-state-warning-solid',
|
||||
data._isBundled && '!shadow-lg',
|
||||
)}
|
||||
>
|
||||
@@ -192,24 +199,29 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
</div>
|
||||
{
|
||||
data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && (
|
||||
<div className='mr-1.5 text-xs font-medium text-primary-600'>
|
||||
<div className='mr-1.5 text-xs font-medium text-text-accent'>
|
||||
{data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex}/{data._iterationLength}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
(data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && (
|
||||
<RiLoader2Line className='w-3.5 h-3.5 text-primary-600 animate-spin' />
|
||||
<RiLoader2Line className='w-3.5 h-3.5 text-text-accent animate-spin' />
|
||||
)
|
||||
}
|
||||
{
|
||||
data._runningStatus === NodeRunningStatus.Succeeded && (
|
||||
<RiCheckboxCircleLine className='w-3.5 h-3.5 text-[#12B76A]' />
|
||||
<RiCheckboxCircleFill className='w-3.5 h-3.5 text-text-success' />
|
||||
)
|
||||
}
|
||||
{
|
||||
data._runningStatus === NodeRunningStatus.Failed && (
|
||||
<RiErrorWarningLine className='w-3.5 h-3.5 text-[#F04438]' />
|
||||
<RiErrorWarningFill className='w-3.5 h-3.5 text-text-destructive' />
|
||||
)
|
||||
}
|
||||
{
|
||||
data._runningStatus === NodeRunningStatus.Exception && (
|
||||
<RiAlertFill className='w-3.5 h-3.5 text-text-warning-secondary' />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
@@ -225,6 +237,14 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasErrorHandleNode(data.type) && (
|
||||
<ErrorHandleOnNode
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
data.desc && data.type !== BlockEnum.Iteration && (
|
||||
<div className='px-3 pt-1 pb-2 system-xs-regular text-text-tertiary whitespace-pre-line break-words'>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
DescriptionInput,
|
||||
TitleInput,
|
||||
} from './components/title-description-input'
|
||||
import ErrorHandleOnPanel from './components/error-handle/error-handle-on-panel'
|
||||
import { useResizePanel } from './hooks/use-resize-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
@@ -34,7 +35,10 @@ import {
|
||||
useWorkflow,
|
||||
useWorkflowHistory,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { canRunBySingle } from '@/app/components/workflow/utils'
|
||||
import {
|
||||
canRunBySingle,
|
||||
hasErrorHandleNode,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
@@ -161,9 +165,17 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='py-2'>
|
||||
<div>
|
||||
{cloneElement(children, { id, data })}
|
||||
</div>
|
||||
{
|
||||
hasErrorHandleNode(data.type) && (
|
||||
<ErrorHandleOnPanel
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!availableNextBlocks.length && (
|
||||
<div className='p-4 border-t-[0.5px] border-t-black/5'>
|
||||
|
||||
@@ -72,7 +72,7 @@ const Panel: FC<NodePanelProps<DocExtractorNodeType>> = ({
|
||||
</Field>
|
||||
</div>
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<div>
|
||||
<OutputVars>
|
||||
<VarItem
|
||||
name='text'
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { Timeout as TimeoutPayloadType } from '../../types'
|
||||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||
|
||||
type Props = {
|
||||
readonly: boolean
|
||||
@@ -53,58 +51,43 @@ const Timeout: FC<Props> = ({ readonly, payload, onChange }) => {
|
||||
const { t } = useTranslation()
|
||||
const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {}
|
||||
|
||||
const [isFold, {
|
||||
toggle: toggleFold,
|
||||
}] = useBoolean(true)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
onClick={toggleFold}
|
||||
className={cn('flex justify-between leading-[18px] text-[13px] font-semibold text-gray-700 uppercase cursor-pointer')}>
|
||||
<div>{t(`${i18nPrefix}.timeout.title`)}</div>
|
||||
<ChevronRight className='w-4 h-4 text-gray-500 transform transition-transform' style={{ transform: isFold ? 'rotate(0deg)' : 'rotate(90deg)' }} />
|
||||
<FieldCollapse title={t(`${i18nPrefix}.timeout.title`)}>
|
||||
<div className='mt-2 space-y-1'>
|
||||
<div className="space-y-3">
|
||||
<InputField
|
||||
title={t('workflow.nodes.http.timeout.connectLabel')!}
|
||||
description={t('workflow.nodes.http.timeout.connectPlaceholder')!}
|
||||
placeholder={t('workflow.nodes.http.timeout.connectPlaceholder')!}
|
||||
readOnly={readonly}
|
||||
value={connect}
|
||||
onChange={v => onChange?.({ ...payload, connect: v })}
|
||||
min={1}
|
||||
max={max_connect_timeout || 300}
|
||||
/>
|
||||
<InputField
|
||||
title={t('workflow.nodes.http.timeout.readLabel')!}
|
||||
description={t('workflow.nodes.http.timeout.readPlaceholder')!}
|
||||
placeholder={t('workflow.nodes.http.timeout.readPlaceholder')!}
|
||||
readOnly={readonly}
|
||||
value={read}
|
||||
onChange={v => onChange?.({ ...payload, read: v })}
|
||||
min={1}
|
||||
max={max_read_timeout || 600}
|
||||
/>
|
||||
<InputField
|
||||
title={t('workflow.nodes.http.timeout.writeLabel')!}
|
||||
description={t('workflow.nodes.http.timeout.writePlaceholder')!}
|
||||
placeholder={t('workflow.nodes.http.timeout.writePlaceholder')!}
|
||||
readOnly={readonly}
|
||||
value={write}
|
||||
onChange={v => onChange?.({ ...payload, write: v })}
|
||||
min={1}
|
||||
max={max_write_timeout || 600}
|
||||
/>
|
||||
</div>
|
||||
{!isFold && (
|
||||
<div className='mt-2 space-y-1'>
|
||||
<div className="space-y-3">
|
||||
<InputField
|
||||
title={t('workflow.nodes.http.timeout.connectLabel')!}
|
||||
description={t('workflow.nodes.http.timeout.connectPlaceholder')!}
|
||||
placeholder={t('workflow.nodes.http.timeout.connectPlaceholder')!}
|
||||
readOnly={readonly}
|
||||
value={connect}
|
||||
onChange={v => onChange?.({ ...payload, connect: v })}
|
||||
min={1}
|
||||
max={max_connect_timeout || 300}
|
||||
/>
|
||||
<InputField
|
||||
title={t('workflow.nodes.http.timeout.readLabel')!}
|
||||
description={t('workflow.nodes.http.timeout.readPlaceholder')!}
|
||||
placeholder={t('workflow.nodes.http.timeout.readPlaceholder')!}
|
||||
readOnly={readonly}
|
||||
value={read}
|
||||
onChange={v => onChange?.({ ...payload, read: v })}
|
||||
min={1}
|
||||
max={max_read_timeout || 600}
|
||||
/>
|
||||
<InputField
|
||||
title={t('workflow.nodes.http.timeout.writeLabel')!}
|
||||
description={t('workflow.nodes.http.timeout.writePlaceholder')!}
|
||||
placeholder={t('workflow.nodes.http.timeout.writePlaceholder')!}
|
||||
readOnly={readonly}
|
||||
value={write}
|
||||
onChange={v => onChange?.({ ...payload, write: v })}
|
||||
min={1}
|
||||
max={max_write_timeout || 600}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</>
|
||||
</FieldCollapse>
|
||||
)
|
||||
}
|
||||
export default React.memo(Timeout)
|
||||
|
||||
@@ -65,7 +65,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='pt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.api`)}
|
||||
@@ -136,14 +136,12 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
</Field>
|
||||
</div>
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-4'>
|
||||
<Timeout
|
||||
nodeId={id}
|
||||
readonly={readOnly}
|
||||
payload={inputs.timeout}
|
||||
onChange={setTimeout}
|
||||
/>
|
||||
</div>
|
||||
<Timeout
|
||||
nodeId={id}
|
||||
readonly={readOnly}
|
||||
payload={inputs.timeout}
|
||||
onChange={setTimeout}
|
||||
/>
|
||||
{(isShowAuthorization && !readOnly) && (
|
||||
<AuthorizationModal
|
||||
nodeId={id}
|
||||
@@ -154,7 +152,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
/>
|
||||
)}
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<div className=''>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { ComparisonOperator } from '../types'
|
||||
import {
|
||||
comparisonOperatorNotRequireValue,
|
||||
@@ -13,6 +14,11 @@ import { Variable02 } from '@/app/components/base/icons/src/vender/solid/develop
|
||||
import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import cn from '@/utils/classnames'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
import type {
|
||||
CommonNodeType,
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
|
||||
type ConditionValueProps = {
|
||||
variableSelector: string[]
|
||||
@@ -27,11 +33,14 @@ const ConditionValue = ({
|
||||
value,
|
||||
}: ConditionValueProps) => {
|
||||
const { t } = useTranslation()
|
||||
const nodes = useNodes()
|
||||
const variableName = labelName || (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.'))
|
||||
const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator
|
||||
const notHasValue = comparisonOperatorNotRequireValue(operator)
|
||||
const isEnvVar = isENV(variableSelector)
|
||||
const isChatVar = isConversationVar(variableSelector)
|
||||
const node: Node<CommonNodeType> | undefined = nodes.find(n => n.id === variableSelector[0]) as Node<CommonNodeType>
|
||||
const isException = isExceptionVariable(variableName, node?.data.type)
|
||||
const formatValue = useMemo(() => {
|
||||
if (notHasValue)
|
||||
return ''
|
||||
@@ -67,7 +76,7 @@ const ConditionValue = ({
|
||||
|
||||
return (
|
||||
<div className='flex items-center px-1 h-6 rounded-md bg-workflow-block-parma-bg'>
|
||||
{!isEnvVar && !isChatVar && <Variable02 className='shrink-0 mr-1 w-3.5 h-3.5 text-text-accent' />}
|
||||
{!isEnvVar && !isChatVar && <Variable02 className={cn('shrink-0 mr-1 w-3.5 h-3.5 text-text-accent', isException && 'text-text-warning')} />}
|
||||
{isEnvVar && <Env className='shrink-0 mr-1 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
|
||||
@@ -75,6 +84,7 @@ const ConditionValue = ({
|
||||
className={cn(
|
||||
'shrink-0 ml-0.5 truncate text-xs font-medium text-text-accent',
|
||||
!notHasValue && 'max-w-[70px]',
|
||||
isException && 'text-text-warning',
|
||||
)}
|
||||
title={variableName}
|
||||
>
|
||||
|
||||
@@ -18,7 +18,6 @@ import Switch from '@/app/components/base/switch'
|
||||
import Select from '@/app/components/base/select'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.iteration'
|
||||
|
||||
@@ -72,7 +71,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
} = useConfig(id, data)
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='pt-2 pb-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.input`)}
|
||||
@@ -131,9 +130,7 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
</Field>
|
||||
</div>)
|
||||
}
|
||||
<div className='px-4 py-2'>
|
||||
<Divider className='h-[1px]'/>
|
||||
</div>
|
||||
<Split />
|
||||
|
||||
<div className='px-4 py-2'>
|
||||
<Field title={t(`${i18nPrefix}.errorResponseMethod`)} >
|
||||
|
||||
@@ -53,7 +53,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
|
||||
}, [setRerankModelOpen])
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='pt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
{/* {JSON.stringify(inputs, null, 2)} */}
|
||||
<Field
|
||||
@@ -108,7 +108,7 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({
|
||||
</div>
|
||||
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<div>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
|
||||
@@ -42,8 +42,8 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
|
||||
} = useConfig(id, data)
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<div className='pt-2'>
|
||||
<div className='px-4 space-y-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.inputVar`)}
|
||||
>
|
||||
@@ -157,7 +157,7 @@ const Panel: FC<NodePanelProps<ListFilterNodeType>> = ({
|
||||
</Field>
|
||||
<Split />
|
||||
</div>
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<div>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
|
||||
@@ -270,17 +270,15 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
/>
|
||||
</div>
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
name='text'
|
||||
type='string'
|
||||
description={t(`${i18nPrefix}.outputVars.output`)}
|
||||
/>
|
||||
</>
|
||||
</OutputVars>
|
||||
</div>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
name='text'
|
||||
type='string'
|
||||
description={t(`${i18nPrefix}.outputVars.output`)}
|
||||
/>
|
||||
</>
|
||||
</OutputVars>
|
||||
{isShowSingleRun && (
|
||||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { InputVarType, type NodePanelProps } from '@/app/components/workflow/typ
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.parameterExtractor'
|
||||
const i18nCommonPrefix = 'workflow.common'
|
||||
@@ -67,8 +68,8 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
||||
const model = inputs.model
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<div className='pt-2'>
|
||||
<div className='px-4 space-y-4'>
|
||||
<Field
|
||||
title={t(`${i18nCommonPrefix}.model`)}
|
||||
>
|
||||
@@ -157,38 +158,33 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
/>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.advancedSetting`)}
|
||||
supportFold
|
||||
>
|
||||
<>
|
||||
|
||||
{/* Memory */}
|
||||
{isChatMode && (
|
||||
<div className='mt-4'>
|
||||
<MemoryConfig
|
||||
readonly={readOnly}
|
||||
config={{ data: inputs.memory }}
|
||||
onChange={handleMemoryChange}
|
||||
canSetRoleName={isCompletionModel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isSupportFunctionCall && (
|
||||
<div className='mt-2'>
|
||||
<ReasoningModePicker
|
||||
type={inputs.reasoning_mode}
|
||||
onChange={handleReasoningModeChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</Field>
|
||||
|
||||
</div>
|
||||
<FieldCollapse title={t(`${i18nPrefix}.advancedSetting`)}>
|
||||
<>
|
||||
{/* Memory */}
|
||||
{isChatMode && (
|
||||
<div className='mt-4'>
|
||||
<MemoryConfig
|
||||
readonly={readOnly}
|
||||
config={{ data: inputs.memory }}
|
||||
onChange={handleMemoryChange}
|
||||
canSetRoleName={isCompletionModel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isSupportFunctionCall && (
|
||||
<div className='mt-2'>
|
||||
<ReasoningModePicker
|
||||
type={inputs.reasoning_mode}
|
||||
onChange={handleReasoningModeChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</FieldCollapse>
|
||||
{inputs.parameters?.length > 0 && (<>
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<div>
|
||||
<OutputVars>
|
||||
<>
|
||||
{inputs.parameters.map((param, index) => (
|
||||
|
||||
@@ -26,6 +26,16 @@ const nodeDefault: NodeDefault<QuestionClassifierNodeType> = {
|
||||
name: '',
|
||||
},
|
||||
],
|
||||
_targetBranches: [
|
||||
{
|
||||
id: '1',
|
||||
name: '',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '',
|
||||
},
|
||||
],
|
||||
vision: {
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/befo
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.questionClassifiers'
|
||||
|
||||
@@ -55,8 +56,8 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
||||
const model = inputs.model
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<div className='pt-2'>
|
||||
<div className='px-4 space-y-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.model`)}
|
||||
>
|
||||
@@ -107,27 +108,27 @@ const Panel: FC<NodePanelProps<QuestionClassifierNodeType>> = ({
|
||||
readonly={readOnly}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.advancedSetting`)}
|
||||
supportFold
|
||||
>
|
||||
<AdvancedSetting
|
||||
hideMemorySetting={!isChatMode}
|
||||
instruction={inputs.instruction}
|
||||
onInstructionChange={handleInstructionChange}
|
||||
memory={inputs.memory}
|
||||
onMemoryChange={handleMemoryChange}
|
||||
readonly={readOnly}
|
||||
isChatApp={isChatMode}
|
||||
isChatModel={isChatModel}
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
/>
|
||||
</Field>
|
||||
<Split />
|
||||
</div>
|
||||
<FieldCollapse
|
||||
title={t(`${i18nPrefix}.advancedSetting`)}
|
||||
>
|
||||
<AdvancedSetting
|
||||
hideMemorySetting={!isChatMode}
|
||||
instruction={inputs.instruction}
|
||||
onInstructionChange={handleInstructionChange}
|
||||
memory={inputs.memory}
|
||||
onMemoryChange={handleMemoryChange}
|
||||
readonly={readOnly}
|
||||
isChatApp={isChatMode}
|
||||
isChatModel={isChatModel}
|
||||
hasSetBlockStatus={hasSetBlockStatus}
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
/>
|
||||
</FieldCollapse>
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<div>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
|
||||
@@ -95,7 +95,7 @@ const Panel: FC<NodePanelProps<TemplateTransformNodeType>> = ({
|
||||
/>
|
||||
</div>
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<div>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
|
||||
@@ -56,10 +56,10 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='pt-2'>
|
||||
{!readOnly && isShowAuthBtn && (
|
||||
<>
|
||||
<div className='px-4 pb-3'>
|
||||
<div className='px-4'>
|
||||
<Button
|
||||
variant='primary'
|
||||
className='w-full'
|
||||
@@ -71,7 +71,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
||||
</>
|
||||
)}
|
||||
{!isShowAuthBtn && <>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<div className='px-4 space-y-4'>
|
||||
{toolInputVarSchema.length > 0 && (
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.inputVars`)}
|
||||
@@ -118,7 +118,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<div>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
|
||||
@@ -21,6 +21,7 @@ import AddVariable from './add-variable'
|
||||
import NodeVariableItem from './node-variable-item'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
||||
type GroupItem = {
|
||||
@@ -128,12 +129,14 @@ const NodeGroupItem = ({
|
||||
|
||||
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
|
||||
const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.')
|
||||
const isException = isExceptionVariable(varName, node?.data.type)
|
||||
|
||||
return (
|
||||
<NodeVariableItem
|
||||
key={index}
|
||||
isEnv={isEnv}
|
||||
isChatVar={isChatVar}
|
||||
isException={isException}
|
||||
node={node as Node}
|
||||
varName={varName}
|
||||
showBorder={showSelectedBorder || showSelectionBorder}
|
||||
|
||||
@@ -17,6 +17,7 @@ type NodeVariableItemProps = {
|
||||
writeMode?: string
|
||||
showBorder?: boolean
|
||||
className?: string
|
||||
isException?: boolean
|
||||
}
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.assigner'
|
||||
@@ -29,6 +30,7 @@ const NodeVariableItem = ({
|
||||
writeMode,
|
||||
showBorder,
|
||||
className,
|
||||
isException,
|
||||
}: NodeVariableItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
@@ -50,14 +52,14 @@ const NodeVariableItem = ({
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600 w-full'>
|
||||
{!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5 text-primary-500' />}
|
||||
{!isEnv && !isChatVar && <Variable02 className={cn('shrink-0 w-3.5 h-3.5 text-primary-500', isException && 'text-text-warning')} />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
{!isChatVar && <div className={cn('max-w-[75px] truncate ml-0.5 system-xs-medium overflow-hidden text-ellipsis', isEnv && 'text-gray-900')} title={varName}>{varName}</div>}
|
||||
{!isChatVar && <div className={cn('max-w-[75px] truncate ml-0.5 system-xs-medium overflow-hidden text-ellipsis', isEnv && 'text-gray-900', isException && 'text-text-warning')} title={varName}>{varName}</div>}
|
||||
{isChatVar
|
||||
&& <div className='flex items-center w-full gap-1'>
|
||||
<div className='flex h-[18px] min-w-[18px] items-center gap-0.5 flex-1'>
|
||||
<BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />
|
||||
<div className='max-w-[75px] truncate ml-0.5 system-xs-medium overflow-hidden text-ellipsis text-util-colors-teal-teal-700'>{varName}</div>
|
||||
<div className={cn('max-w-[75px] truncate ml-0.5 system-xs-medium overflow-hidden text-ellipsis text-util-colors-teal-teal-700')}>{varName}</div>
|
||||
</div>
|
||||
{writeMode && <Badge className='shrink-0' text={t(`${i18nPrefix}.operations.${writeMode}`)} />}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user