@kiberon-labs/behave-graph-flow 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +3 -0
  2. package/package.json +42 -0
  3. package/postcss.config.ts +6 -0
  4. package/src/components/AutoSizeInput.tsx +65 -0
  5. package/src/components/Controls.tsx +87 -0
  6. package/src/components/Flow.tsx +101 -0
  7. package/src/components/InputSocket.tsx +142 -0
  8. package/src/components/Node.tsx +68 -0
  9. package/src/components/NodeContainer.tsx +46 -0
  10. package/src/components/NodePicker.tsx +77 -0
  11. package/src/components/OutputSocket.tsx +58 -0
  12. package/src/components/modals/ClearModal.tsx +40 -0
  13. package/src/components/modals/HelpModal.tsx +36 -0
  14. package/src/components/modals/LoadModal.tsx +96 -0
  15. package/src/components/modals/Modal.tsx +64 -0
  16. package/src/components/modals/SaveModal.tsx +60 -0
  17. package/src/hooks/useBehaveGraphFlow.ts +75 -0
  18. package/src/hooks/useChangeNodeData.ts +24 -0
  19. package/src/hooks/useCustomNodeTypes.tsx +31 -0
  20. package/src/hooks/useFlowHandlers.ts +180 -0
  21. package/src/hooks/useGraphRunner.ts +104 -0
  22. package/src/hooks/useMergeMap.ts +14 -0
  23. package/src/hooks/useNodeSpecJson.ts +20 -0
  24. package/src/hooks/useOnPressKey.ts +16 -0
  25. package/src/hooks/useQueriableDefinitions.ts +22 -0
  26. package/src/index.ts +37 -0
  27. package/src/styles.css +8 -0
  28. package/src/transformers/behaveToFlow.ts +57 -0
  29. package/src/transformers/flowToBehave.ts +93 -0
  30. package/src/types.d.ts +4 -0
  31. package/src/util/autoLayout.ts +9 -0
  32. package/src/util/calculateNewEdge.ts +49 -0
  33. package/src/util/colors.ts +41 -0
  34. package/src/util/getPickerFilters.ts +32 -0
  35. package/src/util/getSocketsByNodeTypeAndHandleType.ts +11 -0
  36. package/src/util/hasPositionMetaData.ts +10 -0
  37. package/src/util/isHandleConnected.ts +12 -0
  38. package/src/util/isValidConnection.ts +51 -0
  39. package/src/util/sleep.ts +6 -0
  40. package/tailwind.config.ts +19 -0
  41. package/tests/flowToBehave.test.ts +25 -0
  42. package/tests/tsconfig.json +10 -0
  43. package/tsconfig.json +60 -0
  44. package/tsdown.config.ts +15 -0
  45. package/typedoc.json +8 -0
  46. package/vitest.config.ts +15 -0
@@ -0,0 +1,104 @@
1
+ import { Engine, readGraphFromJSON } from '@kiberon-labs/behave-graph';
2
+ import type {
3
+ GraphJSON,
4
+ GraphNodes,
5
+ ILifecycleEventEmitter,
6
+ IRegistry
7
+ } from '@kiberon-labs/behave-graph';
8
+ import { useCallback, useEffect, useState } from 'react';
9
+
10
+ /** Runs the behavior graph by building the execution
11
+ * engine and triggering start on the lifecycle event emitter.
12
+ */
13
+ export const useGraphRunner = ({
14
+ graphJson,
15
+ autoRun = false,
16
+ registry
17
+ }: {
18
+ graphJson: GraphJSON | undefined;
19
+ autoRun?: boolean;
20
+ registry: IRegistry;
21
+ }) => {
22
+ const [engine, setEngine] = useState<Engine>();
23
+
24
+ const [run, setRun] = useState(autoRun);
25
+
26
+ const play = useCallback(() => {
27
+ setRun(true);
28
+ }, []);
29
+
30
+ const pause = useCallback(() => {
31
+ setRun(false);
32
+ }, []);
33
+
34
+ const togglePlay = useCallback(() => {
35
+ setRun((existing) => !existing);
36
+ }, []);
37
+
38
+ useEffect(() => {
39
+ if (!graphJson || !registry.values || !run || !registry.dependencies)
40
+ return;
41
+
42
+ let graphNodes: GraphNodes;
43
+ try {
44
+ graphNodes = readGraphFromJSON({
45
+ graphJson,
46
+ registry
47
+ }).nodes;
48
+ } catch (e) {
49
+ console.error(e);
50
+ return;
51
+ }
52
+ const engine = new Engine(graphNodes);
53
+
54
+ setEngine(engine);
55
+
56
+ return () => {
57
+ engine.dispose();
58
+ setEngine(undefined);
59
+ };
60
+ }, [graphJson, registry.values, registry.nodes, run, registry.dependencies]);
61
+
62
+ useEffect(() => {
63
+ if (!engine || !run) return;
64
+
65
+ engine.executeAllSync();
66
+
67
+ let timeout: number;
68
+
69
+ const eventEmitter = registry.dependencies
70
+ ?.ILifecycleEventEmitter as ILifecycleEventEmitter;
71
+
72
+ const onTick = async () => {
73
+ eventEmitter.tickEvent.emit();
74
+
75
+ // eslint-disable-next-line no-await-in-loop
76
+ await engine.executeAllAsync(500);
77
+
78
+ timeout = window.setTimeout(onTick, 50);
79
+ };
80
+
81
+ (async () => {
82
+ if (eventEmitter.startEvent.listenerCount > 0) {
83
+ eventEmitter.startEvent.emit();
84
+
85
+ await engine.executeAllAsync(5);
86
+ } else {
87
+ console.log('has no listener count');
88
+ }
89
+ onTick();
90
+ })();
91
+
92
+ return () => {
93
+ window.clearTimeout(timeout);
94
+ };
95
+ }, [engine, registry.dependencies?.ILifecycleEventEmitter, run]);
96
+
97
+ return {
98
+ engine,
99
+ playing: run,
100
+ play,
101
+ togglePlay,
102
+ pause
103
+ };
104
+ };
@@ -0,0 +1,14 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ export function useMergeMap<T, TMap extends Record<string, T>>(
4
+ mapA: TMap,
5
+ mapB: TMap
6
+ ): TMap {
7
+ const [result, setResult] = useState<TMap>(() => ({ ...mapA, ...mapB }));
8
+
9
+ useEffect(() => {
10
+ setResult({ ...mapA, ...mapB });
11
+ }, [mapA, mapB]);
12
+
13
+ return result;
14
+ }
@@ -0,0 +1,20 @@
1
+ import {
2
+ type IRegistry,
3
+ type NodeSpecJSON,
4
+ writeNodeSpecsToJSON
5
+ } from '@kiberon-labs/behave-graph';
6
+ import { useEffect, useState } from 'react';
7
+
8
+ export const useNodeSpecJson = (registry: IRegistry) => {
9
+ const [specJson, setSpecJson] = useState<NodeSpecJSON[]>();
10
+
11
+ useEffect(() => {
12
+ if (!registry.nodes || !registry.values || !registry.dependencies) {
13
+ setSpecJson(undefined);
14
+ return;
15
+ }
16
+ setSpecJson(writeNodeSpecsToJSON(registry));
17
+ }, [registry.nodes, registry.values, registry.dependencies]);
18
+
19
+ return specJson;
20
+ };
@@ -0,0 +1,16 @@
1
+ import { useEffect } from 'react';
2
+
3
+ export const useOnPressKey = (
4
+ key: string,
5
+ callback: (e: KeyboardEvent) => void
6
+ ) => {
7
+ useEffect(() => {
8
+ const handleKeyDown = (e: KeyboardEvent) => {
9
+ if (e.code === key) {
10
+ callback(e);
11
+ }
12
+ };
13
+ document.addEventListener('keydown', handleKeyDown);
14
+ return () => document.removeEventListener('keydown', handleKeyDown);
15
+ }, [key, callback]);
16
+ };
@@ -0,0 +1,22 @@
1
+ import type { IQueryableRegistry } from '@kiberon-labs/behave-graph';
2
+ import { useMemo } from 'react';
3
+
4
+ export const toQueryableDefinitions = <T>(definitionsMap: {
5
+ [id: string]: T;
6
+ }): IQueryableRegistry<T> => ({
7
+ get: (id: string) => definitionsMap[id],
8
+ getAll: () => Object.values(definitionsMap),
9
+ getAllNames: () => Object.keys(definitionsMap),
10
+ contains: (id: string) => definitionsMap[id] !== undefined
11
+ });
12
+
13
+ export const useQueryableDefinitions = <T>(definitionsMap: {
14
+ [id: string]: T;
15
+ }): IQueryableRegistry<T> => {
16
+ const queriableDefinitions = useMemo(
17
+ () => toQueryableDefinitions(definitionsMap),
18
+ [definitionsMap]
19
+ );
20
+
21
+ return queriableDefinitions;
22
+ };
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ export * from './components/modals/ClearModal.js';
2
+ export * from './components/modals/HelpModal.js';
3
+ export * from './components/modals/LoadModal.js';
4
+ export * from './components/modals/Modal.js';
5
+ export * from './components/modals/SaveModal.js';
6
+ export * from './components/Controls.js';
7
+
8
+ export * from './components/AutoSizeInput.js';
9
+ export * from './components/Controls.js';
10
+ export * from './components/InputSocket.js';
11
+ export * from './components/Node.js';
12
+ export * from './components/Flow.js';
13
+ export * from './components/NodeContainer.js';
14
+ export * from './components/NodePicker.js';
15
+ export * from './components/OutputSocket.js';
16
+
17
+ export * from './hooks/useChangeNodeData.js';
18
+ export * from './hooks/useOnPressKey.js';
19
+ export * from './hooks/useFlowHandlers.js';
20
+ export * from './hooks/useGraphRunner.js';
21
+ export * from './hooks/useBehaveGraphFlow.js';
22
+ export * from './hooks/useNodeSpecJson.js';
23
+ export * from './hooks/useCustomNodeTypes.js';
24
+ export * from './hooks/useMergeMap.js';
25
+
26
+ export * from './transformers/behaveToFlow.js';
27
+ export * from './transformers/flowToBehave.js';
28
+
29
+ export * from './util/autoLayout.js';
30
+ export * from './util/calculateNewEdge.js';
31
+ export * from './util/colors.js';
32
+ export * from './util/getPickerFilters.js';
33
+ export * from './util/getSocketsByNodeTypeAndHandleType.js';
34
+ export * from './util/hasPositionMetaData.js';
35
+ export * from './util/isHandleConnected.js';
36
+ export * from './util/isValidConnection.js';
37
+ export * from './util/sleep.js';
package/src/styles.css ADDED
@@ -0,0 +1,8 @@
1
+ .container {
2
+ display: flex;
3
+ flex-direction: row;
4
+ justify-content: space-between;
5
+ gap: 2rem;
6
+ position: relative;
7
+ padding: 0 0.5rem;
8
+ }
@@ -0,0 +1,57 @@
1
+ import type { GraphJSON } from '@kiberon-labs/behave-graph';
2
+ import type { Edge, Node } from 'reactflow';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+
5
+ export const behaveToFlow = (graph: GraphJSON): [Node[], Edge[]] => {
6
+ const nodes: Node[] = [];
7
+ const edges: Edge[] = [];
8
+
9
+ graph.nodes?.forEach((nodeJSON) => {
10
+ const node: Node = {
11
+ id: nodeJSON.id,
12
+ type: nodeJSON.type,
13
+ position: {
14
+ x: nodeJSON.metadata?.positionX
15
+ ? Number(nodeJSON.metadata?.positionX)
16
+ : 0,
17
+ y: nodeJSON.metadata?.positionY
18
+ ? Number(nodeJSON.metadata?.positionY)
19
+ : 0
20
+ },
21
+ data: {} as { [key: string]: any }
22
+ };
23
+
24
+ nodes.push(node);
25
+
26
+ if (nodeJSON.parameters) {
27
+ for (const [inputKey, input] of Object.entries(nodeJSON.parameters)) {
28
+ if ('link' in input && input.link !== undefined) {
29
+ edges.push({
30
+ id: uuidv4(),
31
+ source: input.link.nodeId,
32
+ sourceHandle: input.link.socket,
33
+ target: nodeJSON.id,
34
+ targetHandle: inputKey
35
+ });
36
+ }
37
+ if ('value' in input) {
38
+ node.data[inputKey] = input.value;
39
+ }
40
+ }
41
+ }
42
+
43
+ if (nodeJSON.flows) {
44
+ for (const [inputKey, link] of Object.entries(nodeJSON.flows)) {
45
+ edges.push({
46
+ id: uuidv4(),
47
+ source: nodeJSON.id,
48
+ sourceHandle: inputKey,
49
+ target: link.nodeId,
50
+ targetHandle: link.socket
51
+ });
52
+ }
53
+ }
54
+ });
55
+
56
+ return [nodes, edges];
57
+ };
@@ -0,0 +1,93 @@
1
+ import type {
2
+ GraphJSON,
3
+ NodeJSON,
4
+ NodeSpecJSON
5
+ } from '@kiberon-labs/behave-graph';
6
+ import type { Edge, Node } from 'reactflow';
7
+
8
+ const isNullish = (value: any): value is null | undefined =>
9
+ value === undefined || value === null;
10
+
11
+ export const flowToBehave = (
12
+ nodes: Node[],
13
+ edges: Edge[],
14
+ nodeSpecJSON: NodeSpecJSON[]
15
+ ): GraphJSON => {
16
+ const graph: GraphJSON = { nodes: [], variables: [], customEvents: [] };
17
+
18
+ nodes.forEach((node) => {
19
+ if (node.type === undefined) return;
20
+
21
+ const nodeSpec = nodeSpecJSON.find(
22
+ (nodeSpec) => nodeSpec.type === node.type
23
+ );
24
+
25
+ if (nodeSpec === undefined) return;
26
+
27
+ const behaveNode: NodeJSON = {
28
+ id: node.id,
29
+ type: node.type,
30
+ metadata: {
31
+ positionX: String(node.position.x),
32
+ positionY: String(node.position.y)
33
+ }
34
+ };
35
+
36
+ Object.entries(node.data).forEach(([key, value]) => {
37
+ if (behaveNode.parameters === undefined) {
38
+ behaveNode.parameters = {};
39
+ }
40
+ behaveNode.parameters[key] = { value: value as string };
41
+ });
42
+
43
+ edges
44
+ .filter((edge) => edge.target === node.id)
45
+ .forEach((edge) => {
46
+ const inputSpec = nodeSpec.inputs.find(
47
+ (input) => input.name === edge.targetHandle
48
+ );
49
+ if (inputSpec && inputSpec.valueType === 'flow') {
50
+ // skip flows
51
+ return;
52
+ }
53
+ if (behaveNode.parameters === undefined) {
54
+ behaveNode.parameters = {};
55
+ }
56
+ if (isNullish(edge.targetHandle)) return;
57
+ if (isNullish(edge.sourceHandle)) return;
58
+
59
+ // TODO: some of these are flow outputs, and should be saved differently. -Ben, Oct 11, 2022
60
+ behaveNode.parameters[edge.targetHandle] = {
61
+ link: { nodeId: edge.source, socket: edge.sourceHandle }
62
+ };
63
+ });
64
+
65
+ edges
66
+ .filter((edge) => edge.source === node.id)
67
+ .forEach((edge) => {
68
+ const outputSpec = nodeSpec.outputs.find(
69
+ (output) => output.name === edge.sourceHandle
70
+ );
71
+ if (outputSpec && outputSpec.valueType !== 'flow') {
72
+ return;
73
+ }
74
+ if (behaveNode.flows === undefined) {
75
+ behaveNode.flows = {};
76
+ }
77
+ if (isNullish(edge.targetHandle)) return;
78
+ if (isNullish(edge.sourceHandle)) return;
79
+
80
+ // TODO: some of these are flow outputs, and should be saved differently. -Ben, Oct 11, 2022
81
+ behaveNode.flows[edge.sourceHandle] = {
82
+ nodeId: edge.target,
83
+ socket: edge.targetHandle
84
+ };
85
+ });
86
+
87
+ // TODO filter out any orphan nodes at this point, to avoid errors further down inside behave-graph
88
+
89
+ graph.nodes?.push(behaveNode);
90
+ });
91
+
92
+ return graph;
93
+ };
package/src/types.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module '*.module.css' {
2
+ const classes: { [key: string]: string };
3
+ export default classes;
4
+ }
@@ -0,0 +1,9 @@
1
+ import type { Edge, Node } from 'reactflow';
2
+
3
+ export const autoLayout = (nodes: Node[], _edges: Edge[]) => {
4
+ let x = 0;
5
+ nodes.forEach((node) => {
6
+ node.position.x = x;
7
+ x += 200;
8
+ });
9
+ };
@@ -0,0 +1,49 @@
1
+ import type { NodeSpecJSON } from '@kiberon-labs/behave-graph';
2
+ import type { Node, OnConnectStartParams } from 'reactflow';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+
5
+ import { getSocketsByNodeTypeAndHandleType } from './getSocketsByNodeTypeAndHandleType.js';
6
+
7
+ export const calculateNewEdge = (
8
+ originNode: Node,
9
+ destinationNodeType: string,
10
+ destinationNodeId: string,
11
+ connection: OnConnectStartParams,
12
+ specJSON: NodeSpecJSON[]
13
+ ) => {
14
+ const sockets = getSocketsByNodeTypeAndHandleType(
15
+ specJSON,
16
+ originNode.type,
17
+ connection.handleType
18
+ );
19
+ const originSocket = sockets?.find(
20
+ (socket) => socket.name === connection.handleId
21
+ );
22
+
23
+ const newSockets = getSocketsByNodeTypeAndHandleType(
24
+ specJSON,
25
+ destinationNodeType,
26
+ connection.handleType === 'source' ? 'target' : 'source'
27
+ );
28
+ const newSocket = newSockets?.find(
29
+ (socket) => socket.valueType === originSocket?.valueType
30
+ );
31
+
32
+ if (connection.handleType === 'source') {
33
+ return {
34
+ id: uuidv4(),
35
+ source: connection.nodeId ?? '',
36
+ sourceHandle: connection.handleId,
37
+ target: destinationNodeId,
38
+ targetHandle: newSocket?.name
39
+ };
40
+ }
41
+
42
+ return {
43
+ id: uuidv4(),
44
+ target: connection.nodeId ?? '',
45
+ targetHandle: connection.handleId,
46
+ source: destinationNodeId,
47
+ sourceHandle: newSocket?.name
48
+ };
49
+ };
@@ -0,0 +1,41 @@
1
+ import type { NodeSpecJSON } from '@kiberon-labs/behave-graph';
2
+
3
+ export type color =
4
+ | 'red'
5
+ | 'green'
6
+ | 'lime'
7
+ | 'purple'
8
+ | 'blue'
9
+ | 'gray'
10
+ | 'white';
11
+
12
+ export const colors: Record<color, [string, string, string]> = {
13
+ red: ['bg-orange-700', 'border-orange-700', 'text-white'],
14
+ green: ['bg-green-600', 'border-green-600', 'text-white'],
15
+ lime: ['bg-lime-500', 'border-lime-500', 'text-gray-900'],
16
+ purple: ['bg-purple-500', 'border-purple-500', 'text-white'],
17
+ blue: ['bg-cyan-600', 'border-cyan-600', 'text-white'],
18
+ gray: ['bg-gray-500', 'border-gray-500', 'text-white'],
19
+ white: ['bg-white', 'border-white', 'text-gray-700']
20
+ };
21
+
22
+ export const valueTypeColorMap: Record<string, string> = {
23
+ flow: 'white',
24
+ number: 'green',
25
+ float: 'green',
26
+ integer: 'lime',
27
+ boolean: 'red',
28
+ string: 'purple'
29
+ };
30
+
31
+ export const categoryColorMap: Record<NodeSpecJSON['category'], color> = {
32
+ Event: 'red',
33
+ Logic: 'green',
34
+ Variable: 'purple',
35
+ Query: 'purple',
36
+ Action: 'blue',
37
+ Flow: 'gray',
38
+ Effect: 'lime',
39
+ Time: 'gray',
40
+ None: 'gray'
41
+ };
@@ -0,0 +1,32 @@
1
+ import type { NodeSpecJSON } from '@kiberon-labs/behave-graph';
2
+ import type { Node, OnConnectStartParams } from 'reactflow';
3
+ import type { NodePickerFilters } from '../components/NodePicker.js';
4
+ import { getSocketsByNodeTypeAndHandleType } from './getSocketsByNodeTypeAndHandleType.js';
5
+
6
+ export const getNodePickerFilters = (
7
+ nodes: Node[],
8
+ params: OnConnectStartParams | undefined,
9
+ specJSON: NodeSpecJSON[] | undefined
10
+ ): NodePickerFilters | undefined => {
11
+ if (params === undefined) return;
12
+
13
+ const originNode = nodes.find((node) => node.id === params.nodeId);
14
+ if (originNode === undefined) return;
15
+
16
+ const sockets = specJSON
17
+ ? getSocketsByNodeTypeAndHandleType(
18
+ specJSON,
19
+ originNode.type,
20
+ params.handleType
21
+ )
22
+ : undefined;
23
+
24
+ const socket = sockets?.find((socket) => socket.name === params.handleId);
25
+
26
+ if (socket === undefined) return;
27
+
28
+ return {
29
+ handleType: params.handleType === 'source' ? 'target' : 'source',
30
+ valueType: socket.valueType
31
+ };
32
+ };
@@ -0,0 +1,11 @@
1
+ import type { NodeSpecJSON } from '@kiberon-labs/behave-graph';
2
+
3
+ export const getSocketsByNodeTypeAndHandleType = (
4
+ nodes: NodeSpecJSON[],
5
+ nodeType: string | undefined,
6
+ handleType: 'source' | 'target' | null
7
+ ) => {
8
+ const nodeSpec = nodes.find((node) => node.type === nodeType);
9
+ if (nodeSpec === undefined) return;
10
+ return handleType === 'source' ? nodeSpec.outputs : nodeSpec.inputs;
11
+ };
@@ -0,0 +1,10 @@
1
+ import type { GraphJSON } from '@kiberon-labs/behave-graph';
2
+
3
+ export const hasPositionMetaData = (graph: GraphJSON): boolean => {
4
+ if (graph.nodes === undefined) return false;
5
+ return graph.nodes.some(
6
+ (node) =>
7
+ node.metadata?.positionX !== undefined ||
8
+ node.metadata?.positionY !== undefined
9
+ );
10
+ };
@@ -0,0 +1,12 @@
1
+ import type { Edge } from 'reactflow';
2
+
3
+ export const isHandleConnected = (
4
+ edges: Edge[],
5
+ nodeId: string,
6
+ handleId: string,
7
+ type: 'source' | 'target'
8
+ ) => {
9
+ return edges.some(
10
+ (edge) => edge[type] === nodeId && edge[`${type}Handle`] === handleId
11
+ );
12
+ };
@@ -0,0 +1,51 @@
1
+ import type { NodeSpecJSON } from '@kiberon-labs/behave-graph';
2
+ import type { Connection, ReactFlowInstance } from 'reactflow';
3
+
4
+ import { getSocketsByNodeTypeAndHandleType } from './getSocketsByNodeTypeAndHandleType.js';
5
+ import { isHandleConnected } from './isHandleConnected.js';
6
+
7
+ export const isValidConnection = (
8
+ connection: Connection,
9
+ instance: ReactFlowInstance,
10
+ specJSON: NodeSpecJSON[]
11
+ ) => {
12
+ if (connection.source === null || connection.target === null) return false;
13
+
14
+ const sourceNode = instance.getNode(connection.source);
15
+ const targetNode = instance.getNode(connection.target);
16
+ const edges = instance.getEdges();
17
+
18
+ if (sourceNode === undefined || targetNode === undefined) return false;
19
+
20
+ const sourceSockets = getSocketsByNodeTypeAndHandleType(
21
+ specJSON,
22
+ sourceNode.type,
23
+ 'source'
24
+ );
25
+
26
+ const sourceSocket = sourceSockets?.find(
27
+ (socket) => socket.name === connection.sourceHandle
28
+ );
29
+
30
+ const targetSockets = getSocketsByNodeTypeAndHandleType(
31
+ specJSON,
32
+ targetNode.type,
33
+ 'target'
34
+ );
35
+
36
+ const targetSocket = targetSockets?.find(
37
+ (socket) => socket.name === connection.targetHandle
38
+ );
39
+
40
+ if (sourceSocket === undefined || targetSocket === undefined) return false;
41
+
42
+ // only flow sockets can have two inputs
43
+ if (
44
+ targetSocket.valueType !== 'flow' &&
45
+ isHandleConnected(edges, targetNode.id, targetSocket.name, 'target')
46
+ ) {
47
+ return false;
48
+ }
49
+
50
+ return sourceSocket.valueType === targetSocket.valueType;
51
+ };
@@ -0,0 +1,6 @@
1
+ /* eslint-disable no-promise-executor-return */
2
+ export function sleep(durationInSeconds: number) {
3
+ return new Promise((resolve) =>
4
+ setTimeout(resolve, Math.round(durationInSeconds * 1000))
5
+ );
6
+ }
@@ -0,0 +1,19 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ './public/index.html',
5
+ './src/**/*.{js,ts,jsx,tsx,css}',
6
+ '../../packages/flow/dist/**/*.{js,ts,jsx,tsx,css}'
7
+ ],
8
+ theme: {
9
+ extend: {}
10
+ },
11
+ plugins: [
12
+ {
13
+ tailwindcss: {}
14
+ },
15
+ {
16
+ autoprefixer: {}
17
+ }
18
+ ]
19
+ };
@@ -0,0 +1,25 @@
1
+ import {
2
+ registerCoreProfile,
3
+ type GraphJSON,
4
+ writeNodeSpecsToJSON
5
+ } from '@kiberon-labs/behave-graph';
6
+
7
+ import rawFlowGraph from '../../../graphs/react-flow/graph.json';
8
+ import { behaveToFlow } from '../src/transformers/behaveToFlow.js';
9
+ import { flowToBehave } from '../src/transformers/flowToBehave.js';
10
+ import { it, expect } from 'vitest';
11
+
12
+ const flowGraph = rawFlowGraph as GraphJSON;
13
+
14
+ const [nodes, edges] = behaveToFlow(flowGraph);
15
+
16
+ it('transforms from flow to behave', () => {
17
+ const registry = registerCoreProfile({
18
+ nodes: {},
19
+ values: {},
20
+ dependencies: {}
21
+ });
22
+ const specJSON = writeNodeSpecsToJSON(registry);
23
+ const output = flowToBehave(nodes, edges, specJSON);
24
+ expect(output).toEqual(flowGraph);
25
+ });
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "../",
5
+ "lib": []
6
+ },
7
+ "include": [
8
+ "**/*.ts"
9
+ ]
10
+ }