diff --git a/src/App.css b/src/App.css
index a241d03..8e078f6 100644
--- a/src/App.css
+++ b/src/App.css
@@ -248,3 +248,11 @@ button.no-button {
text-decoration: underline;
}
}
+
+.flex-center-x {
+ display: flex;
+ justify-content: center; /* horizontal centering */
+ text-align: center; /* center multi-line text */
+ width: 100%; /* allow it to stretch */
+ flex-wrap: wrap; /* optional: let text wrap naturally */
+}
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 75d423d..e0576a2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -8,6 +8,7 @@ import VisProg from "./pages/VisProgPage/VisProg.tsx";
import {useState} from "react";
import Logging from "./components/Logging/Logging.tsx";
+
function App(){
const [showLogs, setShowLogs] = useState(false);
diff --git a/src/components/components.tsx b/src/components/components.tsx
index 7ee7f0d..a89642a 100644
--- a/src/components/components.tsx
+++ b/src/components/components.tsx
@@ -1,25 +1,113 @@
-import { useState } from 'react'
+import React, { useState } from 'react';
-/**
- * A minimal counter component that demonstrates basic React state handling.
- *
- * Maintains an internal count value and provides buttons to increment and reset it.
- *
- * @returns A JSX element rendering the counter UI.
- */
-function Counter() {
- /** The current counter value. */
- const [count, setCount] = useState(0)
+export default function Counter() {
+ const [customText, setCustomText] = useState("");
+ const [selectedGesture, setSelectedGesture] = useState("animations/Stand/BodyTalk/Speaking/BodyTalk_1");
+
+ // Reusable helper to talk to the backend
+ const send = async (type: string, context: string) => {
+ try {
+ await fetch("http://localhost:8000/button_pressed", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ type, context }),
+ });
+ // Backticks restored here:
+ console.log(`Sent ${type}: ${context}`);
+ } catch (err) {
+ // Backticks restored here:
+ console.error(`Error sending ${type}:`, err);
+ }
+ };
+
+ function list_generator(header: string, items: string[]) {
+ return (
+
+ {header}
+
+ {items.map((item, index) => (
+
+ {item}
+
+
+ ))}
+
+
+ );
+ }
return (
-
-
-
+
+
+
+ setCustomText(e.target.value)}
+ placeholder="Type something for Pepper to say..."
+ style={{ padding: '8px', flex: 1 }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {list_generator("Goals to complete:", ["Greet user", "Introduce self", "Ask for name"])}
+ {list_generator("Conditional norms:", ["If {user_tired}, be less energetic", "If {play_mundo}, go where you please"])}
+ {list_generator("Activate triggers:", ["If {user_thirsty}, offer drink", "If {name == Karen}, be triggered"])}
+
- )
-}
-export default Counter
\ No newline at end of file
+ );
+}
\ No newline at end of file
diff --git a/src/pages/MonitoringPage/Components.tsx b/src/pages/MonitoringPage/Components.tsx
index 5dc05f6..64b5761 100644
--- a/src/pages/MonitoringPage/Components.tsx
+++ b/src/pages/MonitoringPage/Components.tsx
@@ -3,7 +3,6 @@ import styles from './MonitoringPage.module.css';
/**
* HELPER: Unified sender function
- * In a real app, you might move this to a /services or /hooks folder
*/
const sendUserInterrupt = async (type: string, context: string) => {
try {
diff --git a/src/pages/MonitoringPage/MonitoringPage.tsx b/src/pages/MonitoringPage/MonitoringPage.tsx
index 6d985a1..890131f 100644
--- a/src/pages/MonitoringPage/MonitoringPage.tsx
+++ b/src/pages/MonitoringPage/MonitoringPage.tsx
@@ -1,14 +1,13 @@
-import React from 'react';
+import React, { use } from 'react';
import styles from './MonitoringPage.module.css';
import useProgramStore from "../../utils/programStore.ts";
import { GestureControls, SpeechPresets, DirectSpeechInput, StatusList } from './Components';
-import { nextPhase } from ".//MonitoringPageAPI.ts"
+import { nextPhase, useExperimentLogger } from ".//MonitoringPageAPI.ts"
type Goal = { id?: string | number; description?: string; achieved?: boolean };
type Trigger = { id?: string | number; label?: string ; achieved?: boolean };
type Norm = { id?: string | number; norm?: string };
-
const MonitoringPage: React.FC = () => {
const getPhaseIds = useProgramStore((s) => s.getPhaseIds);
const getNormsInPhase = useProgramStore((s) => s.getNormsInPhase);
@@ -43,7 +42,7 @@ const MonitoringPage: React.FC = () => {
setLoading(false);
}
};
-
+ useExperimentLogger();
return (
{/* HEADER */}
diff --git a/src/pages/MonitoringPage/MonitoringPageAPI.ts b/src/pages/MonitoringPage/MonitoringPageAPI.ts
index d08f78c..33f14dd 100644
--- a/src/pages/MonitoringPage/MonitoringPageAPI.ts
+++ b/src/pages/MonitoringPage/MonitoringPageAPI.ts
@@ -1,4 +1,5 @@
-const API_BASE = "http://localhost:8000/"; // Change depending on Pims interup agent/ correct endpoint
+import React, { useEffect } from 'react';
+const API_BASE = "http://localhost:8000"; // Change depending on Pims interup agent/ correct endpoint
/**
@@ -16,4 +17,37 @@ export async function nextPhase(): Promise {
if (!res.ok) {
throw new Error("Failed to advance to next phase");
}
+}
+
+/**
+ * A hook that listens to the experiment stream and logs data to the console.
+ * It does not render anything.
+ */
+export function useExperimentLogger() {
+ useEffect(() => {
+ console.log("Starting Experiment Stream listener...");
+
+ const eventSource = new EventSource(`${API_BASE}/experiment_stream`);
+
+ eventSource.onmessage = (event) => {
+ try {
+ const parsedData = JSON.parse(event.data);
+
+ console.log(" [Experiment Update Received] ", parsedData);
+
+ } catch (err) {
+ console.warn("Received non-JSON experiment data:", event.data);
+ }
+ };
+
+ eventSource.onerror = (err) => {
+ console.error("SSE Connection Error (Experiment Stream):", err);
+ eventSource.close();
+ };
+
+ return () => {
+ console.log("Closing Experiment Stream listener...");
+ eventSource.close();
+ };
+ }, []);
}
\ No newline at end of file
diff --git a/src/pages/VisProgPage/VisProg.module.css b/src/pages/VisProgPage/VisProg.module.css
index 5f2aa78..14619c5 100644
--- a/src/pages/VisProgPage/VisProg.module.css
+++ b/src/pages/VisProgPage/VisProg.module.css
@@ -71,6 +71,11 @@
filter: drop-shadow(0 0 0.25rem red);
}
+.node-basic_belief {
+ outline: plum solid 2pt;
+ filter: drop-shadow(0 0 0.25rem plum);
+}
+
.draggable-node {
padding: 3px 10px;
background-color: canvas;
@@ -126,3 +131,11 @@
outline: red solid 2pt;
filter: drop-shadow(0 0 0.25rem red);
}
+
+.draggable-node-basic_belief {
+ padding: 3px 10px;
+ background-color: canvas;
+ border-radius: 5pt;
+ outline: plum solid 2pt;
+ filter: drop-shadow(0 0 0.25rem plum);
+}
\ No newline at end of file
diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx
index f1b5f30..2b91413 100644
--- a/src/pages/VisProgPage/VisProg.tsx
+++ b/src/pages/VisProgPage/VisProg.tsx
@@ -9,8 +9,10 @@ import {
import '@xyflow/react/dist/style.css';
import {useEffect, useState} from "react";
import {useShallow} from 'zustand/react/shallow';
+import orderPhaseNodeArray from "../../utils/orderPhaseNodes.ts";
import useProgramStore from "../../utils/programStore.ts";
import {DndToolbar} from './visualProgrammingUI/components/DragDropSidebar.tsx';
+import type {PhaseNode} from "./visualProgrammingUI/nodes/PhaseNode.tsx";
import useFlowStore from './visualProgrammingUI/VisProgStores.tsx';
import type {FlowState} from './visualProgrammingUI/VisProgTypes.tsx';
import styles from './VisProg.module.css'
@@ -167,14 +169,15 @@ function runProgramm() {
*/
function graphReducer() {
const { nodes } = useFlowStore.getState();
- return nodes
- .filter((n) => n.type == 'phase')
+ return orderPhaseNodeArray(nodes.filter((n) => n.type == 'phase') as PhaseNode [])
.map((n) => {
const reducer = NodeReduces['phase'];
return reducer(n, nodes)
});
}
+
+
/**
* houses the entire page, so also UI elements
* that are not a part of the Visual Programming UI
diff --git a/src/pages/VisProgPage/visualProgrammingUI/EditorUndoRedo.ts b/src/pages/VisProgPage/visualProgrammingUI/EditorUndoRedo.ts
index 70c4c01..6ad705d 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/EditorUndoRedo.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/EditorUndoRedo.ts
@@ -39,10 +39,10 @@ export const UndoRedo = (
* @param {BaseFlowState} state - the current state of the editor
* @returns {FlowSnapshot} - returns a snapshot of the current editor state
*/
- const getSnapshot = (state : BaseFlowState) : FlowSnapshot => ({
+ const getSnapshot = (state : BaseFlowState) : FlowSnapshot => (structuredClone({
nodes: state.nodes,
edges: state.edges
- });
+ }));
const initialState = config(set, get, api);
diff --git a/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts b/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts
index 77a835d..023440c 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts
@@ -46,6 +46,8 @@ import TriggerNode, {
TriggerReduce
} from "./nodes/TriggerNode";
import { TriggerNodeDefaults } from "./nodes/TriggerNode.default";
+import BasicBeliefNode, { BasicBeliefConnectionSource, BasicBeliefConnectionTarget, BasicBeliefDisconnectionSource, BasicBeliefDisconnectionTarget, BasicBeliefReduce } from "./nodes/BasicBeliefNode";
+import { BasicBeliefNodeDefaults } from "./nodes/BasicBeliefNode.default";
/**
* Registered node types in the visual programming system.
@@ -60,6 +62,7 @@ export const NodeTypes = {
norm: NormNode,
goal: GoalNode,
trigger: TriggerNode,
+ basic_belief: BasicBeliefNode,
};
/**
@@ -74,6 +77,7 @@ export const NodeDefaults = {
norm: NormNodeDefaults,
goal: GoalNodeDefaults,
trigger: TriggerNodeDefaults,
+ basic_belief: BasicBeliefNodeDefaults,
};
@@ -90,6 +94,7 @@ export const NodeReduces = {
norm: NormReduce,
goal: GoalReduce,
trigger: TriggerReduce,
+ basic_belief: BasicBeliefReduce,
}
@@ -107,6 +112,7 @@ export const NodeConnections = {
norm: NormConnectionTarget,
goal: GoalConnectionTarget,
trigger: TriggerConnectionTarget,
+ basic_belief: BasicBeliefConnectionTarget,
},
Sources: {
start: StartConnectionSource,
@@ -115,6 +121,7 @@ export const NodeConnections = {
norm: NormConnectionSource,
goal: GoalConnectionSource,
trigger: TriggerConnectionSource,
+ basic_belief: BasicBeliefConnectionSource
}
}
@@ -132,6 +139,7 @@ export const NodeDisconnections = {
norm: NormDisconnectionTarget,
goal: GoalDisconnectionTarget,
trigger: TriggerDisconnectionTarget,
+ basic_belief: BasicBeliefDisconnectionTarget,
},
Sources: {
start: StartDisconnectionSource,
@@ -140,6 +148,7 @@ export const NodeDisconnections = {
norm: NormDisconnectionSource,
goal: GoalDisconnectionSource,
trigger: TriggerDisconnectionSource,
+ basic_belief: BasicBeliefDisconnectionSource,
},
}
@@ -151,7 +160,6 @@ export const NodeDisconnections = {
export const NodeDeletes = {
start: () => false,
end: () => false,
- test: () => false, // Used for coverage of universal/ undefined nodes
}
/**
@@ -164,5 +172,5 @@ export const NodesInPhase = {
start: () => false,
end: () => false,
phase: () => false,
- test: () => false, // Used for coverage of universal/ undefined nodes
+ basic_belief: () => false,
}
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
index 93af838..74d5a1a 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx
@@ -28,31 +28,31 @@ import { UndoRedo } from "./EditorUndoRedo.ts";
* @param deletable - Optional flag to indicate if the node can be deleted (can be deleted by default).
* @returns A fully initialized Node object ready to be added to the flow.
*/
-function createNode(id: string, type: string, position: XYPosition, data: Record, deletable? : boolean) {
- const defaultData = NodeDefaults[type as keyof typeof NodeDefaults]
- const newData = {
- id: id,
- type: type,
- position: position,
- data: data,
- deletable: deletable,
+function createNode(id: string, type: string, position: XYPosition, data: Record, deletable?: boolean) {
+ const defaultData = NodeDefaults[type as keyof typeof NodeDefaults]
+ return {
+ id,
+ type,
+ position,
+ deletable,
+ data: {
+ ...JSON.parse(JSON.stringify(defaultData)),
+ ...data,
+ },
+ }
}
- return {...defaultData, ...newData}
-}
+
//* Initial nodes, created by using createNode. */
const initialNodes : Node[] = [
createNode('start', 'start', {x: 100, y: 100}, {label: "Start"}, false),
createNode('end', 'end', {x: 500, y: 100}, {label: "End"}, false),
- createNode('phase-1', 'phase', {x:200, y:100}, {label: "Phase 1", children : []}),
- createNode('norms-1', 'norm', {x:-200, y:100}, {label: "Initial Norms", normList: ["Be a robot", "get good"], critical:false}),
+ createNode(crypto.randomUUID(), 'phase', {x:200, y:100}, {label: "Phase 1", children : []}),
+ createNode('norms-1', 'norm', {x:-200, y:100}, {label: "Initial Norms", normList: ["Be a robot", "get good"], critical:false}),
];
// * Initial edges * /
-const initialEdges: Edge[] = [
- { id: 'start-phase-1', source: 'start', target: 'phase-1' },
- { id: 'phase-1-end', source: 'phase-1', target: 'end' },
-];
+const initialEdges: Edge[] = []; // no initial edges as edge connect events don't fire when using initial edges
/**
diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/CustomNodeHandles.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/CustomNodeHandles.tsx
new file mode 100644
index 0000000..853c488
--- /dev/null
+++ b/src/pages/VisProgPage/visualProgrammingUI/components/CustomNodeHandles.tsx
@@ -0,0 +1,30 @@
+import {
+ Handle,
+ useNodeConnections,
+ type HandleType,
+ type Position
+} from '@xyflow/react';
+
+
+const LimitedConnectionCountHandle = (props: {
+ node_id: string,
+ type: HandleType,
+ position: Position,
+ connection_count: number,
+ id?: string
+}) => {
+ const connections = useNodeConnections({
+ id: props.node_id,
+ handleType: props.type,
+ handleId: props.id,
+ });
+
+ return (
+
+ );
+};
+
+export default LimitedConnectionCountHandle;
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.default.ts b/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.default.ts
new file mode 100644
index 0000000..72066c4
--- /dev/null
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.default.ts
@@ -0,0 +1,12 @@
+import type { BasicBeliefNodeData } from "./BasicBeliefNode";
+
+
+/**
+ * Default data for this node
+ */
+export const BasicBeliefNodeDefaults: BasicBeliefNodeData = {
+ label: "Belief",
+ droppable: true,
+ belief: {type: "keyword", id: "help", value: "help", label: "Keyword said:"},
+ hasReduce: true,
+};
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx
new file mode 100644
index 0000000..821569f
--- /dev/null
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx
@@ -0,0 +1,193 @@
+import {
+ Handle,
+ type NodeProps,
+ Position,
+ type Node,
+} from '@xyflow/react';
+import { Toolbar } from '../components/NodeComponents';
+import styles from '../../VisProg.module.css';
+import useFlowStore from '../VisProgStores';
+import { TextField } from '../../../../components/TextField';
+
+/**
+ * The default data structure for a BasicBelief node
+ *
+ * Represents configuration for a node that activates when a specific condition is met,
+ * such as keywords being spoken or emotions detected.
+ *
+ * @property label: the display label of this BasicBelief node.
+ * @property droppable: Whether this node can be dropped from the toolbar (default: true).
+ * @property BasicBeliefType - The type of BasicBelief ("keywords" or a custom string).
+ * @property BasicBeliefs - The list of keyword BasicBeliefs (if applicable).
+ * @property hasReduce - Whether this node supports reduction logic.
+ */
+export type BasicBeliefNodeData = {
+ label: string;
+ droppable: boolean;
+ belief: BasicBeliefType;
+ hasReduce: boolean;
+};
+
+// These are all the types a basic belief could be.
+type BasicBeliefType = Keyword | Semantic | DetectedObject | Emotion
+type Keyword = { type: "keyword", id: string, value: string, label: "Keyword said:"};
+type Semantic = { type: "semantic", id: string, value: string, label: "Detected with LLM:"};
+type DetectedObject = { type: "object", id: string, value: string, label: "Object found:"};
+type Emotion = { type: "emotion", id: string, value: string, label: "Emotion recognised:"};
+
+export type BasicBeliefNode = Node
+
+
+/**
+ * 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 BasicBeliefConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
+ // no additional connection logic exists yet
+}
+
+/**
+ * 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 BasicBeliefConnectionSource(_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 BasicBeliefDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
+ // no additional connection logic exists yet
+}
+
+/**
+ * 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 BasicBeliefDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
+ // no additional connection logic exists yet
+}
+
+/**
+ * Defines how a BasicBelief node should be rendered
+ * @param props - Node properties provided by React Flow, including `id` and `data`.
+ * @returns The rendered BasicBeliefNode React element (React.JSX.Element).
+ */
+export default function BasicBeliefNode(props: NodeProps) {
+ const data = props.data;
+ const {updateNodeData} = useFlowStore();
+ const updateValue = (value: string) => updateNodeData(props.id, {...data, belief: {...data.belief, value: value}});
+ const label_input_id = `basic_belief_${props.id}_label_input`;
+
+ type BeliefString = BasicBeliefType["type"];
+
+ function updateBeliefType(newType: BeliefString) {
+ updateNodeData(props.id, {
+ ...data,
+ belief: {
+ ...data.belief,
+ type: newType,
+ value:
+ newType === "emotion"
+ ? emotionOptions[0]
+ : data.belief.value,
+ },
+ });
+ }
+
+
+ // Use this
+ const emotionOptions = ["Happy", "Angry", "Sad", "Cheerful"]
+
+
+ let placeholder = ""
+ let wrapping = ""
+ switch (props.data.belief.type) {
+ case ("keyword"):
+ placeholder = "keyword..."
+ wrapping = '"'
+ break;
+ case ("semantic"):
+ placeholder = "word..."
+ wrapping = '"'
+ break;
+ case ("object"):
+ placeholder = "object..."
+ break;
+ case ("emotion"):
+ // TODO: emotion should probably be a drop-down menu rather than a string
+ // So this placeholder won't hold for always
+ placeholder = "emotion..."
+ break;
+ default:
+ break;
+ }
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+/**
+ * Reduces each BasicBelief, including its children down into its core data.
+ * @param node - The BasicBelief node to reduce.
+ * @param _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 BasicBeliefReduce(node: Node, _nodes: Node[]) {
+ const data = node.data as BasicBeliefNodeData;
+ return {
+ id: node.id,
+ type: data.belief.type,
+ value: data.belief.value
+ }
+}
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx
index 57db571..116dc01 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/EndNode.tsx
@@ -1,9 +1,9 @@
import {
- Handle,
type NodeProps,
Position,
type Node,
} from '@xyflow/react';
+import LimitedConnectionCountHandle from "../components/CustomNodeHandles.tsx";
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
@@ -32,7 +32,13 @@ export default function EndNode(props: NodeProps) {
End
-
+
>
);
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts
index 4b4a3ed..8df25cc 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.default.ts
@@ -6,6 +6,7 @@ import type { NormNodeData } from "./NormNode";
export const NormNodeDefaults: NormNodeData = {
label: "Norm Node",
droppable: true,
+ conditions: [],
norm: "",
hasReduce: true,
critical: false,
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx
index 8f619a5..4e94834 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx
@@ -8,6 +8,7 @@ import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
import { TextField } from '../../../../components/TextField';
import useFlowStore from '../VisProgStores';
+import { BasicBeliefReduce } from './BasicBeliefNode';
/**
* The default data dot a phase node
@@ -19,6 +20,7 @@ import useFlowStore from '../VisProgStores';
export type NormNodeData = {
label: string;
droppable: boolean;
+ conditions: string[]; // List of (basic) belief nodes' ids.
norm: string;
hasReduce: boolean;
critical: boolean;
@@ -67,7 +69,14 @@ export default function NormNode(props: NodeProps) {
onChange={(e) => setCritical(e.target.checked)}
/>
+
+ {data.conditions.length > 0 && (
+
+
)}
+
+
+
>;
};
@@ -78,14 +87,29 @@ export default function NormNode(props: NodeProps) {
* @param node The Node Properties of this node.
* @param _nodes all the nodes in the graph
*/
-export function NormReduce(node: Node, _nodes: Node[]) {
+export function NormReduce(node: Node, nodes: Node[]) {
const data = node.data as NormNodeData;
- return {
- id: node.id,
- label: data.label,
- norm: data.norm,
- critical: data.critical,
- }
+
+ // conditions nodes - make sure to check for empty arrays
+ let conditionNodes: Node[] = [];
+ if (data.conditions)
+ conditionNodes = nodes.filter((node) => data.conditions.includes(node.id));
+
+ // Build the result object
+ const result: Record = {
+ id: node.id,
+ label: data.label,
+ norm: data.norm,
+ critical: data.critical,
+ };
+
+ // Go over our conditionNodes. They should either be Basic (OR TODO: Inferred)
+ const reducer = BasicBeliefReduce;
+ result["basic_beliefs"] = conditionNodes.map((condition) => reducer(condition, nodes))
+
+ // When the Inferred is being implemented, you should follow the same kind of structure that PhaseNode has,
+ // dividing the conditions into basic and inferred, then calling the correct reducer on them.
+ return result
}
/**
@@ -94,7 +118,11 @@ export function NormReduce(node: Node, _nodes: Node[]) {
* @param _sourceNodeId the source of the received connection
*/
export function NormConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
- // no additional connection logic exists yet
+ const data = _thisNode.data as NormNodeData;
+ // If we got a belief connected, this is a condition for the norm.
+ if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'basic_belief' /* TODO: Add the option for an inferred belief */))) {
+ data.conditions.push(_sourceNodeId);
+ }
}
/**
@@ -112,7 +140,11 @@ export function NormConnectionSource(_thisNode: Node, _targetNodeId: string) {
* @param _sourceNodeId the source of the disconnected connection
*/
export function NormDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
- // no additional connection logic exists yet
+ const data = _thisNode.data as NormNodeData;
+ // If we got a belief connected, this is a condition for the norm.
+ if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'basic_belief' /* TODO: Add the option for an inferred belief */))) {
+ data.conditions = data.conditions.filter(id => id != _sourceNodeId);
+ }
}
/**
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.default.ts b/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.default.ts
index 0a96d6b..73697eb 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.default.ts
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.default.ts
@@ -8,4 +8,6 @@ export const PhaseNodeDefaults: PhaseNodeData = {
droppable: true,
children: [],
hasReduce: true,
+ nextPhaseId: null,
+ isFirstPhase: false,
};
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx
index 41679f1..9e1fb24 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/PhaseNode.tsx
@@ -9,6 +9,7 @@ import styles from '../../VisProg.module.css';
import { NodeReduces, NodesInPhase, NodeTypes} from '../NodeRegistry';
import useFlowStore from '../VisProgStores';
import { TextField } from '../../../../components/TextField';
+import LimitedConnectionCountHandle from "../components/CustomNodeHandles.tsx";
/**
* The default data dot a phase node
@@ -16,12 +17,15 @@ import { TextField } from '../../../../components/TextField';
* @param droppable: whether this node is droppable from the drop bar (initialized as true)
* @param children: ID's of children of this node
* @param hasReduce: whether this node has reducing functionality (true by default)
+ * @param nextPhaseId:
*/
export type PhaseNodeData = {
label: string;
droppable: boolean;
children: string[];
hasReduce: boolean;
+ nextPhaseId: string | "end" | null;
+ isFirstPhase: boolean;
};
export type PhaseNode = Node
@@ -50,9 +54,21 @@ export default function PhaseNode(props: NodeProps) {
placeholder={"Phase ..."}
/>
-
+
-
+
>
);
@@ -65,8 +81,8 @@ export default function PhaseNode(props: NodeProps) {
* @returns A collection of all reduced nodes in this phase, starting with this phases' reduced data.
*/
export function PhaseReduce(node: Node, nodes: Node[]) {
- const thisnode = node as PhaseNode;
- const data = thisnode.data as PhaseNodeData;
+ const thisNode = node as PhaseNode;
+ const data = thisNode.data as PhaseNodeData;
// node typings that are not in phase
const nodesNotInPhase: string[] = Object.entries(NodesInPhase)
@@ -85,8 +101,8 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
// Build the result object
const result: Record = {
- id: thisnode.id,
- label: data.label,
+ id: thisNode.id,
+ label: data.label,
};
nodesInPhase.forEach((type) => {
@@ -109,13 +125,19 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
* @param _sourceNodeId the source of the received connection
*/
export function PhaseConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
- const node = _thisNode as PhaseNode
- const data = node.data as PhaseNodeData
- // we only add none phase nodes to the children
- if (!(useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && node.type === 'phase'))) {
- data.children.push(_sourceNodeId)
- }
+ const data = _thisNode.data as PhaseNodeData
+ const nodes = useFlowStore.getState().nodes;
+ const sourceNode = nodes.find((node) => node.id === _sourceNodeId)!
+ switch (sourceNode.type) {
+ case "phase": break;
+ case "start": data.isFirstPhase = true; break;
+ // we only add none phase or start nodes to the children
+ // endNodes cannot be the source of an outgoing connection
+ // so we don't need to cover them with a special case
+ // before handling the default behavior
+ default: data.children.push(_sourceNodeId); break;
+ }
}
/**
@@ -124,7 +146,19 @@ export function PhaseConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
* @param _targetNodeId the target of the created connection
*/
export function PhaseConnectionSource(_thisNode: Node, _targetNodeId: string) {
- // no additional connection logic exists yet
+ const data = _thisNode.data as PhaseNodeData
+ const nodes = useFlowStore.getState().nodes;
+
+ const targetNode = nodes.find((node) => node.id === _targetNodeId)
+ if (!targetNode) {throw new Error("Source node not found")}
+
+ // we set the nextPhaseId to the next target's id if the target is a phaseNode,
+ // or "end" if the target node is the end node
+ switch (targetNode.type) {
+ case 'phase': data.nextPhaseId = _targetNodeId; break;
+ case 'end': data.nextPhaseId = "end"; break;
+ default: break;
+ }
}
/**
@@ -133,9 +167,23 @@ export function PhaseConnectionSource(_thisNode: Node, _targetNodeId: string) {
* @param _sourceNodeId the source of the disconnected connection
*/
export function PhaseDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
- const node = _thisNode as PhaseNode
- const data = node.data as PhaseNodeData
- data.children = data.children.filter((child) => { if (child != _sourceNodeId) return child; });
+ const data = _thisNode.data as PhaseNodeData
+
+ const nodes = useFlowStore.getState().nodes;
+ const sourceNode = nodes.find((node) => node.id === _sourceNodeId)
+ const sourceType = sourceNode ? sourceNode.type : "deleted";
+ switch (sourceType) {
+ case "phase": break;
+ case "start": data.isFirstPhase = false; break;
+ // we only add none phase or start nodes to the children
+ // endNodes cannot be the source of an outgoing connection
+ // so we don't need to cover them with a special case
+ // before handling the default behavior
+ default:
+ data.children = data.children.filter((child) => { if (child != _sourceNodeId) return child; });
+ break;
+ }
+
}
/**
@@ -144,5 +192,12 @@ export function PhaseDisconnectionTarget(_thisNode: Node, _sourceNodeId: string)
* @param _targetNodeId the target of the diconnected connection
*/
export function PhaseDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
- // no additional connection logic exists yet
+ const data = _thisNode.data as PhaseNodeData
+ const nodes = useFlowStore.getState().nodes;
+
+ // if the target is a phase or end node set the nextPhaseId to null,
+ // as we are no longer connected to a subsequent phaseNode or to the endNode
+ if (nodes.some((node) => node.id === _targetNodeId && ['phase', 'end'].includes(node.type!))){
+ data.nextPhaseId = null;
+ }
}
\ No newline at end of file
diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
index 92ca6ed..13f3fc8 100644
--- a/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
+++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/StartNode.tsx
@@ -1,9 +1,9 @@
import {
- Handle,
type NodeProps,
Position,
type Node,
} from '@xyflow/react';
+import LimitedConnectionCountHandle from "../components/CustomNodeHandles.tsx";
import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
@@ -31,7 +31,13 @@ export default function StartNode(props: NodeProps) {