Skip to content

Commit cbe43bb

Browse files
authored
Merge pull request #44 from Dialogue-Bot/DIAL-32-ui-chat-bot
feat: add remove un-use flows
2 parents 66076f1 + f2a74b1 commit cbe43bb

File tree

7 files changed

+11763
-7472
lines changed

7 files changed

+11763
-7472
lines changed

client/src/components/pages/flow-detail/constant.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ export const useMapActionToLabel = () => {
6565

6666
export const SOURCE_HANDLE_PROMPT_YES = 'prompt-and-collect-yes'
6767
export const SOURCE_HANDLE_PROMPT_NO = 'prompt-and-collect-no'
68+
export const SOURCE_HANDLE_VARIABLES_YES = 'check-variables-yes'
69+
export const SOURCE_HANDLE_VARIABLES_NO = 'check-variables-no'
6870

6971
export const MAP_ACTION: Record<
7072
EActionTypes,

client/src/components/pages/flow-detail/edge.tsx

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Button } from '@/components/ui'
2+
import _ from 'lodash'
23
import { X } from 'lucide-react'
34
import { BaseEdge, EdgeProps, getSmoothStepPath, useReactFlow } from 'reactflow'
5+
import { useUnmount } from 'usehooks-ts'
46

57
const foreignObjectSize = 16
68

@@ -18,10 +20,12 @@ export const Edge = ({
1820
data = {
1921
deletable: true,
2022
},
23+
source,
24+
target,
2125
}: EdgeProps<{
2226
deletable?: boolean
2327
}>) => {
24-
const { setEdges } = useReactFlow()
28+
const { setEdges, getNode, setNodes } = useReactFlow()
2529
const [edgePath, labelX, labelY] = getSmoothStepPath({
2630
sourceX,
2731
sourceY,
@@ -31,12 +35,53 @@ export const Edge = ({
3135
targetPosition,
3236
})
3337

38+
/**
39+
* Handles the delete action for an edge.
40+
* If the edge is not deletable or the source node does not exist, the function returns early.
41+
* If the source node exists, it creates a deep clone of the node and modifies its data accordingly.
42+
* Finally, it updates the nodes and edges state to reflect the changes.
43+
*/
3444
const handleDelete = () => {
3545
if (!data?.deletable) return
3646

47+
const sourceNode = getNode(source)
48+
49+
if (sourceNode) {
50+
const cloned = _.cloneDeep(sourceNode)
51+
52+
if (cloned.data?.nextAction) {
53+
delete cloned.data.nextAction
54+
}
55+
56+
if (cloned.data?.nextActions) {
57+
cloned.data.nextActions = cloned.data.nextActions.filter(
58+
(nextAction: any) => nextAction.id !== target,
59+
)
60+
61+
if (cloned.data.nextActions.length === 0) {
62+
delete cloned.data.nextActions
63+
}
64+
}
65+
66+
setNodes((nodes) =>
67+
nodes.map((node) => {
68+
if (node.id === source) {
69+
return cloned
70+
}
71+
72+
return node
73+
}),
74+
)
75+
}
76+
3777
setEdges((edges) => edges.filter((edge) => edge.id !== id))
3878
}
3979

80+
useUnmount(() => {
81+
console.log('edge unmount')
82+
handleDelete()
83+
})
84+
4085
return (
4186
<>
4287
<BaseEdge path={edgePath} markerEnd={markerEnd} style={style} />

client/src/components/pages/flow-detail/flow-provider.tsx

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
useState,
1212
} from 'react'
1313
import {
14+
Connection,
1415
Edge,
1516
EdgeChange,
1617
EdgeMouseHandler,
@@ -24,7 +25,13 @@ import {
2425
useNodesState,
2526
} from 'reactflow'
2627
import { useToggle } from 'usehooks-ts'
27-
import { SOURCE_HANDLE_PROMPT_YES, useMapActionToLabel } from './constant'
28+
import {
29+
SOURCE_HANDLE_PROMPT_NO,
30+
SOURCE_HANDLE_PROMPT_YES,
31+
SOURCE_HANDLE_VARIABLES_NO,
32+
SOURCE_HANDLE_VARIABLES_YES,
33+
useMapActionToLabel,
34+
} from './constant'
2835

2936
type FlowCtx = {
3037
flow: TFlowInput
@@ -46,6 +53,8 @@ type FlowCtx = {
4653
handleDoubleClickEdge: EdgeMouseHandler
4754
getNode: (id: string) => Node<any> | undefined
4855
getEdge: (id: string) => Edge<any> | undefined
56+
handleValidateConnection: (connection: Connection) => boolean
57+
getCompleteFlows: () => any[]
4958
}
5059

5160
const FlowContext = createContext<FlowCtx | undefined>(undefined)
@@ -343,6 +352,120 @@ export const FlowProvider = ({ children, flow }: Props) => {
343352
[_getNode],
344353
)
345354

355+
/**
356+
* Checks the condition node to determine if it is valid.
357+
*
358+
* @param connection - The connection object.
359+
* @param sourceHandleNo - The source handle for the "No" path.
360+
* @param sourceHandleYes - The source handle for the "Yes" path.
361+
* @returns True if the condition node is valid, false otherwise.
362+
*/
363+
const handleCheckConditionNode = useCallback(
364+
({
365+
connection,
366+
sourceHandleNo,
367+
sourceHandleYes,
368+
}: {
369+
connection: Connection
370+
sourceHandleYes: string
371+
sourceHandleNo: string
372+
}) => {
373+
const numberOfYes = edges.filter((edge) => {
374+
return (
375+
edge.source === connection.source &&
376+
edge.sourceHandle === sourceHandleYes
377+
)
378+
})
379+
380+
const numberOfNo = edges.filter((edge) => {
381+
return (
382+
edge.source === connection.source &&
383+
edge.sourceHandle === sourceHandleNo
384+
)
385+
})
386+
387+
if (
388+
numberOfYes.length === 1 &&
389+
connection.sourceHandle === sourceHandleYes
390+
) {
391+
return false
392+
}
393+
394+
if (
395+
numberOfNo.length === 1 &&
396+
connection.sourceHandle === sourceHandleNo
397+
) {
398+
return false
399+
}
400+
401+
const targetEdge = edges.find(
402+
(edge) =>
403+
edge.target === connection.target &&
404+
edge.source === connection.source &&
405+
edge.sourceHandle !== null,
406+
)
407+
408+
if (targetEdge) {
409+
return false
410+
}
411+
412+
return true
413+
},
414+
[edges],
415+
)
416+
417+
/**
418+
* Handles the validation of a connection.
419+
*
420+
* @param connection - The connection to validate.
421+
* @returns A boolean indicating whether the connection is valid or not.
422+
*/
423+
const handleValidateConnection = useCallback(
424+
(connection: Connection) => {
425+
const sourcesFromHandleInState = edges.filter(
426+
(edge) => edge.source === connection.source,
427+
).length
428+
const sourceNode = nodes.find((node) => node.id === connection.source)
429+
430+
const targetFromHandleInState = edges.filter(
431+
(edge) => edge.target === connection.target,
432+
).length
433+
434+
if (connection.source === connection.target) return false
435+
436+
if (
437+
sourceNode?.data.action === EActionTypes.START &&
438+
sourcesFromHandleInState < 2
439+
)
440+
return true
441+
442+
if (sourceNode?.data.action === EActionTypes.PROMPT_AND_COLLECT) {
443+
return handleCheckConditionNode({
444+
connection,
445+
sourceHandleNo: SOURCE_HANDLE_PROMPT_NO,
446+
sourceHandleYes: SOURCE_HANDLE_PROMPT_YES,
447+
})
448+
}
449+
450+
if (
451+
sourceNode?.data.action === EActionTypes.CHECK_VARIABLES &&
452+
sourcesFromHandleInState < 2
453+
) {
454+
return handleCheckConditionNode({
455+
connection,
456+
sourceHandleNo: SOURCE_HANDLE_VARIABLES_NO,
457+
sourceHandleYes: SOURCE_HANDLE_VARIABLES_YES,
458+
})
459+
}
460+
461+
if (targetFromHandleInState === 1) return false
462+
if (sourcesFromHandleInState < 1) return true
463+
464+
return false
465+
},
466+
[edges, nodes, handleCheckConditionNode],
467+
)
468+
346469
/**
347470
* Handles the change of the selected node.
348471
*
@@ -389,6 +512,58 @@ export const FlowProvider = ({ children, flow }: Props) => {
389512
})
390513
}, [])
391514

515+
/**
516+
* Retrieves the complete flows from the given nodes.
517+
* A complete flow is a sequence of nodes where each node has a next action or is a final node.
518+
* Nodes that do not have a next action and are not final nodes are removed from the result.
519+
*
520+
* @returns An array of data objects representing the complete flows.
521+
*/
522+
const getCompleteFlows = useCallback(() => {
523+
let clonedNodes = _.cloneDeep(nodes)
524+
525+
const nodeIsNextAction = (node: Node<any>) => {
526+
return clonedNodes.some((nd) => {
527+
return (
528+
nd.data?.nextAction === node.id ||
529+
nd.data?.nextActions?.some((na: any) => na.id === node.id)
530+
)
531+
})
532+
}
533+
534+
const nodeIsToBeRemoved = (node: Node<any>) => {
535+
if (
536+
node.data.action === EActionTypes.START ||
537+
node.data.action === EActionTypes.FALLBACK
538+
) {
539+
return false
540+
}
541+
542+
if (
543+
!nodeIsNextAction(node) &&
544+
(!node.data?.nextAction || !node.data?.nextActions)
545+
) {
546+
console.log(node.id)
547+
return true
548+
}
549+
550+
return false
551+
}
552+
553+
// eslint-disable-next-line no-constant-condition
554+
while (true) {
555+
const nodeToBeRemoved = clonedNodes.find(nodeIsToBeRemoved)
556+
557+
if (!nodeToBeRemoved) {
558+
break
559+
}
560+
561+
clonedNodes = clonedNodes.filter((node) => node.id !== nodeToBeRemoved.id)
562+
}
563+
564+
return clonedNodes.map((node) => node.data)
565+
}, [nodes])
566+
392567
useDidUpdate(() => {
393568
setNodes((nds) => {
394569
return nds.map((node) =>
@@ -419,6 +594,8 @@ export const FlowProvider = ({ children, flow }: Props) => {
419594
handleDoubleClickEdge,
420595
getNode: _getNode,
421596
getEdge,
597+
handleValidateConnection,
598+
getCompleteFlows,
422599
}}
423600
>
424601
{children}

0 commit comments

Comments
 (0)