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>))
|
||||
|
Reference in New Issue
Block a user