diff --git a/web/app/dashboard/ai-workflows/_components/workflow-editor.tsx b/web/app/dashboard/ai-workflows/_components/workflow-editor.tsx
index 4af018e..311e735 100644
--- a/web/app/dashboard/ai-workflows/_components/workflow-editor.tsx
+++ b/web/app/dashboard/ai-workflows/_components/workflow-editor.tsx
@@ -8,9 +8,9 @@ import {
BaseEdge,
ConnectionMode,
Controls,
+ EdgeLabelRenderer,
getBezierPath,
Handle,
- MarkerType,
Position,
ReactFlow,
ViewportPortal,
@@ -30,16 +30,22 @@ import {
} from "@xyflow/react"
import {
AlertCircleIcon,
+ BotIcon,
CheckCircle2Icon,
+ CircleStopIcon,
+ DatabaseIcon,
GitBranchIcon,
+ MessageSquareTextIcon,
PanelLeftCloseIcon,
PanelLeftOpenIcon,
+ PlayIcon,
PlusIcon,
Redo2Icon,
RotateCcwIcon,
SaveIcon,
SendIcon,
Undo2Icon,
+ UserRoundIcon,
} from "lucide-react"
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"
@@ -103,6 +109,8 @@ type WorkflowEditorSnapshot = {
}
type WorkflowEdgeRenderData = {
active?: boolean
+ nodeSpecs?: AIWorkflowNodeSpec[]
+ onInsertNode?: (edgeId: string, spec: AIWorkflowNodeSpec) => void
}
type WorkflowFinalConnectionState = FinalConnectionState
@@ -131,16 +139,11 @@ const fitViewOptions = {
const defaultEdgeOptions = {
type: "workflowEdge",
- markerEnd: {
- type: MarkerType.ArrowClosed,
- },
style: {
- strokeWidth: 1.6,
+ strokeWidth: 2,
},
}
-const workflowHandleRadius = 8
-
function toFlowNodes(definition: AIWorkflowDefinition): WorkflowFlowNode[] {
return fromApiDefinition(definition).nodes.map((node) => ({
id: node.id,
@@ -659,6 +662,65 @@ export function WorkflowEditor({
[pushCurrentSnapshotToHistory, setEdges, setNodes]
)
+ const insertNodeOnEdge = useCallback(
+ (edgeId: string, spec: AIWorkflowNodeSpec) => {
+ const edge = edges.find((item) => item.id === edgeId)
+ if (!edge) {
+ return
+ }
+ pushCurrentSnapshotToHistory()
+ const remainingEdges = edges.filter((item) => item.id !== edge.id)
+ setNodes((currentNodes) => {
+ const sourceNode = currentNodes.find((node) => node.id === edge.source)
+ const targetNode = currentNodes.find((node) => node.id === edge.target)
+ const nextPosition = sourceNode && targetNode
+ ? {
+ x: (sourceNode.position.x + targetNode.position.x) / 2,
+ y: (sourceNode.position.y + targetNode.position.y) / 2,
+ }
+ : { x: 180 + currentNodes.length * 32, y: 120 + currentNodes.length * 24 }
+ const nextNode = createWorkflowNodeFromSpec(spec, currentNodes, nextPosition) as WorkflowFlowNode
+ const sourceToNew = {
+ id: uniqueEdgeId(remainingEdges, edge.source, nextNode.id, edge.sourceHandle),
+ source: edge.source,
+ target: nextNode.id,
+ sourceHandle: edge.sourceHandle,
+ type: "workflowEdge",
+ } as WorkflowFlowEdge
+ const newToTarget = {
+ id: uniqueEdgeId([...remainingEdges, sourceToNew], nextNode.id, edge.target),
+ source: nextNode.id,
+ target: edge.target,
+ targetHandle: edge.targetHandle,
+ type: "workflowEdge",
+ } as WorkflowFlowEdge
+ const nextEdges = [...remainingEdges, sourceToNew, newToTarget]
+ let nextDraft = applyConditionBranchConnection(
+ toDraft([...currentNodes, nextNode], nextEdges),
+ sourceToNew
+ )
+ nextDraft = applyAutoInputMappings(nextDraft, edge.source, nextNode.id, nodeSpecs)
+ nextDraft = applyAutoInputMappings(nextDraft, nextNode.id, edge.target, nodeSpecs)
+ setEdges(nextEdges)
+ return [...currentNodes, nextNode].map((node) => {
+ const draftNode = nextDraft.nodes.find((item) => item.id === node.id)
+ if (!draftNode) {
+ return node
+ }
+ return {
+ ...node,
+ data: {
+ ...node.data,
+ config: draftNode.data?.config ?? node.data.config,
+ inputs: draftNode.data?.inputs ?? node.data.inputs,
+ },
+ }
+ })
+ })
+ },
+ [edges, nodeSpecs, pushCurrentSnapshotToHistory, setEdges, setNodes]
+ )
+
const renderedNodes = useMemo(
() =>
enrichNodesForRender(nodes, nodeSpecs).map((node) => ({
@@ -774,10 +836,12 @@ export function WorkflowEditor({
selected: active,
data: {
active,
+ nodeSpecs,
+ onInsertNode: insertNodeOnEdge,
} satisfies WorkflowEdgeRenderData,
}
}),
- [edges, selectedEdgeId]
+ [edges, insertNodeOnEdge, nodeSpecs, selectedEdgeId]
)
const clampNodeLibraryWidth = useCallback((width: number) => {
@@ -810,7 +874,7 @@ export function WorkflowEditor({
}
return (
-
+
{nodeLibraryRendered ? (
<>