diff --git a/src/pages/VisProgPage/VisProg.module.css b/src/pages/VisProgPage/VisProg.module.css index 429e740..781f3be 100644 --- a/src/pages/VisProgPage/VisProg.module.css +++ b/src/pages/VisProgPage/VisProg.module.css @@ -76,6 +76,11 @@ filter: drop-shadow(0 0 0.25rem plum); } +.node-inferred_belief { + outline: mediumpurple solid 2pt; + filter: drop-shadow(0 0 0.25rem mediumpurple); +} + .draggable-node { padding: 3px 10px; background-color: canvas; @@ -140,8 +145,16 @@ filter: drop-shadow(0 0 0.25rem plum); } +.draggable-node-inferred_belief { + padding: 3px 10px; + background-color: canvas; + border-radius: 5pt; + outline: mediumpurple solid 2pt; + filter: drop-shadow(0 0 0.25rem mediumpurple); +} + .planNoIterate { opacity: 0.5; font-style: italic; text-decoration: line-through; -} \ No newline at end of file +} diff --git a/src/pages/VisProgPage/VisProg.tsx b/src/pages/VisProgPage/VisProg.tsx index 486cc7f..fb192fc 100644 --- a/src/pages/VisProgPage/VisProg.tsx +++ b/src/pages/VisProgPage/VisProg.tsx @@ -163,6 +163,7 @@ function runProgram() { // when the program was sent to the backend successfully: useProgramStore.getState().setProgramState(structuredClone(program)); }).catch(() => console.log("Failed to send program to the backend.")); + console.log(program); } /** diff --git a/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts b/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts index 023440c..0bd2c3d 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts +++ b/src/pages/VisProgPage/visualProgrammingUI/NodeRegistry.ts @@ -46,8 +46,22 @@ 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"; +import InferredBeliefNode, { + InferredBeliefConnectionTarget, + InferredBeliefConnectionSource, + InferredBeliefDisconnectionTarget, + InferredBeliefDisconnectionSource, + InferredBeliefReduce +} from "./nodes/InferredBeliefNode"; +import { InferredBeliefNodeDefaults } from "./nodes/InferredBeliefNode.default"; +import BasicBeliefNode, { + BasicBeliefConnectionSource, + BasicBeliefConnectionTarget, + BasicBeliefDisconnectionSource, + BasicBeliefDisconnectionTarget, + BasicBeliefReduce +} from "./nodes/BasicBeliefNode.tsx"; +import { BasicBeliefNodeDefaults } from "./nodes/BasicBeliefNode.default.ts"; /** * Registered node types in the visual programming system. @@ -63,6 +77,7 @@ export const NodeTypes = { goal: GoalNode, trigger: TriggerNode, basic_belief: BasicBeliefNode, + inferred_belief: InferredBeliefNode, }; /** @@ -78,6 +93,7 @@ export const NodeDefaults = { goal: GoalNodeDefaults, trigger: TriggerNodeDefaults, basic_belief: BasicBeliefNodeDefaults, + inferred_belief: InferredBeliefNodeDefaults, }; @@ -95,6 +111,7 @@ export const NodeReduces = { goal: GoalReduce, trigger: TriggerReduce, basic_belief: BasicBeliefReduce, + inferred_belief: InferredBeliefReduce, } @@ -113,6 +130,7 @@ export const NodeConnections = { goal: GoalConnectionTarget, trigger: TriggerConnectionTarget, basic_belief: BasicBeliefConnectionTarget, + inferred_belief: InferredBeliefConnectionTarget, }, Sources: { start: StartConnectionSource, @@ -121,7 +139,8 @@ export const NodeConnections = { norm: NormConnectionSource, goal: GoalConnectionSource, trigger: TriggerConnectionSource, - basic_belief: BasicBeliefConnectionSource + basic_belief: BasicBeliefConnectionSource, + inferred_belief: InferredBeliefConnectionSource, } } @@ -140,6 +159,7 @@ export const NodeDisconnections = { goal: GoalDisconnectionTarget, trigger: TriggerDisconnectionTarget, basic_belief: BasicBeliefDisconnectionTarget, + inferred_belief: InferredBeliefDisconnectionTarget, }, Sources: { start: StartDisconnectionSource, @@ -149,6 +169,7 @@ export const NodeDisconnections = { goal: GoalDisconnectionSource, trigger: TriggerDisconnectionSource, basic_belief: BasicBeliefDisconnectionSource, + inferred_belief: InferredBeliefDisconnectionSource, }, } @@ -173,4 +194,5 @@ export const NodesInPhase = { end: () => false, phase: () => false, basic_belief: () => false, + inferred_belief: () => false, } \ 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 index 655aaaa..01f1cfa 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.default.ts +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.default.ts @@ -1,4 +1,4 @@ -import type { BasicBeliefNodeData } from "./BasicBeliefNode"; +import type { BasicBeliefNodeData } from "./BasicBeliefNode.tsx"; /** diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx index 06df1e8..1891f6a 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx @@ -3,13 +3,13 @@ import { Position, type Node, } from '@xyflow/react'; -import { Toolbar } from '../components/NodeComponents'; +import { Toolbar } from '../components/NodeComponents.tsx'; import styles from '../../VisProg.module.css'; import {MultiConnectionHandle} from "../components/RuleBasedHandle.tsx"; import {allowOnlyConnectionsFromType} from "../HandleRules.ts"; -import useFlowStore from '../VisProgStores'; -import { TextField } from '../../../../components/TextField'; -import { MultilineTextField } from '../../../../components/MultilineTextField'; +import useFlowStore from '../VisProgStores.tsx'; +import { TextField } from '../../../../components/TextField.tsx'; +import { MultilineTextField } from '../../../../components/MultilineTextField.tsx'; /** * The default data structure for a BasicBelief node @@ -31,7 +31,7 @@ export type BasicBeliefNodeData = { }; // These are all the types a basic belief could be. -type BasicBeliefType = Keyword | Semantic | DetectedObject | Emotion +export 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, description: string, label: "Detected with LLM:"}; type DetectedObject = { type: "object", id: string, value: string, label: "Object found:"}; diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.default.ts b/src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.default.ts new file mode 100644 index 0000000..ce86fb8 --- /dev/null +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.default.ts @@ -0,0 +1,17 @@ +import type { InferredBeliefNodeData } from "./InferredBeliefNode.tsx"; + + +/** + * Default data for this node + */ +export const InferredBeliefNodeDefaults: InferredBeliefNodeData = { + label: "Inferred Belief", + droppable: true, + inferredBelief: { + id: "", + left: undefined, + operator: "OR", + right: undefined + }, + hasReduce: true, +}; \ No newline at end of file diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.module.css b/src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.module.css new file mode 100644 index 0000000..2f9b7ae --- /dev/null +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.module.css @@ -0,0 +1,80 @@ +.operator-switch { + display: inline-flex; + align-items: center; + gap: 0.5em; + cursor: pointer; + font-family: sans-serif; + /* Change this font-size to scale the whole component */ + font-size: 12px; +} + +/* hide the default checkbox */ +.operator-switch input { + display: none; +} + +/* The Track */ +.switch-visual { + position: relative; + /* height is now 3x the font size */ + height: 3em; + aspect-ratio: 1 / 2; + background-color: ButtonFace; + border-radius: 2em; + transition: 0.2s; +} + +/* The Knob */ +.switch-visual::after { + content: ""; + position: absolute; + top: 0.1em; + left: 0.1em; + width: 1em; + height: 1em; + background: Canvas; + border: 0.175em solid mediumpurple; + border-radius: 50%; + transition: transform 0.2s ease-in-out, border-color 0.2s; +} + +/* Labels */ +.switch-labels { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 3em; /* Matches the track height */ + font-weight: 800; + color: Canvas; + line-height: 1.4; + padding: 0.2em 0; +} + +.operator-switch input:checked + .switch-visual::after { + /* Moves the slider down */ + transform: translateY(1.4em); +} + +/*change the colours to highlight the selected operator*/ +.operator-switch input:checked ~ .switch-labels{ + :first-child { + transition: ease-in-out color 0.2s; + color: ButtonFace; + } + :last-child { + transition: ease-in-out color 0.2s; + color: mediumpurple; + } +} + +.operator-switch input:not(:checked) ~ .switch-labels{ + :first-child { + transition: ease-in-out color 0.2s; + color: mediumpurple; + } + :last-child { + transition: ease-in-out color 0.2s; + color: ButtonFace; + } + +} \ No newline at end of file diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.tsx new file mode 100644 index 0000000..7cbf06f --- /dev/null +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/InferredBeliefNode.tsx @@ -0,0 +1,153 @@ +import { + type NodeProps, + Position, + type Node, +} from '@xyflow/react'; +import {useState} from "react"; +import { Toolbar } from '../components/NodeComponents.tsx'; +import styles from '../../VisProg.module.css'; +import switchStyles from './InferredBeliefNode.module.css'; +import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx"; +import {allowOnlyConnectionsFromType} from "../HandleRules.ts"; +import useFlowStore from "../VisProgStores.tsx"; +import type {BasicBeliefType} from "./BasicBeliefNode.tsx"; + + + +/** + * The default data structure for an InferredBelief node + */ +export type InferredBeliefNodeData = { + label: string; + droppable: boolean; + inferredBelief: Belief; + hasReduce: boolean; +}; + +type Belief = InferredBelief | BasicBeliefType; + +type InferredBelief = { + id: string; + left: Belief | undefined, + operator: "AND" | "OR" + right: Belief | undefined +}; + +// helper validation function for InferredBelief objects +// const isValidInferredBelief = (inferredBelief: InferredBelief) : boolean => { +// return !(inferredBelief.left && inferredBelief.right); +// }; + +export type InferredBeliefNode = 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 InferredBeliefConnectionTarget(_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 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) { + // 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 InferredBeliefDisconnectionSource(_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 InferredBeliefNode(props: NodeProps) { + const data = props.data; + const { updateNodeData } = useFlowStore(); + // start of as an AND operator, true: "AND", false: "OR" + const [enforceAllBeliefs, setEnforceAllBeliefs] = useState(true); + function onToggle() { + setEnforceAllBeliefs(!enforceAllBeliefs); + updateNodeData(props.id, { + ...data, + belief: { + ...data.inferredBelief, + operator: enforceAllBeliefs ? "AND" : "OR", + } + }); + } + + // TODO define node + + return ( + <> + +
+ + + + + {/* outgoing connections */} + + + {/* incoming connections */} + + +
+ + ); +}; + +/** + * 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 InferredBeliefReduce(node: Node, _nodes: Node[]) { + //const data = node.data as InferredBeliefNodeData; + const result: Record = { + id: node.id, + }; + + // TODO define reduce + return result +} \ No newline at end of file diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx index e216b18..f2c00dc 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/NormNode.tsx @@ -9,7 +9,7 @@ import { TextField } from '../../../../components/TextField'; import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx"; import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts"; import useFlowStore from '../VisProgStores'; -import { BasicBeliefReduce } from './BasicBeliefNode'; +import { BasicBeliefReduce } from './BasicBeliefNode.tsx'; /** * The default data dot a phase node @@ -81,7 +81,7 @@ export default function NormNode(props: NodeProps) { allowOnlyConnectionsFromHandle([{nodeType:"phase",handleId:"data"}]) ]}/> ; @@ -122,7 +122,7 @@ export function NormReduce(node: Node, nodes: Node[]) { export function NormConnectionTarget(_thisNode: Node, _sourceNodeId: string) { const data = _thisNode.data as NormNodeData; // If we got a belief connected, this is the 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 */))) { + if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && ['basic_belief', 'inferred_belief'].includes(node.type!)))) { data.condition = _sourceNodeId; } } diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.tsx index f05fd87..0cee832 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/TriggerNode.tsx @@ -12,7 +12,7 @@ import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../H import useFlowStore from '../VisProgStores'; import { PlanReduce, type Plan } from '../components/Plan'; import PlanEditorDialog from '../components/PlanEditor'; -import { BasicBeliefReduce } from './BasicBeliefNode'; +import { BasicBeliefReduce } from './BasicBeliefNode.tsx'; /** * The default data structure for a Trigger node @@ -66,7 +66,7 @@ export default function TriggerNode(props: NodeProps) { allowOnlyConnectionsFromHandle([{nodeType:"phase",handleId:"data"}]), ]}/> @@ -110,7 +110,7 @@ export function TriggerConnectionTarget(_thisNode: Node, _sourceNodeId: string) // no additional connection logic exists yet const data = _thisNode.data as TriggerNodeData; // If we got a belief connected, this is the 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 */))) { + if ((useFlowStore.getState().nodes.find((node) => node.id === _sourceNodeId && ['basic_belief', 'inferred_belief'].includes(node.type!)))) { data.condition = _sourceNodeId; } } diff --git a/test/pages/visProgPage/visualProgrammingUI/nodes/BeliefNode.test.tsx b/test/pages/visProgPage/visualProgrammingUI/nodes/BeliefNode.test.tsx index 34872c9..a023769 100644 --- a/test/pages/visProgPage/visualProgrammingUI/nodes/BeliefNode.test.tsx +++ b/test/pages/visProgPage/visualProgrammingUI/nodes/BeliefNode.test.tsx @@ -3,7 +3,7 @@ import { describe, it, beforeEach } from '@jest/globals'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '../.././/./../../test-utils/test-utils'; -import BasicBeliefNode, { type BasicBeliefNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode'; +import BasicBeliefNode, { type BasicBeliefNodeData } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx'; import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores'; import type { Node } from '@xyflow/react'; import '@testing-library/jest-dom';