{conditionTypeIcons[condition.type]}
{condition.name}
@@ -59,7 +59,7 @@ const Condition = ({ data, condition }: NodeComponentConditionType) => {
borderStyle: "solid",
width: "0.7rem",
height: "0.7rem",
- right: "-0.95rem",
+ right: "-0.7rem",
zIndex: 10,
}}
/>
diff --git a/frontend/src/components/nodes/responses/Response.tsx b/frontend/src/components/nodes/responses/Response.tsx
index 2e4ea468..413bbcc8 100644
--- a/frontend/src/components/nodes/responses/Response.tsx
+++ b/frontend/src/components/nodes/responses/Response.tsx
@@ -5,7 +5,7 @@ const Response = ({ data }: NodeComponentType) => {
return (
-
{data.response?.data[0]?.text ?? "No text response"}
+
{data.response.data[0]?.text ?? "No text response"}
)
}
diff --git a/frontend/src/components/notifications/NotificationsWindow.tsx b/frontend/src/components/notifications/NotificationsWindow.tsx
index a947c854..31f1abb8 100644
--- a/frontend/src/components/notifications/NotificationsWindow.tsx
+++ b/frontend/src/components/notifications/NotificationsWindow.tsx
@@ -8,7 +8,7 @@ import {
} from "@nextui-org/react"
import { AlertOctagon, AlertTriangle, BugIcon, CheckCircle2, InfoIcon, Trash } from "lucide-react"
import { useContext, useState } from "react"
-import { notificationsContext } from "../../contexts/notificationsContext"
+import { NotificationsContext } from "../../contexts/notificationsContext"
import NotificationComponent from "./components/NotificationComponent"
type NotificationsWindowProps = {
@@ -30,7 +30,7 @@ type NotificationsWindowProps = {
// }
export const NotificationsWindow = ({ isOpen, setIsOpen }: NotificationsWindowProps) => {
- const { notifications, notification } = useContext(notificationsContext)
+ const { notifications, notification } = useContext(NotificationsContext)
const [notificationFilter, setNotificationFilter] = useState
([])
const handleSelectionChange = (e: React.ChangeEvent) => {
@@ -41,7 +41,6 @@ export const NotificationsWindow = ({ isOpen, setIsOpen }: NotificationsWindowPr
}
};
- console.log(notificationFilter)
const renderNotifications = () => {
const filtered_notifications = notifications.filter(
@@ -54,13 +53,11 @@ export const NotificationsWindow = ({ isOpen, setIsOpen }: NotificationsWindowPr
const stack = not.stack + 1
const next_not = time_sorted_notifications[idx + 1]
if (next_not && not && next_not.message === not.message && not.stack !== 0) {
- console.log(not, next_not)
next_not.stack = stack
not.stack = 0
}
return not
})
- console.log(stack_checked_notifications)
const stack_filtered_notifications = stack_checked_notifications.filter((not) => not.stack > 0)
if (stack_filtered_notifications.length === 0) {
return (
diff --git a/frontend/src/components/notifications/components/NotificationComponent.tsx b/frontend/src/components/notifications/components/NotificationComponent.tsx
index 53170a30..7377160b 100644
--- a/frontend/src/components/notifications/components/NotificationComponent.tsx
+++ b/frontend/src/components/notifications/components/NotificationComponent.tsx
@@ -2,7 +2,7 @@ import { Button } from "@nextui-org/react"
import classNames from "classnames"
import { AlertOctagon, AlertTriangle, Bug, CheckCircle2, Info, X } from "lucide-react"
import { useContext, useMemo, useState } from "react"
-import { notificationType, notificationsContext } from "../../../contexts/notificationsContext"
+import { NotificationsContext, notificationType } from "../../../contexts/notificationsContext"
type NotificationComponentType = {
notification: notificationType & {
@@ -11,7 +11,7 @@ type NotificationComponentType = {
}
const NotificationComponent = ({ notification }: NotificationComponentType) => {
- const { notification: nt, notifications } = useContext(notificationsContext)
+ const { notification: nt, notifications } = useContext(NotificationsContext)
const [isDelete, setIsDelete] = useState(false)
const notificationTypeColor = (type: string) => {
@@ -83,9 +83,7 @@ const NotificationComponent = ({ notification }: NotificationComponentType) => {
if (notification.stack > 1) {
const index = notifications.findIndex((n) => n.timestamp == notification.timestamp)
for (let i = 0; i <= index; i++) {
- console.log(notifications[i])
if (notifications[i].stack === 0 && notifications[i].message === notification.message) {
- console.log("deletion")
nt.delete(notifications[i].timestamp)
}
}
diff --git a/frontend/src/components/sidebar/DragListItem.tsx b/frontend/src/components/sidebar/DragListItem.tsx
index 864214ef..d49d2717 100644
--- a/frontend/src/components/sidebar/DragListItem.tsx
+++ b/frontend/src/components/sidebar/DragListItem.tsx
@@ -4,7 +4,7 @@ import React from "react";
const DragListItem = ({ item }: { item: { name: string; color: string; type: string } }) => {
const onDragStart = (event: React.DragEvent, nodeType: string) => {
// if (event.dataTransfer) {
- event.dataTransfer.setData("application/reactflow", nodeType)
+ event.dataTransfer.setData("application/@xyflow/react", nodeType)
event.dataTransfer.effectAllowed = "move"
// }
}
diff --git a/frontend/src/consts.tsx b/frontend/src/consts.tsx
index 0be0e0f0..6b9f5048 100644
--- a/frontend/src/consts.tsx
+++ b/frontend/src/consts.tsx
@@ -73,7 +73,7 @@ export const NODE_NAMES = [
"Science discussion",
"Experience story",
"Political discussion",
- "Now plans",
+ "Now plans",
"Sport talk",
"Movie discussion",
"Family story",
@@ -108,7 +108,7 @@ export const NODE_NAMES = [
"Cooking discussion",
"Technology story",
"Music talk",
- "Next year plans"
+ "Next year plans",
]
export const START_FALLBACK_NODE_FLAGS = ["start", "fallback"]
@@ -119,7 +119,7 @@ export const NODES = {
default_node: {
name: "Default Node",
type: "default_node",
- dragHandle: '.custom-drag-handle',
+ dragHandle: ".custom-drag-handle",
conditions: [],
global_conditions: [],
local_conditions: [],
@@ -148,15 +148,11 @@ export const NODES = {
link_node: {
name: "Link",
type: "link_node",
- dragHandle: '',
- conditions: [],
- global_conditions: [],
- local_conditions: [],
- response: {
- name: "Link response",
- type: "text",
- data: [{ text: "Link response", priority: 1 }],
- }
+ dragHandle: "",
+ transition: {
+ target_flow: "",
+ target_node: "",
+ },
},
}
@@ -194,6 +190,5 @@ export const conditionTypeIcons = {
export const responseTypeIcons = {
python: ,
text: ,
- llm:
+ llm: ,
}
-
diff --git a/frontend/src/contexts/buildContext.tsx b/frontend/src/contexts/buildContext.tsx
index a83433a8..07126512 100644
--- a/frontend/src/contexts/buildContext.tsx
+++ b/frontend/src/contexts/buildContext.tsx
@@ -11,7 +11,7 @@ import {
get_builds,
localBuildType,
} from "../api/bot"
-import { notificationsContext } from "./notificationsContext"
+import { NotificationsContext } from "./notificationsContext"
type BuildContextType = {
build: boolean
@@ -53,7 +53,7 @@ export const BuildProvider = ({ children }: { children: React.ReactNode }) => {
const [searchParams, setSearchParams] = useSearchParams()
const [logsPage, setLogsPage] = useState(searchParams.get("logs_page") === "opened")
const [builds, setBuilds] = useState([])
- const { notification: n } = useContext(notificationsContext)
+ const { notification: n } = useContext(NotificationsContext)
const setBuildsHandler = (builds: buildMinifyApiType[]) => {
setBuilds(() =>
diff --git a/frontend/src/contexts/flowContext.tsx b/frontend/src/contexts/flowContext.tsx
index 9ae0aa78..cb848f7a 100644
--- a/frontend/src/contexts/flowContext.tsx
+++ b/frontend/src/contexts/flowContext.tsx
@@ -1,14 +1,14 @@
/* eslint-disable react-refresh/only-export-components */
+import { Edge, OnBeforeDelete, ReactFlowInstance } from "@xyflow/react"
import React, { createContext, useCallback, useContext, useEffect, useState } from "react"
import { useParams } from "react-router-dom"
-import { Edge, ReactFlowInstance } from "reactflow"
import { v4 } from "uuid"
import { get_flows, save_flows } from "../api/flows"
import { FLOW_COLORS } from "../consts"
import { FlowType } from "../types/FlowTypes"
-import { NodeType } from "../types/NodeTypes"
+import { AppNode } from "../types/NodeTypes"
import { MetaContext } from "./metaContext"
-import { notificationsContext } from "./notificationsContext"
+import { NotificationsContext } from "./notificationsContext"
// import { v4 } from "uuid"
const globalFlow: FlowType = {
@@ -22,12 +22,14 @@ const globalFlow: FlowType = {
id: v4(),
type: "default_node",
data: {
+ id: "GLOBAL_NODE",
flags: [],
conditions: [],
global_conditions: [],
local_conditions: [],
name: "Global node",
response: {
+ id: "GLOBAL_NODE_RESPONSE",
name: "global_response",
type: "text",
data: [{ text: "Global node response", priority: 1 }],
@@ -48,9 +50,11 @@ const globalFlow: FlowType = {
},
}
+export type CustomReactFlowInstanceType = ReactFlowInstance
+
type TabContextType = {
- reactFlowInstance: ReactFlowInstance | null
- setReactFlowInstance: React.Dispatch>
+ reactFlowInstance: CustomReactFlowInstanceType | null
+ setReactFlowInstance: React.Dispatch>
tab: string
setTab: React.Dispatch>
flows: FlowType[]
@@ -64,6 +68,8 @@ type TabContextType = {
deleteNode: (id: string) => void
deleteEdge: (id: string) => void
deleteObject: (id: string) => void
+ validateDeletion: OnBeforeDelete
+ validateNodeDeletion: (node: AppNode) => boolean
}
const initialValue: TabContextType = {
@@ -84,20 +90,28 @@ const initialValue: TabContextType = {
deleteNode: () => {},
deleteEdge: () => {},
deleteObject: () => {},
+ validateDeletion: () =>
+ new Promise(() => {
+ return false
+ }),
+ validateNodeDeletion: () => false,
}
export const flowContext = createContext(initialValue)
export const FlowProvider = ({ children }: { children: React.ReactNode }) => {
-
- const [reactFlowInstance, setReactFlowInstance] = useState(null)
+ const [reactFlowInstance, setReactFlowInstance] = useState | null>(null)
const [tab, setTab] = useState(initialValue.tab)
const { flowId } = useParams()
const [flows, setFlows] = useState([])
- const { notification: n } = useContext(notificationsContext)
+ const { notification: n } = useContext(NotificationsContext)
const { screenLoading } = useContext(MetaContext)
useEffect(() => {
+ setReactFlowInstance(null)
setTab(flowId || "")
}, [flowId])
@@ -123,10 +137,11 @@ export const FlowProvider = ({ children }: { children: React.ReactNode }) => {
useEffect(() => {
getFlows()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const saveFlows = async (flows: FlowType[]) => {
- const res = await save_flows(flows)
+ await save_flows(flows)
setFlows(flows)
}
@@ -160,23 +175,55 @@ export const FlowProvider = ({ children }: { children: React.ReactNode }) => {
[flows]
)
+ const validateDeletion = ({ nodes, edges }: { nodes: AppNode[]; edges: Edge[] }) => {
+ const is_nodes_valid = nodes.every((node) => {
+ if (node.type === "default_node" && node?.data.flags?.includes("start")) {
+ n.add({ title: "Warning!", message: "Can't delete start node", type: "warning" })
+ return false
+ }
+ if (node?.id?.includes("LOCAL")) {
+ n.add({ title: "Warning!", message: "Can't delete local node", type: "warning" })
+ return false
+ }
+ if (node?.id?.includes("GLOBAL")) {
+ n.add({ title: "Warning!", message: "Can't delete global node", type: "warning" })
+ return false
+ }
+ return true
+ })
+ return new Promise((resolve) => {
+ resolve(is_nodes_valid)
+ })
+ }
+
+ const validateNodeDeletion = (node: AppNode) => {
+ if (node.type === "default_node" && node.data.flags.includes("start")) {
+ n.add({ title: "Warning!", message: "Can't delete start node", type: "warning" })
+ return false
+ }
+ if (node.id.includes("LOCAL")) {
+ n.add({ title: "Warning!", message: "Can't delete local node", type: "warning" })
+ return false
+ }
+ if (node.id.includes("GLOBAL")) {
+ n.add({ title: "Warning!", message: "Can't delete global node", type: "warning" })
+ return false
+ }
+ return true
+ }
+
const deleteNode = useCallback(
(id: string) => {
const flow = flows.find((flow) => flow.data.nodes.some((node) => node.id === id))
if (!flow) return -1
- const deleted_node: NodeType = flow.data.nodes.find((node) => node.id === id) as NodeType
- if (deleted_node?.data.flags?.includes("start"))
+ const deleted_node: AppNode = flow.data.nodes.find((node) => node.id === id) as AppNode
+ if (deleted_node.type === "default_node" && deleted_node?.data.flags?.includes("start"))
return n.add({ title: "Warning!", message: "Can't delete start node", type: "warning" })
if (deleted_node?.id?.includes("LOCAL"))
return n.add({ title: "Warning!", message: "Can't delete local node", type: "warning" })
if (deleted_node?.id?.includes("GLOBAL"))
return n.add({ title: "Warning!", message: "Can't delete global node", type: "warning" })
- if (deleted_node?.data.flags?.includes("fallback")) {
- console.log(
- flow.data.nodes
- .find((node) => node.id !== id && !node.data.id.includes("LOCAL"))
- ?.data.flags.push("fallback")
- )
+ if (deleted_node.type === "default_node" && deleted_node?.data.flags?.includes("fallback")) {
// any_node.data.flags?.push("fallback")
}
const newNodes = flow.data.nodes.filter((node) => node.id !== id)
@@ -240,6 +287,8 @@ export const FlowProvider = ({ children }: { children: React.ReactNode }) => {
deleteNode,
deleteEdge,
deleteObject,
+ validateDeletion,
+ validateNodeDeletion,
}}>
{children}
diff --git a/frontend/src/contexts/metaContext.tsx b/frontend/src/contexts/metaContext.tsx
index 07533a2d..6c9fe84d 100644
--- a/frontend/src/contexts/metaContext.tsx
+++ b/frontend/src/contexts/metaContext.tsx
@@ -55,6 +55,7 @@ const MetaProvider = ({ children }: MetaProviderProps) => {
useEffect(() => {
getVersion()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
diff --git a/frontend/src/contexts/notificationsContext.tsx b/frontend/src/contexts/notificationsContext.tsx
index f02b9964..d2e3f259 100644
--- a/frontend/src/contexts/notificationsContext.tsx
+++ b/frontend/src/contexts/notificationsContext.tsx
@@ -34,7 +34,7 @@ type notificationsContextType = {
}
}
-export const notificationsContext = createContext({
+export const NotificationsContext = createContext({
notifications: [],
notification: {
add: () => {},
@@ -153,13 +153,13 @@ const NotificationsProvider = ({ children }: { children: React.ReactNode }) => {
}
return (
-
{children}
-
+
)
}
diff --git a/frontend/src/contexts/runContext.tsx b/frontend/src/contexts/runContext.tsx
index fb49a0c0..96ccea4a 100644
--- a/frontend/src/contexts/runContext.tsx
+++ b/frontend/src/contexts/runContext.tsx
@@ -11,7 +11,7 @@ import {
run_stop,
} from "../api/bot"
import { buildContext } from "./buildContext"
-import { notificationsContext } from "./notificationsContext"
+import { NotificationsContext } from "./notificationsContext"
export type runApiType = {
id: number
@@ -58,7 +58,7 @@ export const RunProvider = ({ children }: { children: React.ReactNode }) => {
const [runStatus, setRunStatus] = useState("stopped")
const [runs, setRuns] = useState([])
const { setBuildsHandler, builds: context_builds } = useContext(buildContext)
- const { notification: n } = useContext(notificationsContext)
+ const { notification: n } = useContext(NotificationsContext)
const setRunsHandler = (runs: runMinifyApiType[]) => {
setRuns(runs.map((run) => ({ ...run, type: "run" })))
diff --git a/frontend/src/contexts/undoRedoContext.tsx b/frontend/src/contexts/undoRedoContext.tsx
index a8d40e5f..f6c78c21 100644
--- a/frontend/src/contexts/undoRedoContext.tsx
+++ b/frontend/src/contexts/undoRedoContext.tsx
@@ -1,10 +1,13 @@
+import { addEdge, Edge, Node, OnSelectionChangeParams, useReactFlow } from "@xyflow/react"
import { cloneDeep } from "lodash"
import { createContext, useCallback, useContext, useEffect, useState } from "react"
-import { addEdge, Edge, Node, OnSelectionChangeParams, useReactFlow } from "reactflow"
import { v4 } from "uuid"
-import { NodeDataType } from "../types/NodeTypes"
+import { AppNode } from "../types/NodeTypes"
+import { OnSelectionChangeParamsCustom } from "../types/ReactFlowTypes"
+import { generateNewNode } from "../utils"
import { flowContext } from "./flowContext"
-import { notificationsContext } from "./notificationsContext"
+import { NotificationsContext } from "./notificationsContext"
+import { workspaceContext } from "./workspaceContext"
type undoRedoContextType = {
undo: () => void
@@ -16,6 +19,8 @@ type undoRedoContextType = {
position: { x: number; y: number; paneX?: number; paneY?: number }
) => void
copiedSelection: OnSelectionChangeParams | null
+ disableCopyPaste: boolean
+ setDisableCopyPaste: React.Dispatch>
}
type UseUndoRedoOptions = {
@@ -43,6 +48,8 @@ const initialValue = {
copy: () => {},
paste: () => {},
copiedSelection: null,
+ disableCopyPaste: false,
+ setDisableCopyPaste: () => {},
}
const defaultOptions: UseUndoRedoOptions = {
@@ -55,7 +62,8 @@ export const undoRedoContext = createContext(initialValue)
export function UndoRedoProvider({ children }: { children: React.ReactNode }) {
const { tab, flows } = useContext(flowContext)
- const { notification: n } = useContext(notificationsContext)
+ const { notification: n } = useContext(NotificationsContext)
+ const { modalsOpened } = useContext(workspaceContext)
const [past, setPast] = useState(flows.map(() => []))
const [future, setFuture] = useState(flows.map(() => []))
@@ -165,6 +173,15 @@ export function UndoRedoProvider({ children }: { children: React.ReactNode }) {
const { reactFlowInstance } = useContext(flowContext)
const [copiedSelection, setCopiedSelection] = useState(null)
+ const [disableCopyPaste, setDisableCopyPaste] = useState(false)
+
+ useEffect(() => {
+ if (modalsOpened === 0) {
+ setDisableCopyPaste(false)
+ } else if (modalsOpened > 0) {
+ setDisableCopyPaste(true)
+ }
+ }, [modalsOpened])
const copy = (selection: OnSelectionChangeParams) => {
if (selection && (selection.nodes.length || selection.edges.length)) {
@@ -204,8 +221,9 @@ export function UndoRedoProvider({ children }: { children: React.ReactNode }) {
type: "warning",
})
}
- const nodes: Node[] = reactFlowInstance.getNodes()
- let edges: Edge[] = reactFlowInstance.getEdges()
+ const _selectionInstance = selectionInstance as OnSelectionChangeParamsCustom
+ const nodes = reactFlowInstance.getNodes()
+ let edges = reactFlowInstance.getEdges()
let minimumX = Infinity
let minimumY = Infinity
const idsMap: { [id: string]: string } = {}
@@ -223,30 +241,29 @@ export function UndoRedoProvider({ children }: { children: React.ReactNode }) {
const insidePosition =
position.paneX && position.paneY
? { x: position.paneX + position.x, y: position.paneY + position.y }
- : reactFlowInstance.project({ x: position.x, y: position.y })
-
- const resultNodes: Node[] = []
-
- selectionInstance.nodes.forEach((n: Node) => {
- // Generate a unique node ID
- const newId = v4()
- idsMap[n.id] = newId
- const newConditions = n.data.conditions?.map((c) => {
- const newCondId = v4()
- sourceHandlesMap[c.id] = newCondId
- return { ...c, id: newCondId }
- })
- const newResponse = n.data.response
- ? {
- ...n.data.response,
- id: v4(),
- }
- : undefined
+ : reactFlowInstance.screenToFlowPosition({ x: position.x, y: position.y })
+
+ const resultNodes: AppNode[] = []
+
+ _selectionInstance.nodes.forEach((n: AppNode) => {
+ let newConditions
+ let newResponse
+ if (n.type === "default_node") {
+ newConditions = n.data.conditions.map((c) => {
+ const newCondId = "condition_" + v4()
+ sourceHandlesMap[c.id] = newCondId
+ return { ...c, id: newCondId }
+ })
+ newResponse = n.data.response
+ ? {
+ ...n.data.response,
+ id: "response_" + v4(),
+ }
+ : undefined
+ }
// Create a new node object
- const newNode: Node = {
- id: newId,
- type: n.type,
+ const newNode = generateNewNode(n.type, {
position: {
x: insidePosition.x + n.position.x - minimumX,
y: insidePosition.y + n.position.y - minimumY,
@@ -256,9 +273,25 @@ export function UndoRedoProvider({ children }: { children: React.ReactNode }) {
conditions: newConditions,
response: newResponse,
flags: [],
- id: newId,
},
- }
+ })
+ idsMap[n.id] = newNode.id
+
+ // const newNode: AppNode = {
+ // id: newId,
+ // type: n.type,
+ // position: {
+ // x: insidePosition.x + n.position.x - minimumX,
+ // y: insidePosition.y + n.position.y - minimumY,
+ // },
+ // data: {
+ // ...cloneDeep(n.data),
+ // conditions: newConditions,
+ // response: newResponse,
+ // flags: [],
+ // id: newId,
+ // },
+ // }
resultNodes.push({ ...newNode, selected: true })
})
@@ -267,26 +300,22 @@ export function UndoRedoProvider({ children }: { children: React.ReactNode }) {
return
}
- const newNodes = [
- ...nodes.map((e: Node) => ({ ...e, selected: false })),
- ...resultNodes,
- ]
+ const newNodes = [...nodes.map((e: AppNode) => ({ ...e, selected: false })), ...resultNodes]
- console.log(selectionInstance.edges)
selectionInstance.edges.forEach((e) => {
const source = idsMap[e.source]
const target = idsMap[e.target]
if (e.sourceHandle) {
const sourceHandle = sourceHandlesMap[e.sourceHandle]
- const id = v4()
+ const id = "reactflow__edge-" + v4()
edges = addEdge(
{
source,
target,
sourceHandle,
targetHandle: null,
- id,
+ id: id,
selected: false,
},
edges.map((e) => ({ ...e, selected: false }))
@@ -298,120 +327,6 @@ export function UndoRedoProvider({ children }: { children: React.ReactNode }) {
reactFlowInstance.setEdges(edges)
}
- // function paste(
- // selectionInstance,
- // position: { x: number; y: number; paneX?: number; paneY?: number }
- // ) {
- // let minimumX = Infinity;
- // let minimumY = Infinity;
- // let idsMap = {};
- // let nodes = reactFlowInstance.getNodes();
- // let edges = reactFlowInstance.getEdges();
- // selectionInstance.nodes.forEach((n) => {
- // if (n.position.y < minimumY) {
- // minimumY = n.position.y;
- // }
- // if (n.position.x < minimumX) {
- // minimumX = n.position.x;
- // }
- // });
-
- // const insidePosition = position.paneX
- // ? { x: position.paneX + position.x, y: position.paneY + position.y }
- // : reactFlowInstance.project({ x: position.x, y: position.y });
-
- // const resultNodes: any[] = []
-
- // selectionInstance.nodes.forEach((n: NodeType) => {
- // // Generate a unique node ID
- // let newId = getNodeId(n.data.type);
- // idsMap[n.id] = newId;
-
- // const positionX = insidePosition.x + n.position.x - minimumX
- // const positionY = insidePosition.y + n.position.y - minimumY
-
- // // Create a new node object
- // const newNode: NodeType = {
- // id: newId,
- // type: "genericNode",
- // position: {
- // x: insidePosition.x + n.position.x - minimumX,
- // y: insidePosition.y + n.position.y - minimumY,
- // },
- // data: {
- // ..._.cloneDeep(n.data),
- // id: newId,
- // },
- // };
-
- // // FIXME: CHECK WORK >>>>>>>
- // // check for intersections before paste
- // if (nodes.some(({ position, id, width, height }) => {
- // const xIntersect = ((positionX > position.x - width) && (positionX < (position.x + width)))
- // const yIntersect = ((positionY > position.y - height) && (positionY < (position.y + height)))
- // const result = xIntersect && yIntersect
- // // console.log({id: id, xIntersect: xIntersect, yIntersect: yIntersect, result: result})
- // return result
- // })) {
- // return setErrorData({ title: "Invalid place! Nodes can't intersect!" })
- // }
- // // FIXME: CHECK WORK >>>>>>>>
-
- // resultNodes.push({ ...newNode, selected: true })
-
- // });
-
- // if (resultNodes.length < selectionInstance.nodes.length) {
- // return
- // }
-
- // // Add the new node to the list of nodes in state
- // nodes = nodes
- // .map((e) => ({ ...e, selected: false }))
- // .concat(resultNodes);
- // reactFlowInstance.setNodes(nodes);
-
- // selectionInstance.edges.forEach((e) => {
- // let source = idsMap[e.source];
- // let target = idsMap[e.target];
- // let sourceHandleSplitted = e.sourceHandle.split("|");
- // let sourceHandle =
- // source +
- // "|" +
- // sourceHandleSplitted[1] +
- // "|" +
- // source
- // let targetHandleSplitted = e.targetHandle.split("|");
- // let targetHandle =
- // targetHandleSplitted.slice(0, -1).join("|") + target;
- // let id =
- // "reactflow__edge-" +
- // source +
- // sourceHandle +
- // "-" +
- // target +
- // targetHandle;
- // edges = addEdge(
- // {
- // source,
- // target,
- // sourceHandle,
- // targetHandle,
- // id,
- // style: { stroke: "inherit" },
- // className:
- // targetHandle.split("|")[0] === "Text"
- // ? "stroke-foreground "
- // : "stroke-foreground ",
- // animated: targetHandle.split("|")[0] === "Text",
- // selected: false,
- // },
- // edges.map((e) => ({ ...e, selected: false }))
- // );
- // });
- // reactFlowInstance.setEdges(edges);
- // }
-
return (
{children}
diff --git a/frontend/src/contexts/workspaceContext.tsx b/frontend/src/contexts/workspaceContext.tsx
index 41381b6e..fd40bffa 100644
--- a/frontend/src/contexts/workspaceContext.tsx
+++ b/frontend/src/contexts/workspaceContext.tsx
@@ -1,11 +1,10 @@
/* eslint-disable react-refresh/only-export-components */
import { createContext, useCallback, useContext, useEffect, useState } from "react"
import { useSearchParams } from "react-router-dom"
-import { Node } from "reactflow"
import { FlowType } from "../types/FlowTypes"
-import { NodeDataType } from "../types/NodeTypes"
+import { AppNode } from "../types/NodeTypes"
import { flowContext } from "./flowContext"
-import { notificationsContext } from "./notificationsContext"
+import { NotificationsContext } from "./notificationsContext"
type WorkspaceContextType = {
workspaceMode: boolean
@@ -20,7 +19,7 @@ type WorkspaceContextType = {
setSelectedNode: React.Dispatch>
handleNodeFlags: (
e: React.MouseEvent,
- setNodes: React.Dispatch[]>>
+ setNodes: React.Dispatch>
) => void
mouseOnPane: boolean
setMouseOnPane: React.Dispatch>
@@ -60,17 +59,13 @@ export const WorkspaceProvider = ({ children }: { children: React.ReactNode }) =
const [workspaceMode, setWorkspaceMode] = useState(false)
const [nodesLayoutMode, setNodesLayoutMode] = useState(false)
const [managerMode, setManagerMode] = useState(false)
- const [searchParams, setSearchParams] = useSearchParams()
+ const [searchParams] = useSearchParams()
const [settingsPage, setSettingsPage] = useState(searchParams.get("settings") === "opened")
const [selectedNode, setSelectedNode] = useState("")
- const { updateFlow, flows, tab, quietSaveFlows, setFlows } = useContext(flowContext)
+ const { flows, quietSaveFlows, setFlows } = useContext(flowContext)
const [mouseOnPane, setMouseOnPane] = useState(true)
const [modalsOpened, setModalsOpened] = useState(0)
- const { notification: n } = useContext(notificationsContext)
-
- useEffect(() => {
- console.log(modalsOpened)
- }, [modalsOpened])
+ const { notification: n } = useContext(NotificationsContext)
useEffect(() => {
if (modalsOpened === 0) {
@@ -83,10 +78,6 @@ export const WorkspaceProvider = ({ children }: { children: React.ReactNode }) =
}
}, [modalsOpened])
- useEffect(() => console.log(mouseOnPane), [mouseOnPane])
-
- const flow = flows.find((flow) => flow.name === tab)
-
const toggleWorkspaceMode = useCallback(() => {
setWorkspaceMode(() => !workspaceMode)
n.add({
@@ -114,42 +105,44 @@ export const WorkspaceProvider = ({ children }: { children: React.ReactNode }) =
})
}, [managerMode, n])
- const handleNodeFlags = useCallback((e: React.MouseEvent) => {
- const nodes = flows.flatMap((flow) => flow.data.nodes)
- console.log(nodes)
- const new_nds = nodes.map((nd: Node) => {
- if (nd.data.flags?.includes(e.currentTarget.name)) {
- nd.data.flags = nd.data.flags.filter((flag) => flag !== e.currentTarget.name)
- }
- if (nd.id === selectedNode) {
- if (nd.data.flags?.includes(e.currentTarget.name)) {
+ const handleNodeFlags = useCallback(
+ (e: React.MouseEvent) => {
+ const nodes = flows.flatMap((flow) => flow.data.nodes)
+ const new_nds = nodes.map((nd: AppNode) => {
+ if (nd.type === "default_node" && nd.data.flags?.includes(e.currentTarget.name)) {
nd.data.flags = nd.data.flags.filter((flag) => flag !== e.currentTarget.name)
- } else {
- if (!nd.data.flags) nd.data.flags = [e.currentTarget.name]
- else nd.data.flags = [...nd.data.flags, e.currentTarget.name]
}
- }
- return nd
- })
- const new_flows: FlowType[] = flows.map((flow) => {
- return {
- ...flow,
- data: {
- ...flow.data,
- nodes: flow.data.nodes.map((nd: Node) => {
- const new_nd = new_nds.find((n) => n.id === nd.id)
- if (new_nd) return new_nd
- else return nd
- }),
- },
- }
- })
- setFlows(() => new_flows)
- // if (flow) {
- // updateFlow(flow)
- // }
- quietSaveFlows()
- }, [flows, quietSaveFlows, selectedNode, setFlows])
+ if (nd.type === "default_node" && nd.id === selectedNode) {
+ if (nd.data.flags?.includes(e.currentTarget.name)) {
+ nd.data.flags = nd.data.flags.filter((flag) => flag !== e.currentTarget.name)
+ } else {
+ if (!nd.data.flags) nd.data.flags = [e.currentTarget.name]
+ else nd.data.flags = [...nd.data.flags, e.currentTarget.name]
+ }
+ }
+ return nd
+ })
+ const new_flows: FlowType[] = flows.map((flow) => {
+ return {
+ ...flow,
+ data: {
+ ...flow.data,
+ nodes: flow.data.nodes.map((nd: AppNode) => {
+ const new_nd = new_nds.find((n) => n.id === nd.id)
+ if (new_nd) return new_nd
+ else return nd
+ }),
+ },
+ }
+ })
+ setFlows(() => new_flows)
+ // if (flow) {
+ // updateFlow(flow)
+ // }
+ quietSaveFlows()
+ },
+ [flows, quietSaveFlows, selectedNode, setFlows]
+ )
const onModalOpen = useCallback((onOpen: () => void) => {
setMouseOnPane(false)
diff --git a/frontend/src/icons/nodes/conditions/CodeConditionIcon.tsx b/frontend/src/icons/nodes/conditions/CodeConditionIcon.tsx
index 933c473a..7518c176 100644
--- a/frontend/src/icons/nodes/conditions/CodeConditionIcon.tsx
+++ b/frontend/src/icons/nodes/conditions/CodeConditionIcon.tsx
@@ -10,25 +10,25 @@ const CodeConditionIcon = ({className, fill="var(--foreground)"}: React.SVGAttri
fill='none'
xmlns='http://www.w3.org/2000/svg'>
)
diff --git a/frontend/src/icons/nodes/conditions/LLMConditionIcon.tsx b/frontend/src/icons/nodes/conditions/LLMConditionIcon.tsx
index 7b981cc6..792646f0 100644
--- a/frontend/src/icons/nodes/conditions/LLMConditionIcon.tsx
+++ b/frontend/src/icons/nodes/conditions/LLMConditionIcon.tsx
@@ -10,11 +10,11 @@ const LLMConditionIcon = ({className, fill="var(--foreground)"}: React.SVGAttrib
fill='none'
xmlns='http://www.w3.org/2000/svg'>
)
diff --git a/frontend/src/modals/ConditionModal/ConditionModal.tsx b/frontend/src/modals/ConditionModal/ConditionModal.tsx
index 649e6204..cd03f148 100644
--- a/frontend/src/modals/ConditionModal/ConditionModal.tsx
+++ b/frontend/src/modals/ConditionModal/ConditionModal.tsx
@@ -9,23 +9,23 @@ import {
Tab,
Tabs,
} from "@nextui-org/react"
+import { Edge, useReactFlow } from "@xyflow/react"
import classNames from "classnames"
import { HelpCircle, TrashIcon } from "lucide-react"
import { useContext, useEffect, useMemo, useState } from "react"
import { useParams } from "react-router-dom"
-import { useReactFlow } from "reactflow"
import { lint_service } from "../../api/services"
import ModalComponent from "../../components/ModalComponent"
import { flowContext } from "../../contexts/flowContext"
-import { notificationsContext } from "../../contexts/notificationsContext"
+import { NotificationsContext } from "../../contexts/notificationsContext"
import { conditionType, conditionTypeType } from "../../types/ConditionTypes"
-import { NodeDataType, NodeType } from "../../types/NodeTypes"
+import { AppNode, DefaultNodeDataType, DefaultNodeType } from "../../types/NodeTypes"
import { generateNewConditionBase } from "../../utils"
import PythonCondition from "./components/PythonCondition"
import UsingLLMConditionSection from "./components/UsingLLMCondition"
type ConditionModalProps = {
- data: NodeDataType
+ data: DefaultNodeDataType
condition?: conditionType
is_create?: boolean
size?: ModalProps["size"]
@@ -61,8 +61,8 @@ const ConditionModal = ({
setSelected(key)
}
- const { getNode, setNodes, getNodes } = useReactFlow()
- const { notification: n } = useContext(notificationsContext)
+ const { getNode, setNodes, getNodes } = useReactFlow()
+ const { notification: n } = useContext(NotificationsContext)
const { updateFlow, flows, quietSaveFlows } = useContext(flowContext)
const { flowId } = useParams()
@@ -71,10 +71,11 @@ const ConditionModal = ({
)
const validateConditionName = (is_create: boolean) => {
- const nodes = getNodes() as NodeType[]
+ const nodes = getNodes() as AppNode[]
if (!is_create) {
- const is_name_valid = !nodes.some((node: NodeType) =>
- node.data.conditions?.some(
+ const is_name_valid = !nodes.some((node: AppNode) =>
+ node.type === "default_node" &&
+ node.data.conditions.some(
(c) => c.name === currentCondition.name && c.id !== currentCondition.id
)
)
@@ -90,7 +91,8 @@ const ConditionModal = ({
}
}
} else {
- const is_name_valid = !nodes.some((node: NodeType) =>
+ const is_name_valid = !nodes.some((node: AppNode) =>
+ node.type === "default_node" &&
node.data.conditions?.some((c) => c.name === currentCondition.name)
)
if (!is_name_valid) {
@@ -206,16 +208,12 @@ const ConditionModal = ({
[currentCondition]
)
- // useEffect(() => {
- // console.log(currentCondition)
- // }, [currentCondition])
const lintCondition = async () => {
setLintStatus(null)
if (currentCondition.type === "python") {
try {
const res = await lint_service(currentCondition.data.python?.action ?? "")
- console.log(res)
setLintStatus(res)
return res
} catch (error) {
@@ -231,7 +229,6 @@ const ConditionModal = ({
if (currentCondition.type === "python") {
const lint = await lintCondition()
const validate_action = validateConditionAction()
- console.log(lint)
if (lint && validate_action.status) {
setTestConditionPending(() => false)
return true
@@ -261,14 +258,14 @@ const ConditionModal = ({
const currentFlow = flows.find((flow) => flow.name === flowId)
const validate_name: ValidateErrorType = validateConditionName(is_create)
if (validate_name.status) {
- if (node && currentFlow) {
- const new_node = {
+ if (node && node.type === 'default_node' && currentFlow) {
+ const new_node: DefaultNodeType = {
...node,
data: {
...node.data,
conditions: is_create
? [...node.data.conditions, currentCondition]
- : data.conditions?.map((condition) =>
+ : data.conditions.map((condition) =>
condition.id === currentCondition.id ? currentCondition : condition
),
},
@@ -298,8 +295,8 @@ const ConditionModal = ({
const nodes = getNodes()
const node = getNode(data.id)
const currentFlow = flows.find((flow) => flow.name === flowId)
- if (node && currentFlow) {
- const new_node = {
+ if (node && node.type === 'default_node' && currentFlow) {
+ const new_node: DefaultNodeType = {
...node,
data: {
...node.data,
diff --git a/frontend/src/modals/FlowModal/CreateFlowModal.tsx b/frontend/src/modals/FlowModal/CreateFlowModal.tsx
index d167bd76..b369c1fd 100644
--- a/frontend/src/modals/FlowModal/CreateFlowModal.tsx
+++ b/frontend/src/modals/FlowModal/CreateFlowModal.tsx
@@ -14,7 +14,7 @@ import { useContext, useState } from "react"
import ModalComponent from "../../components/ModalComponent"
import { FLOW_COLORS } from "../../consts"
import { flowContext } from "../../contexts/flowContext"
-import { notificationsContext } from "../../contexts/notificationsContext"
+import { NotificationsContext } from "../../contexts/notificationsContext"
import { ModalType } from "../../types/ModalTypes"
import { generateNewFlow, validateFlowName } from "../../utils"
@@ -29,7 +29,7 @@ export type CreateFlowType = {
const CreateFlowModal = ({ isOpen, onClose, size = "3xl" }: CreateFlowModalProps) => {
const { flows, setFlows, saveFlows } = useContext(flowContext)
- const { notification: n } = useContext(notificationsContext)
+ const { notification: n } = useContext(NotificationsContext)
const [flow, setFlow] = useState({
name: "",
description: "",
diff --git a/frontend/src/modals/FlowModal/ManageFlowsModal.tsx b/frontend/src/modals/FlowModal/ManageFlowsModal.tsx
index 285fc04f..e56b6fd9 100644
--- a/frontend/src/modals/FlowModal/ManageFlowsModal.tsx
+++ b/frontend/src/modals/FlowModal/ManageFlowsModal.tsx
@@ -10,11 +10,11 @@ import {
} from "@nextui-org/react"
import { HelpCircle, TrashIcon } from "lucide-react"
import { useContext, useEffect, useState } from "react"
-import { useParams } from "react-router-dom"
+import { useNavigate, useParams } from "react-router-dom"
import ModalComponent from "../../components/ModalComponent"
import { FLOW_COLORS } from "../../consts"
import { flowContext } from "../../contexts/flowContext"
-import { notificationsContext } from "../../contexts/notificationsContext"
+import { NotificationsContext } from "../../contexts/notificationsContext"
import { FlowType } from "../../types/FlowTypes"
import { ModalType } from "../../types/ModalTypes"
import { validateFlowName } from "../../utils"
@@ -30,12 +30,13 @@ export type CreateFlowType = {
const ManageFlowsModal = ({ isOpen, onClose, size = "3xl" }: CreateFlowModalProps) => {
const { flows, setFlows, saveFlows } = useContext(flowContext)
- const { notification: n } = useContext(notificationsContext)
+ const { notification: n } = useContext(NotificationsContext)
const [newFlows, setNewFlows] = useState([...flows] ?? [])
const { flowId } = useParams()
const [flow, setFlow] = useState(
newFlows.find((_flow) => _flow.name === flowId) ?? [][0]
)
+ const navigate = useNavigate()
const [newFlow, setNewFlow] = useState(flow)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -101,6 +102,22 @@ const ManageFlowsModal = ({ isOpen, onClose, size = "3xl" }: CreateFlowModalProp
}
}
+ const onFlowDelete = () => {
+ if (newFlow.name === "Global") {
+ return n.add({
+ title: "Warning!",
+ message: "Global flow cannot be deleted.",
+ type: "warning",
+ })
+ } else {
+ if (flowId === newFlow.name) {
+ navigate('/app/home')
+ }
+ setFlows([...newFlows.filter((_flow) => _flow.name !== newFlow.name)])
+ saveFlows([...newFlows.filter((_flow) => _flow.name !== newFlow.name)])
+ }
+ }
+
return (
- {node.data.conditions &&
+ {node.type === 'default_node' && node.data.conditions &&
node.data.conditions.map((condition) => (
<>
{condition.data.transition_type !== "manual" && (
diff --git a/frontend/src/types/FlowTypes.ts b/frontend/src/types/FlowTypes.ts
index 9760bfd2..92e28910 100644
--- a/frontend/src/types/FlowTypes.ts
+++ b/frontend/src/types/FlowTypes.ts
@@ -1,4 +1,5 @@
-import { ReactFlowJsonObject } from "reactflow"
+import { Edge, ReactFlowJsonObject } from "@xyflow/react"
+import { AppNode } from "./NodeTypes"
export type FlowType = {
id: string
@@ -6,5 +7,5 @@ export type FlowType = {
description?: string
color?: string
subflow?: string
- data: ReactFlowJsonObject
+ data: ReactFlowJsonObject
}
diff --git a/frontend/src/types/NodeTypes.ts b/frontend/src/types/NodeTypes.ts
index a8e2dfd5..31c102dd 100644
--- a/frontend/src/types/NodeTypes.ts
+++ b/frontend/src/types/NodeTypes.ts
@@ -1,19 +1,39 @@
+import { Node } from "@xyflow/react"
import { conditionType } from "./ConditionTypes"
import { responseType } from "./ResponseTypes"
export type NodesTypes = 'default_node' | 'link_node'
-export type NodeType = {
+export type DefaultNodeType = Node
+export type LinkNodeType = Node
+export type AppNode = DefaultNodeType | LinkNodeType
+export type AllowAppNode = DefaultNodeType & LinkNodeType
+
+
+export type DefaultNodeDataType = {
+ id: string
+ name: string
+ response: responseType
+ conditions: conditionType[]
+ global_conditions?: string[]
+ local_conditions?: string[]
+ flags: string[]
+}
+
+export type LinkNodeDataType = {
id: string
- type: string
- dragHandle?: string
- data: NodeDataType
- position: {
- x: number
- y: number
+ name: string
+ transition: {
+ target_flow: string
+ target_node: string
}
}
+export type PartialDefaultNodeDataType = Partial
+export type PartialLinkNodeDataType = Partial
+
+export type AppNodeDataType = DefaultNodeDataType | LinkNodeDataType
+
export type NodeDataType = {
id: string
name: string
@@ -29,7 +49,7 @@ export type NodeDataType = {
}
export type NodeComponentType = {
- data: NodeDataType
+ data: DefaultNodeDataType
}
export interface NodeComponentConditionType extends NodeComponentType {
diff --git a/frontend/src/types/ReactFlowTypes.ts b/frontend/src/types/ReactFlowTypes.ts
new file mode 100644
index 00000000..21fed9ff
--- /dev/null
+++ b/frontend/src/types/ReactFlowTypes.ts
@@ -0,0 +1,8 @@
+import { Edge } from "@xyflow/react";
+import { AppNode } from "./NodeTypes";
+
+
+export type OnSelectionChangeParamsCustom = {
+ nodes: AppNode[]
+ edges: Edge[]
+}
\ No newline at end of file
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts
index 0bbf908b..bbafbfec 100644
--- a/frontend/src/utils.ts
+++ b/frontend/src/utils.ts
@@ -2,25 +2,22 @@ import { v4 } from "uuid"
import { CreateFlowType } from "./modals/FlowModal/CreateFlowModal"
import { conditionType } from "./types/ConditionTypes"
import { FlowType } from "./types/FlowTypes"
-import { NodeType } from "./types/NodeTypes"
+import {
+ AppNode,
+ DefaultNodeDataType,
+ DefaultNodeType,
+ LinkNodeDataType,
+ LinkNodeType,
+ NodesTypes,
+} from "./types/NodeTypes"
export const generateNewFlow = (flow: CreateFlowType) => {
- const node_id = v4()
- const node_2_id = v4()
- const condition_id = v4()
const newFlow: FlowType = {
...flow,
- id: v4(),
+ id: "flow_" + v4(),
data: {
nodes: [],
- edges: [
- {
- id: v4(),
- source: node_id,
- sourceHandle: condition_id,
- target: node_2_id,
- },
- ],
+ edges: [],
viewport: {
x: 0,
y: 0,
@@ -50,7 +47,7 @@ export const parseSearchParams = (
export const generateNewConditionBase = (): conditionType => {
return {
- id: v4(),
+ id: "condition_" + v4(),
name: "new_cnd",
type: "python",
data: {
@@ -60,14 +57,83 @@ export const generateNewConditionBase = (): conditionType => {
}
}
-export const isNodeDeletionValid = (nodes: NodeType[], id: string) => {
+export const isNodeDeletionValid = (nodes: AppNode[], id: string) => {
const node = nodes.find((n) => n.id === id)
if (!node) return false
- return !node.data.flags?.includes("start")
+ if (node.type === "link_node") return true
+ if (node.type === "default_node") return !node.data.flags?.includes("start")
}
export function delay(ms: number) {
return new Promise((resolve, reject) => {
- setTimeout(resolve, ms);
- });
+ setTimeout(resolve, ms)
+ })
+}
+
+export const generateNewNode = (
+ type: NodesTypes | undefined,
+ template?: Partial<
+ (Omit & {
+ data: Partial
+ }) &
+ (Omit & { data: Partial })
+ >
+) => {
+ const id = type + "_" + v4()
+ switch (type) {
+ case "default_node":
+ return {
+ id,
+ type,
+ position: template?.position ?? { x: 0, y: 0 },
+ data: {
+ id,
+ name: template?.data?.name ?? "New node",
+ response: template?.data?.response ?? {
+ id: "response_" + v4(),
+ name: "response",
+ type: "text",
+ data: [{ text: "New node response", priority: 1 }],
+ },
+ flags: template?.data?.flags ?? [],
+ conditions: template?.data?.conditions ?? [],
+ global_conditions: template?.data?.global_conditions ?? [],
+ local_conditions: template?.data?.local_conditions ?? [],
+ },
+ }
+ case "link_node":
+ return {
+ id,
+ type,
+ position: template?.position ?? { x: 0, y: 0 },
+ data: {
+ id,
+ name: template?.data?.name ?? "Link",
+ transition: template?.data?.transition ?? {
+ target_flow: template?.data?.transition?.target_flow ?? "",
+ target_node: template?.data?.transition?.target_flow ?? "",
+ },
+ },
+ }
+ }
+ return {
+ id,
+ type,
+ position: template?.position ?? { x: 0, y: 0 },
+ data: {
+ id,
+ name: template?.data?.name ?? "New node",
+ response: template?.data?.response ?? {
+ id: "response_" + v4(),
+ name: "response",
+ type: "text",
+ data: [{ text: "New node response", priority: 1 }],
+ },
+ flags: template?.data?.flags ?? [],
+ conditions: template?.data?.conditions ?? [],
+ global_conditions: template?.data?.global_conditions ?? [],
+ local_conditions: template?.data?.local_conditions ?? [],
+ },
+ }
}
+