feat: automatic addition of goals to a current goal node, adding it to the plan, making sure the data stays correct. Also for the trigger nodes. :)

ref: N25B-434
This commit is contained in:
Björn Otgaar
2026-01-07 17:55:54 +01:00
parent e805c882fe
commit a4428c0d67
8 changed files with 137 additions and 66 deletions

View File

@@ -7,11 +7,12 @@ import { Toolbar } from '../components/NodeComponents';
import styles from '../../VisProg.module.css';
import { TextField } from '../../../../components/TextField';
import {MultiConnectionHandle} from "../components/RuleBasedHandle.tsx";
import {allowOnlyConnectionsFromHandle} from "../HandleRules.ts";
import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from '../VisProgStores';
import { DoesPlanIterate, PlanReduce, type Plan } from '../components/Plan';
import { deleteGoalInPlanByID, DoesPlanIterate, insertGoalInPlan, PlanReduce, type Plan, type PlanElement } from '../components/Plan';
import PlanEditorDialog from '../components/PlanEditor';
import { MultilineTextField } from '../../../../components/MultilineTextField';
import { defaultPlan } from '../components/Plan.default.ts';
/**
* The default data dot a phase node
@@ -115,6 +116,10 @@ export default function GoalNode({id, data}: NodeProps<GoalNode>) {
<MultiConnectionHandle type="source" position={Position.Right} id="GoalSource" rules={[
allowOnlyConnectionsFromHandle([{nodeType:"phase",handleId:"data"}]),
]}/>
<MultiConnectionHandle type="target" position={Position.Bottom} id="GoalTarget" rules={[allowOnlyConnectionsFromType(["goal"])]}/>
</div>
</>;
}
@@ -132,7 +137,7 @@ export function GoalReduce(node: Node, _nodes: Node[]) {
name: data.name,
description: data.description,
can_fail: data.can_fail,
plan: data.plan ? PlanReduce(data.plan) : "",
plan: data.plan ? PlanReduce(_nodes, data.plan) : "",
}
}
@@ -142,7 +147,22 @@ export function GoalReduce(node: Node, _nodes: Node[]) {
* @param _sourceNodeId the source of the received connection
*/
export function GoalConnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
// Goals should only be targeted by other goals, for them to be part of our plan.
const nodes = useFlowStore.getState().nodes;
const otherNode = nodes.find((x) => x.id === _sourceNodeId)
if (!otherNode || otherNode.type !== "goal") return;
const data = _thisNode.data as GoalNodeData
// First, let's see if we have a plan currently. If not, let's create a default plan with this goal inside.:)
if (!data.plan) {
data.plan = insertGoalInPlan({...structuredClone(defaultPlan), id: crypto.randomUUID()} as Plan, otherNode as GoalNode)
}
// Else, lets just insert this goal into our current plan.
else {
data.plan = insertGoalInPlan(structuredClone(data.plan), otherNode as GoalNode)
}
}
/**
@@ -160,7 +180,9 @@ export function GoalConnectionSource(_thisNode: Node, _targetNodeId: string) {
* @param _sourceNodeId the source of the disconnected connection
*/
export function GoalDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
// no additional connection logic exists yet
// We should probably check if our disconnection was by a goal, since it would mean we have to remove it from our plan list.
const data = _thisNode.data as GoalNodeData
data.plan = deleteGoalInPlanByID(structuredClone(data.plan) as Plan, _sourceNodeId)
}
/**

View File

@@ -108,7 +108,10 @@ export function PhaseReduce(node: Node, nodes: Node[]) {
console.warn(`No reducer found for node type ${type}`);
result[type + "s"] = [];
} else {
result[type + "s"] = typedChildren.map((child) => reducer(child, nodes));
result[type + "s"] = [];
for (const typedChild of typedChildren) {
(result[type + "s"] as object[]).push(reducer(typedChild, nodes))
}
}
});

View File

@@ -10,9 +10,11 @@ import styles from '../../VisProg.module.css';
import {MultiConnectionHandle, SingleConnectionHandle} from "../components/RuleBasedHandle.tsx";
import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts";
import useFlowStore from '../VisProgStores';
import { PlanReduce, type Plan } from '../components/Plan';
import { deleteGoalInPlanByID, insertGoalInPlan, PlanReduce, type Plan } from '../components/Plan';
import PlanEditorDialog from '../components/PlanEditor';
import { BasicBeliefReduce } from './BasicBeliefNode';
import type { GoalNode } from './GoalNode.tsx';
import { defaultPlan } from '../components/Plan.default.ts';
/**
* The default data structure for a Trigger node
@@ -65,10 +67,25 @@ export default function TriggerNode(props: NodeProps<TriggerNode>) {
<MultiConnectionHandle type="source" position={Position.Right} id="TriggerSource" rules={[
allowOnlyConnectionsFromHandle([{nodeType:"phase",handleId:"data"}]),
]}/>
<SingleConnectionHandle type="target" position={Position.Bottom} id="beliefs" rules={[
allowOnlyConnectionsFromType(["basic_belief"])
]}/>
<SingleConnectionHandle
type="target"
position={Position.Bottom}
id="beliefs"
style={{ left: '40%' }}
rules={[
allowOnlyConnectionsFromType(['basic_belief']),
]}
/>
<MultiConnectionHandle
type="target"
position={Position.Bottom}
id="GoalTarget"
style={{ left: '60%' }}
rules={[
allowOnlyConnectionsFromType(['goal']),
]}
/>
<PlanEditorDialog
plan={data.plan}
@@ -96,7 +113,7 @@ export function TriggerReduce(node: Node, nodes: Node[]) {
return {
id: node.id,
condition: conditionData, // Make sure we have a condition before reducing, or default to ""
plan: !data.plan ? "" : PlanReduce(data.plan), // Make sure we have a plan when reducing, or default to ""
plan: !data.plan ? "" : PlanReduce(nodes, data.plan), // Make sure we have a plan when reducing, or default to ""
}
}
@@ -110,9 +127,25 @@ 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 */))) {
const nodes = useFlowStore.getState().nodes;
const otherNode = nodes.find((x) => x.id === _sourceNodeId)
if (!otherNode) return;
if (otherNode.type === 'basic_belief' /* TODO: Add the option for an inferred belief */) {
data.condition = _sourceNodeId;
}
else if (otherNode.type === 'goal') {
// First, let's see if we have a plan currently. If not, let's create a default plan with this goal inside.:)
if (!data.plan) {
data.plan = insertGoalInPlan({...structuredClone(defaultPlan), id: crypto.randomUUID()} as Plan, otherNode as GoalNode)
}
// Else, lets just insert this goal into our current plan.
else {
data.plan = insertGoalInPlan(structuredClone(data.plan), otherNode as GoalNode)
}
}
}
/**
@@ -134,6 +167,8 @@ export function TriggerDisconnectionTarget(_thisNode: Node, _sourceNodeId: strin
const data = _thisNode.data as TriggerNodeData;
// remove if the target of disconnection was our condition
if (_sourceNodeId == data.condition) data.condition = undefined
data.plan = deleteGoalInPlanByID(structuredClone(data.plan) as Plan, _sourceNodeId)
}
/**