Feat/loop break node (#17268)
This commit is contained in:
parent
627a9e2ce1
commit
713902dc47
64 changed files with 1397 additions and 139 deletions
|
|
@ -144,7 +144,7 @@ export default function AccountPage() {
|
|||
{userProfile.name}
|
||||
{isEducationAccount && (
|
||||
<PremiumBadge size='s' color='blue' className='ml-1 !px-2'>
|
||||
<RiGraduationCapFill className='w-3 h-3 mr-1' />
|
||||
<RiGraduationCapFill className='mr-1 h-3 w-3' />
|
||||
<span className='system-2xs-medium'>EDU</span>
|
||||
</PremiumBadge>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export default function AppSelector() {
|
|||
{userProfile.name}
|
||||
{isEducationAccount && (
|
||||
<PremiumBadge size='s' color='blue' className='ml-1 !px-2'>
|
||||
<RiGraduationCapFill className='w-3 h-3 mr-1' />
|
||||
<RiGraduationCapFill className='mr-1 h-3 w-3' />
|
||||
<span className='system-2xs-medium'>EDU</span>
|
||||
</PremiumBadge>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="ongoing">
|
||||
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M8 2.75C5.10051 2.75 2.75 5.10051 2.75 8C2.75 10.8995 5.1005 13.25 8 13.25C8.41421 13.25 8.75 13.5858 8.75 14C8.75 14.4142 8.41421 14.75 8 14.75C4.27208 14.75 1.25 11.7279 1.25 8C1.25 4.27208 4.27208 1.25 8 1.25C8.41421 1.25 8.75 1.58579 8.75 2C8.75 2.41421 8.41421 2.75 8 2.75ZM10.3508 2.42715C10.5582 2.06861 11.017 1.94608 11.3755 2.15349C11.9971 2.51301 12.5556 2.96859 13.0311 3.49984C13.3073 3.8085 13.281 4.28264 12.9724 4.55887C12.6637 4.8351 12.1896 4.80882 11.9133 4.50016C11.5429 4.08625 11.1079 3.73153 10.6245 3.4519C10.2659 3.2445 10.1434 2.7857 10.3508 2.42715ZM8.13634 5.46967C8.42923 5.17678 8.9041 5.17678 9.197 5.46967L11.197 7.46967C11.4899 7.76256 11.4899 8.23744 11.197 8.53033L9.197 10.5303C8.9041 10.8232 8.42923 10.8232 8.13634 10.5303C7.84344 10.2374 7.84344 9.76256 8.13634 9.46967L8.85601 8.75H5.33333C4.91912 8.75 4.58333 8.41421 4.58333 8C4.58333 7.58579 4.91912 7.25 5.33333 7.25H8.85601L8.13634 6.53033C7.84344 6.23744 7.84344 5.76256 8.13634 5.46967ZM13.7414 6.09691C14.1478 6.01676 14.5422 6.28123 14.6224 6.68762C14.7062 7.1128 14.75 7.55166 14.75 8C14.75 8.44834 14.7062 8.88721 14.6224 9.31234C14.5422 9.71872 14.1478 9.98318 13.7414 9.90302C13.335 9.82287 13.0706 9.42845 13.1507 9.02206C13.2158 8.69213 13.25 8.35046 13.25 8C13.25 7.64954 13.2158 7.30787 13.1507 6.97785C13.0706 6.57146 13.335 6.17705 13.7414 6.09691ZM12.9723 11.4411C13.281 11.7173 13.3073 12.1915 13.0311 12.5002C12.5556 13.0314 11.9971 13.487 11.3756 13.8465C11.017 14.0539 10.5582 13.9314 10.3508 13.5729C10.1434 13.2143 10.2659 12.7556 10.6244 12.5481C11.1079 12.2685 11.5429 11.9138 11.9133 11.4999C12.1895 11.1912 12.6637 11.1649 12.9723 11.4411Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -4,12 +4,16 @@
|
|||
import * as React from 'react'
|
||||
import data from './Triangle.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'Triangle'
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "16",
|
||||
"height": "16",
|
||||
"viewBox": "0 0 16 16",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "ongoing"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M8 2.75C5.10051 2.75 2.75 5.10051 2.75 8C2.75 10.8995 5.1005 13.25 8 13.25C8.41421 13.25 8.75 13.5858 8.75 14C8.75 14.4142 8.41421 14.75 8 14.75C4.27208 14.75 1.25 11.7279 1.25 8C1.25 4.27208 4.27208 1.25 8 1.25C8.41421 1.25 8.75 1.58579 8.75 2C8.75 2.41421 8.41421 2.75 8 2.75ZM10.3508 2.42715C10.5582 2.06861 11.017 1.94608 11.3755 2.15349C11.9971 2.51301 12.5556 2.96859 13.0311 3.49984C13.3073 3.8085 13.281 4.28264 12.9724 4.55887C12.6637 4.8351 12.1896 4.80882 11.9133 4.50016C11.5429 4.08625 11.1079 3.73153 10.6245 3.4519C10.2659 3.2445 10.1434 2.7857 10.3508 2.42715ZM8.13634 5.46967C8.42923 5.17678 8.9041 5.17678 9.197 5.46967L11.197 7.46967C11.4899 7.76256 11.4899 8.23744 11.197 8.53033L9.197 10.5303C8.9041 10.8232 8.42923 10.8232 8.13634 10.5303C7.84344 10.2374 7.84344 9.76256 8.13634 9.46967L8.85601 8.75H5.33333C4.91912 8.75 4.58333 8.41421 4.58333 8C4.58333 7.58579 4.91912 7.25 5.33333 7.25H8.85601L8.13634 6.53033C7.84344 6.23744 7.84344 5.76256 8.13634 5.46967ZM13.7414 6.09691C14.1478 6.01676 14.5422 6.28123 14.6224 6.68762C14.7062 7.1128 14.75 7.55166 14.75 8C14.75 8.44834 14.7062 8.88721 14.6224 9.31234C14.5422 9.71872 14.1478 9.98318 13.7414 9.90302C13.335 9.82287 13.0706 9.42845 13.1507 9.02206C13.2158 8.69213 13.25 8.35046 13.25 8C13.25 7.64954 13.2158 7.30787 13.1507 6.97785C13.0706 6.57146 13.335 6.17705 13.7414 6.09691ZM12.9723 11.4411C13.281 11.7173 13.3073 12.1915 13.0311 12.5002C12.5556 13.0314 11.9971 13.487 11.3756 13.8465C11.017 14.0539 10.5582 13.9314 10.3508 13.5729C10.1434 13.2143 10.2659 12.7556 10.6244 12.5481C11.1079 12.2685 11.5429 11.9138 11.9133 11.4999C12.1895 11.1912 12.6637 11.1649 12.9723 11.4411Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "LoopEnd"
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './LoopEnd.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'LoopEnd'
|
||||
|
||||
export default Icon
|
||||
|
|
@ -13,6 +13,7 @@ export { default as Jinja } from './Jinja'
|
|||
export { default as KnowledgeRetrieval } from './KnowledgeRetrieval'
|
||||
export { default as ListFilter } from './ListFilter'
|
||||
export { default as Llm } from './Llm'
|
||||
export { default as LoopEnd } from './LoopEnd'
|
||||
export { default as Loop } from './Loop'
|
||||
export { default as ParameterExtractor } from './ParameterExtractor'
|
||||
export { default as QuestionClassifier } from './QuestionClassifier'
|
||||
|
|
|
|||
157
web/app/components/base/select/pure.tsx
Normal file
157
web/app/components/base/select/pure.tsx
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiCheckLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import type {
|
||||
PortalToFollowElemOptions,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Option = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type PureSelectProps = {
|
||||
options: Option[]
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
containerProps?: PortalToFollowElemOptions & {
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}
|
||||
triggerProps?: {
|
||||
className?: string
|
||||
},
|
||||
popupProps?: {
|
||||
wrapperClassName?: string
|
||||
className?: string
|
||||
itemClassName?: string
|
||||
title?: string
|
||||
},
|
||||
}
|
||||
const PureSelect = ({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
containerProps,
|
||||
triggerProps,
|
||||
popupProps,
|
||||
}: PureSelectProps) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
open,
|
||||
onOpenChange,
|
||||
placement,
|
||||
offset,
|
||||
} = containerProps || {}
|
||||
const {
|
||||
className: triggerClassName,
|
||||
} = triggerProps || {}
|
||||
const {
|
||||
wrapperClassName: popupWrapperClassName,
|
||||
className: popupClassName,
|
||||
itemClassName: popupItemClassName,
|
||||
title: popupTitle,
|
||||
} = popupProps || {}
|
||||
|
||||
const [localOpen, setLocalOpen] = useState(false)
|
||||
const mergedOpen = open ?? localOpen
|
||||
|
||||
const handleOpenChange = useCallback((openValue: boolean) => {
|
||||
onOpenChange?.(openValue)
|
||||
setLocalOpen(openValue)
|
||||
}, [onOpenChange])
|
||||
|
||||
const selectedOption = options.find(option => option.value === value)
|
||||
const triggerText = selectedOption?.label || t('common.placeholder.select')
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
placement={placement || 'bottom-start'}
|
||||
offset={offset || 4}
|
||||
open={mergedOpen}
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => handleOpenChange(!mergedOpen)}
|
||||
asChild
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'system-sm-regular group flex h-8 cursor-pointer items-center rounded-lg bg-components-input-bg-normal px-2 text-components-input-text-filled hover:bg-state-base-hover-alt',
|
||||
mergedOpen && 'bg-state-base-hover-alt',
|
||||
triggerClassName,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className='grow'
|
||||
title={triggerText}
|
||||
>
|
||||
{triggerText}
|
||||
</div>
|
||||
<RiArrowDownSLine
|
||||
className={cn(
|
||||
'h-4 w-4 shrink-0 text-text-quaternary group-hover:text-text-secondary',
|
||||
mergedOpen && 'text-text-secondary',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className={cn(
|
||||
'z-10',
|
||||
popupWrapperClassName,
|
||||
)}>
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg',
|
||||
popupClassName,
|
||||
)}
|
||||
>
|
||||
{
|
||||
popupTitle && (
|
||||
<div className='system-xs-medium-uppercase flex h-[22px] items-center px-3 text-text-tertiary'>
|
||||
{popupTitle}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
className={cn(
|
||||
'system-sm-medium flex h-8 cursor-pointer items-center rounded-lg px-2 text-text-secondary hover:bg-state-base-hover',
|
||||
popupItemClassName,
|
||||
)}
|
||||
title={option.label}
|
||||
onClick={() => {
|
||||
onChange?.(option.value)
|
||||
handleOpenChange(false)
|
||||
}}
|
||||
>
|
||||
<div className='mr-1 grow truncate px-1'>
|
||||
{option.label}
|
||||
</div>
|
||||
{
|
||||
value === option.value && <RiCheckLine className='h-4 w-4 shrink-0 text-text-accent' />
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default PureSelect
|
||||
|
|
@ -94,7 +94,7 @@ export default function AppSelector() {
|
|||
{userProfile.name}
|
||||
{isEducationAccount && (
|
||||
<PremiumBadge size='s' color='blue' className='ml-1 !px-2'>
|
||||
<RiGraduationCapFill className='w-3 h-3 mr-1' />
|
||||
<RiGraduationCapFill className='mr-1 h-3 w-3' />
|
||||
<span className='system-2xs-medium'>EDU</span>
|
||||
</PremiumBadge>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ const PlanBadge: FC<PlanBadgeProps> = ({ plan, allowHover, sandboxAsUpgrade = fa
|
|||
if (plan === Plan.professional) {
|
||||
return <PremiumBadge className='select-none' size='s' color='blue' allowHover={allowHover} onClick={onClick}>
|
||||
<div className='system-2xs-medium-uppercase'>
|
||||
<span className='p-1 inline-flex items-center gap-1'>
|
||||
{isEducationWorkspace && <RiGraduationCapFill className='w-3 h-3' />}
|
||||
<span className='inline-flex items-center gap-1 p-1'>
|
||||
{isEducationWorkspace && <RiGraduationCapFill className='h-3 w-3' />}
|
||||
pro
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
ListFilter,
|
||||
Llm,
|
||||
Loop,
|
||||
LoopEnd,
|
||||
ParameterExtractor,
|
||||
QuestionClassifier,
|
||||
TemplatingTransform,
|
||||
|
|
@ -54,6 +55,7 @@ const getIcon = (type: BlockEnum, className: string) => {
|
|||
[BlockEnum.Iteration]: <Iteration className={className} />,
|
||||
[BlockEnum.LoopStart]: <VariableX className={className} />,
|
||||
[BlockEnum.Loop]: <Loop className={className} />,
|
||||
[BlockEnum.LoopEnd]: <LoopEnd className={className} />,
|
||||
[BlockEnum.ParameterExtractor]: <ParameterExtractor className={className} />,
|
||||
[BlockEnum.DocExtractor]: <DocsExtractor className={className} />,
|
||||
[BlockEnum.ListFilter]: <ListFilter className={className} />,
|
||||
|
|
@ -68,6 +70,7 @@ const ICON_CONTAINER_BG_COLOR_MAP: Record<string, string> = {
|
|||
[BlockEnum.IfElse]: 'bg-util-colors-cyan-cyan-500',
|
||||
[BlockEnum.Iteration]: 'bg-util-colors-cyan-cyan-500',
|
||||
[BlockEnum.Loop]: 'bg-util-colors-cyan-cyan-500',
|
||||
[BlockEnum.LoopEnd]: 'bg-util-colors-warning-warning-500',
|
||||
[BlockEnum.HttpRequest]: 'bg-util-colors-violet-violet-500',
|
||||
[BlockEnum.Answer]: 'bg-util-colors-warning-warning-500',
|
||||
[BlockEnum.KnowledgeRetrieval]: 'bg-util-colors-green-green-500',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { BLOCK_CLASSIFICATIONS } from './constants'
|
|||
import { useBlocks } from './hooks'
|
||||
import type { ToolDefaultValue } from './types'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
|
||||
type BlocksProps = {
|
||||
searchText: string
|
||||
|
|
@ -90,7 +91,15 @@ const Blocks = ({
|
|||
className='mr-2 shrink-0'
|
||||
type={block.type}
|
||||
/>
|
||||
<div className='text-sm text-text-secondary'>{block.title}</div>
|
||||
<div className='grow text-sm text-text-secondary'>{block.title}</div>
|
||||
{
|
||||
block.type === BlockEnum.LoopEnd && (
|
||||
<Badge
|
||||
text={t('workflow.nodes.loop.loopNode')}
|
||||
className='ml-2 shrink-0'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</Tooltip>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ export const BLOCKS: Block[] = [
|
|||
type: BlockEnum.IfElse,
|
||||
title: 'IF/ELSE',
|
||||
},
|
||||
{
|
||||
classification: BlockClassificationEnum.Logic,
|
||||
type: BlockEnum.LoopEnd,
|
||||
title: 'Exit Loop',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
classification: BlockClassificationEnum.Logic,
|
||||
type: BlockEnum.Iteration,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import ListFilterDefault from './nodes/list-operator/default'
|
|||
import IterationStartDefault from './nodes/iteration-start/default'
|
||||
import AgentDefault from './nodes/agent/default'
|
||||
import LoopStartDefault from './nodes/loop-start/default'
|
||||
import LoopEndDefault from './nodes/loop-end/default'
|
||||
|
||||
type NodesExtraData = {
|
||||
author: string
|
||||
|
|
@ -122,6 +123,15 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
|
|||
getAvailableNextNodes: LoopStartDefault.getAvailableNextNodes,
|
||||
checkValid: LoopStartDefault.checkValid,
|
||||
},
|
||||
[BlockEnum.LoopEnd]: {
|
||||
author: 'Dify',
|
||||
about: '',
|
||||
availablePrevNodes: [],
|
||||
availableNextNodes: [],
|
||||
getAvailablePrevNodes: LoopEndDefault.getAvailablePrevNodes,
|
||||
getAvailableNextNodes: LoopEndDefault.getAvailableNextNodes,
|
||||
checkValid: LoopEndDefault.checkValid,
|
||||
},
|
||||
[BlockEnum.Code]: {
|
||||
author: 'Dify',
|
||||
about: '',
|
||||
|
|
@ -297,6 +307,12 @@ export const NODES_INITIAL_DATA = {
|
|||
desc: '',
|
||||
...LoopStartDefault.defaultValue,
|
||||
},
|
||||
[BlockEnum.LoopEnd]: {
|
||||
type: BlockEnum.LoopEnd,
|
||||
title: '',
|
||||
desc: '',
|
||||
...LoopEndDefault.defaultValue,
|
||||
},
|
||||
[BlockEnum.Code]: {
|
||||
type: BlockEnum.Code,
|
||||
title: '',
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean
|
|||
const availableNextBlocks = useMemo(() => {
|
||||
if (!nodeType)
|
||||
return []
|
||||
|
||||
return nodesExtraData[nodeType].availableNextNodes || []
|
||||
}, [nodeType, nodesExtraData])
|
||||
|
||||
|
|
@ -54,6 +55,9 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean
|
|||
if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
|
||||
return false
|
||||
|
||||
if (!isInLoop && nType === BlockEnum.LoopEnd)
|
||||
return false
|
||||
|
||||
return true
|
||||
}),
|
||||
availableNextBlocks: availableNextBlocks.filter((nType) => {
|
||||
|
|
@ -63,6 +67,9 @@ export const useAvailableBlocks = (nodeType?: BlockEnum, isInIteration?: boolean
|
|||
if (isInLoop && (nType === BlockEnum.Iteration || nType === BlockEnum.Loop || nType === BlockEnum.End))
|
||||
return false
|
||||
|
||||
if (!isInLoop && nType === BlockEnum.LoopEnd)
|
||||
return false
|
||||
|
||||
return true
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import {
|
|||
import {
|
||||
genNewNodeTitleFromOld,
|
||||
generateNewNode,
|
||||
getNodeCustomTypeByNodeDataType,
|
||||
getNodesConnectedSourceOrTargetHandleIdsMap,
|
||||
getTopLeftNodePosition,
|
||||
} from '../utils'
|
||||
|
|
@ -638,7 +639,7 @@ export const useNodesInteractions = () => {
|
|||
}
|
||||
|
||||
if (node.id === currentNode.parentId)
|
||||
node.data._children = node.data._children?.filter(child => child !== nodeId)
|
||||
node.data._children = node.data._children?.filter(child => child.nodeId !== nodeId)
|
||||
})
|
||||
draft.splice(currentNodeIndex, 1)
|
||||
})
|
||||
|
|
@ -686,6 +687,7 @@ export const useNodesInteractions = () => {
|
|||
newIterationStartNode,
|
||||
newLoopStartNode,
|
||||
} = generateNewNode({
|
||||
type: getNodeCustomTypeByNodeDataType(nodeType),
|
||||
data: {
|
||||
...NODES_INITIAL_DATA[nodeType],
|
||||
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
|
||||
|
|
@ -775,10 +777,10 @@ export const useNodesInteractions = () => {
|
|||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id)
|
||||
node.data._children?.push(newNode.id)
|
||||
node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type })
|
||||
|
||||
if (node.data.type === BlockEnum.Loop && prevNode.parentId === node.id)
|
||||
node.data._children?.push(newNode.id)
|
||||
node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type })
|
||||
})
|
||||
draft.push(newNode)
|
||||
|
||||
|
|
@ -853,7 +855,7 @@ export const useNodesInteractions = () => {
|
|||
|
||||
let newEdge
|
||||
|
||||
if ((nodeType !== BlockEnum.IfElse) && (nodeType !== BlockEnum.QuestionClassifier)) {
|
||||
if ((nodeType !== BlockEnum.IfElse) && (nodeType !== BlockEnum.QuestionClassifier) && (nodeType !== BlockEnum.LoopEnd)) {
|
||||
newEdge = {
|
||||
id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
|
||||
type: CUSTOM_EDGE,
|
||||
|
|
@ -901,7 +903,7 @@ export const useNodesInteractions = () => {
|
|||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration && nextNode.parentId === node.id)
|
||||
node.data._children?.push(newNode.id)
|
||||
node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type })
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration && node.data.start_node_id === nextNodeId) {
|
||||
node.data.start_node_id = newNode.id
|
||||
|
|
@ -909,7 +911,7 @@ export const useNodesInteractions = () => {
|
|||
}
|
||||
|
||||
if (node.data.type === BlockEnum.Loop && nextNode.parentId === node.id)
|
||||
node.data._children?.push(newNode.id)
|
||||
node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type })
|
||||
|
||||
if (node.data.type === BlockEnum.Loop && node.data.start_node_id === nextNodeId) {
|
||||
node.data.start_node_id = newNode.id
|
||||
|
|
@ -1004,7 +1006,7 @@ export const useNodesInteractions = () => {
|
|||
const isNextNodeInIteration = !!nextNodeParentNode && nextNodeParentNode.data.type === BlockEnum.Iteration
|
||||
const isNextNodeInLoop = !!nextNodeParentNode && nextNodeParentNode.data.type === BlockEnum.Loop
|
||||
|
||||
if (nodeType !== BlockEnum.IfElse && nodeType !== BlockEnum.QuestionClassifier) {
|
||||
if (nodeType !== BlockEnum.IfElse && nodeType !== BlockEnum.QuestionClassifier && nodeType !== BlockEnum.LoopEnd) {
|
||||
newNextEdge = {
|
||||
id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`,
|
||||
type: CUSTOM_EDGE,
|
||||
|
|
@ -1049,9 +1051,9 @@ export const useNodesInteractions = () => {
|
|||
node.position.x += NODE_WIDTH_X_OFFSET
|
||||
|
||||
if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id)
|
||||
node.data._children?.push(newNode.id)
|
||||
node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type })
|
||||
if (node.data.type === BlockEnum.Loop && prevNode.parentId === node.id)
|
||||
node.data._children?.push(newNode.id)
|
||||
node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type })
|
||||
})
|
||||
draft.push(newNode)
|
||||
if (newIterationStartNode)
|
||||
|
|
@ -1117,6 +1119,7 @@ export const useNodesInteractions = () => {
|
|||
newIterationStartNode,
|
||||
newLoopStartNode,
|
||||
} = generateNewNode({
|
||||
type: getNodeCustomTypeByNodeDataType(nodeType),
|
||||
data: {
|
||||
...NODES_INITIAL_DATA[nodeType],
|
||||
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
|
||||
|
|
@ -1240,7 +1243,7 @@ export const useNodesInteractions = () => {
|
|||
if (nodeId) {
|
||||
// If nodeId is provided, copy that specific node
|
||||
const nodeToCopy = nodes.find(node => node.id === nodeId && node.data.type !== BlockEnum.Start
|
||||
&& node.type !== CUSTOM_ITERATION_START_NODE && node.type !== CUSTOM_LOOP_START_NODE)
|
||||
&& node.type !== CUSTOM_ITERATION_START_NODE && node.type !== CUSTOM_LOOP_START_NODE && node.data.type !== BlockEnum.LoopEnd)
|
||||
if (nodeToCopy)
|
||||
setClipboardElements([nodeToCopy])
|
||||
}
|
||||
|
|
@ -1254,7 +1257,7 @@ export const useNodesInteractions = () => {
|
|||
return
|
||||
}
|
||||
|
||||
const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start)
|
||||
const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.LoopEnd)
|
||||
|
||||
if (selectedNode)
|
||||
setClipboardElements([selectedNode])
|
||||
|
|
@ -1328,7 +1331,7 @@ export const useNodesInteractions = () => {
|
|||
newChildren = copyChildren
|
||||
idMapping = newIdMapping
|
||||
newChildren.forEach((child) => {
|
||||
newNode.data._children?.push(child.id)
|
||||
newNode.data._children?.push({ nodeId: child.id, nodeType: child.data.type })
|
||||
})
|
||||
newChildren.push(newIterationStartNode!)
|
||||
}
|
||||
|
|
@ -1339,7 +1342,7 @@ export const useNodesInteractions = () => {
|
|||
|
||||
newChildren = handleNodeLoopChildrenCopy(nodeToPaste.id, newNode.id)
|
||||
newChildren.forEach((child) => {
|
||||
newNode.data._children?.push(child.id)
|
||||
newNode.data._children?.push({ nodeId: child.id, nodeType: child.data.type })
|
||||
})
|
||||
newChildren.push(newLoopStartNode!)
|
||||
}
|
||||
|
|
@ -1424,7 +1427,7 @@ export const useNodesInteractions = () => {
|
|||
|
||||
const nodes = getNodes()
|
||||
const currentNode = nodes.find(n => n.id === nodeId)!
|
||||
const childrenNodes = nodes.filter(n => currentNode.data._children?.includes(n.id))
|
||||
const childrenNodes = nodes.filter(n => currentNode.data._children?.find((c: any) => c.nodeId === n.id))
|
||||
let rightNode: Node
|
||||
let bottomNode: Node
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ export const useWorkflowNodeIterationFinished = () => {
|
|||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
|
|
@ -38,6 +40,18 @@ export const useWorkflowNodeIterationFinished = () => {
|
|||
currentNode.data._runningStatus = data.status
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const incomeEdges = draft.filter((edge) => {
|
||||
return edge.target === data.node_id
|
||||
})
|
||||
incomeEdges.forEach((edge) => {
|
||||
edge.data = {
|
||||
...edge.data,
|
||||
_targetRunningStatus: data.status as any,
|
||||
}
|
||||
})
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [workflowStore, store])
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { useStoreApi } from 'reactflow'
|
|||
import produce from 'immer'
|
||||
import type { LoopFinishedResponse } from '@/types/workflow'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { DEFAULT_LOOP_TIMES } from '@/app/components/workflow/constants'
|
||||
|
||||
export const useWorkflowNodeLoopFinished = () => {
|
||||
const store = useStoreApi()
|
||||
|
|
@ -14,11 +13,12 @@ export const useWorkflowNodeLoopFinished = () => {
|
|||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
setLoopTimes,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
|
|
@ -31,13 +31,24 @@ export const useWorkflowNodeLoopFinished = () => {
|
|||
}
|
||||
}
|
||||
}))
|
||||
setLoopTimes(DEFAULT_LOOP_TIMES)
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
|
||||
currentNode.data._runningStatus = data.status
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const incomeEdges = draft.filter((edge) => {
|
||||
return edge.target === data.node_id
|
||||
})
|
||||
incomeEdges.forEach((edge) => {
|
||||
edge.data = {
|
||||
...edge.data,
|
||||
_targetRunningStatus: data.status as any,
|
||||
}
|
||||
})
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [workflowStore, store])
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -2,18 +2,12 @@ import { useCallback } from 'react'
|
|||
import { useStoreApi } from 'reactflow'
|
||||
import produce from 'immer'
|
||||
import type { LoopNextResponse } from '@/types/workflow'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
|
||||
export const useWorkflowNodeLoopNext = () => {
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const handleWorkflowNodeLoopNext = useCallback((params: LoopNextResponse) => {
|
||||
const {
|
||||
loopTimes,
|
||||
setLoopTimes,
|
||||
} = workflowStore.getState()
|
||||
|
||||
const { data } = params
|
||||
const {
|
||||
getNodes,
|
||||
|
|
@ -23,11 +17,17 @@ export const useWorkflowNodeLoopNext = () => {
|
|||
const nodes = getNodes()
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
currentNode.data._loopIndex = loopTimes
|
||||
setLoopTimes(loopTimes + 1)
|
||||
currentNode.data._loopIndex = data.index
|
||||
|
||||
draft.forEach((node) => {
|
||||
if (node.parentId === data.node_id) {
|
||||
node.data._waitingRun = true
|
||||
node.data._runningStatus = NodeRunningStatus.Waiting
|
||||
}
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
}, [workflowStore, store])
|
||||
}, [store])
|
||||
|
||||
return {
|
||||
handleWorkflowNodeLoopNext,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import produce from 'immer'
|
|||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import type { LoopStartedResponse } from '@/types/workflow'
|
||||
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
import { DEFAULT_LOOP_TIMES } from '@/app/components/workflow/constants'
|
||||
|
||||
export const useWorkflowNodeLoopStarted = () => {
|
||||
const store = useStoreApi()
|
||||
|
|
@ -25,7 +24,6 @@ export const useWorkflowNodeLoopStarted = () => {
|
|||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
setLoopTimes,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
|
|
@ -41,7 +39,6 @@ export const useWorkflowNodeLoopStarted = () => {
|
|||
status: NodeRunningStatus.Running,
|
||||
})
|
||||
}))
|
||||
setLoopTimes(DEFAULT_LOOP_TIMES)
|
||||
|
||||
const {
|
||||
setViewport,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue