All files / src/pages/VisProgPage/visualProgrammingUI/nodes InferredBeliefNode.tsx

0% Statements 0/34
0% Branches 0/16
0% Functions 0/11
0% Lines 0/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176                                                                                                                                                                                                                                                                                                                                                               
import {getConnectedEdges, type Node, type NodeProps, Position} from '@xyflow/react';
import {useState} from "react";
import styles from '../../VisProg.module.css';
import {Toolbar} from '../components/NodeComponents.tsx';
import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx";
import {allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from "../VisProgStores.tsx";
import {BeliefGlobalReduce, noBeliefCycles, noMatchingLeftRightBelief} from "./BeliefGlobals.ts";
import switchStyles from './InferredBeliefNode.module.css';
 
 
/**
 * The default data structure for an InferredBelief node
 */
export type InferredBeliefNodeData = {
  label: string;
  droppable: boolean;
  inferredBelief: InferredBelief;
  hasReduce: boolean;
};
 
/**
 * stores a boolean to represent the operator
 * and a left and right BeliefNode (can be both an inferred and a basic belief)
 * in the form of their corresponding id's
 */
export type InferredBelief = {
  left: string | undefined,
  operator: boolean,
  right: string | undefined,
}
 
export type InferredBeliefNode = Node<InferredBeliefNodeData>;
 
/**
 * This function is called whenever a connection is made with this node type as the target
 * @param _thisNode the node of this node type which function is called
 * @param _sourceNodeId the source of the received connection
 */
export function InferredBeliefConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
  const data = _thisNode.data as InferredBeliefNodeData;
 
  if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId
    && ['basic_belief', 'inferred_belief'].includes(node.type!)))
  ) {
    const connectedEdges = getConnectedEdges([_thisNode], useFlowStore.getState().edges);
    switch(connectedEdges.find(edge => edge.source === _sourceNodeId)?.targetHandle){
      case 'beliefLeft': data.inferredBelief.left = _sourceNodeId; break;
      case 'beliefRight': data.inferredBelief.right = _sourceNodeId; break;
    }
  }
}
 
/**
 * This function is called whenever a connection is made with this node type as the source
 * @param _thisNode the node of this node type which function is called
 * @param _targetNodeId the target of the created connection
 */
export function InferredBeliefConnectionSource(_thisNode: Node, _targetNodeId: string) {
  // no additional connection logic exists yet
}
 
/**
 * This function is called whenever a connection is disconnected with this node type as the target
 * @param _thisNode the node of this node type which function is called
 * @param _sourceNodeId the source of the disconnected connection
 */
export function InferredBeliefDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
  const data = _thisNode.data as InferredBeliefNodeData;
 
  if (_sourceNodeId === data.inferredBelief.left) data.inferredBelief.left = undefined;
  if (_sourceNodeId === data.inferredBelief.right) data.inferredBelief.right = undefined;
}
 
/**
 * This function is called whenever a connection is disconnected with this node type as the source
 * @param _thisNode the node of this node type which function is called
 * @param _targetNodeId the target of the diconnected connection
 */
export function InferredBeliefDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
  // no additional connection logic exists yet
}
 
export const InferredBeliefTooltip = `
  Combines two beliefs into a single belief using logical inference, 
  the node can be toggled between using "AND" and "OR" mode for inference`;
/**
 * Defines how an InferredBelief node should be rendered
 * @param {NodeProps<InferredBeliefNode>} props - Node properties provided by React Flow, including `id` and `data`.
 * @returns The rendered InferredBeliefNode React element. (React.JSX.Element)
 */
export default function InferredBeliefNode(props: NodeProps<InferredBeliefNode>) {
  const data = props.data;
  const { updateNodeData } = useFlowStore();
  // start of as an AND operator, true: "AND", false: "OR"
  const [enforceAllBeliefs, setEnforceAllBeliefs] = useState(true);
 
  // used to toggle operator
  function onToggle() {
    const newOperator = !enforceAllBeliefs; // compute the new value
    setEnforceAllBeliefs(newOperator);
 
    updateNodeData(props.id, {
      ...data,
      inferredBelief: {
        ...data.inferredBelief,
        operator: enforceAllBeliefs,
      }
    });
  }
 
  return (
    <>
      <Toolbar nodeId={props.id} allowDelete={true}/>
      <div className={`${styles.defaultNode} ${styles.nodeInferredBelief}`}>
        {/* The checkbox used to toggle the operator between 'AND' and 'OR' */}
        <label className={switchStyles.operatorSwitch}>
          <input
            type="checkbox"
            checked={data.inferredBelief.operator}
            onChange={onToggle}
          />
          <div className={switchStyles.switchVisual}></div>
          <div className={switchStyles.switchLabels}>
            <span title={"Belief is fulfilled if either of the supplied beliefs is true"}>OR</span>
            <span title={"Belief is fulfilled if all of the supplied beliefs are true"}>AND</span>
          </div>
        </label>
 
 
        {/* outgoing connections */}
        <MultiConnectionHandle type="source" position={Position.Right} id="source" rules={[
          allowOnlyConnectionsFromType(["norm", "trigger"]),
          noBeliefCycles,
          noMatchingLeftRightBelief
        ]}/>
 
        {/* incoming connections */}
        <SingleConnectionHandle type="target" position={Position.Left} style={{top: '30%'}} id="beliefLeft" rules={[
          allowOnlyConnectionsFromType(["basic_belief", "inferred_belief"]),
          noBeliefCycles,
          noMatchingLeftRightBelief
        ]}/>
        <SingleConnectionHandle type="target" position={Position.Left} style={{top: '70%'}} id="beliefRight" rules={[
          allowOnlyConnectionsFromType(["basic_belief", "inferred_belief"]),
          noBeliefCycles,
          noMatchingLeftRightBelief
        ]}/>
      </div>
    </>
  );
};
 
/**
 * Reduces each BasicBelief, including its children down into its core data.
 * @param {Node} node - The BasicBelief node to reduce.
 * @param {Node[]} nodes - The list of all nodes in the current flow graph.
 * @returns A simplified object containing the node label and its list of BasicBeliefs.
 */
export function InferredBeliefReduce(node: Node, nodes: Node[]) {
  const data = node.data as InferredBeliefNodeData;
  const leftBelief = nodes.find((node) => node.id === data.inferredBelief.left);
  const rightBelief = nodes.find((node) => node.id === data.inferredBelief.right);
 
  if (!leftBelief) { throw new Error("No Left belief found")}
  if (!rightBelief) { throw new Error("No Right Belief found")}
 
  const result: Record<string, unknown> = {
    id: node.id,
    left: BeliefGlobalReduce(leftBelief, nodes),
    operator: data.inferredBelief.operator ? "AND" : "OR",
    right: BeliefGlobalReduce(rightBelief, nodes),
  };
 
  return result
}