also moved some functions from VisProg outside VisProg.tsx into VisProgLogic.tsx so I can reuse it for the reset experiment function of monitor page Also fixed a small merge error in TriggerNodes.tsx ref: N25B-400
206 lines
7.6 KiB
TypeScript
206 lines
7.6 KiB
TypeScript
import {
|
|
type NodeProps,
|
|
Position,
|
|
type Node,
|
|
} from '@xyflow/react';
|
|
import { Toolbar } from '../components/NodeComponents';
|
|
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 PlanEditorDialog from '../components/PlanEditor';
|
|
import {BeliefGlobalReduce} from "./BeliefGlobals.ts";
|
|
import type { GoalNode } from './GoalNode.tsx';
|
|
import { defaultPlan } from '../components/Plan.default.ts';
|
|
import { deleteGoalInPlanByID, insertGoalInPlan } from '../components/PlanEditingFunctions.tsx';
|
|
import { TextField } from '../../../../components/TextField.tsx';
|
|
|
|
/**
|
|
* The default data structure for a Trigger 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 Trigger node.
|
|
* @property droppable: Whether this node can be dropped from the toolbar (default: true).
|
|
* @property hasReduce - Whether this node supports reduction logic.
|
|
*/
|
|
export type TriggerNodeData = {
|
|
label: string;
|
|
name: string;
|
|
droppable: boolean;
|
|
condition?: string; // id of the belief
|
|
plan?: Plan;
|
|
hasReduce: boolean;
|
|
};
|
|
|
|
|
|
export type TriggerNode = Node<TriggerNodeData>
|
|
|
|
/**
|
|
* Defines how a Trigger node should be rendered
|
|
* @param props - Node properties provided by React Flow, including `id` and `data`.
|
|
* @returns The rendered TriggerNode React element (React.JSX.Element).
|
|
*/
|
|
export default function TriggerNode(props: NodeProps<TriggerNode>) {
|
|
const data = props.data;
|
|
const {updateNodeData} = useFlowStore();
|
|
|
|
const setName= (value: string) => {
|
|
updateNodeData(props.id, {...data, name: value})
|
|
}
|
|
|
|
return <>
|
|
|
|
<Toolbar nodeId={props.id} allowDelete={true}/>
|
|
<div className={`${styles.defaultNode} ${styles.nodeTrigger} flex-col gap-sm`}>
|
|
<TextField
|
|
value={props.data.name}
|
|
setValue={(val) => setName(val)}
|
|
placeholder={"Name of this trigger..."}
|
|
/>
|
|
<div className={"flex-row gap-md"}>Triggers when the condition is met.</div>
|
|
<div className={"flex-row gap-md"}>Condition/ Belief is currently {data.condition ? "" : "not"} set. {data.condition ? "🟢" : "🔴"}</div>
|
|
<div className={"flex-row gap-md"}>Plan{data.plan ? (": " + data.plan.name) : ""} is currently {data.plan ? "" : "not"} set. {data.plan ? "🟢" : "🔴"}</div>
|
|
<MultiConnectionHandle type="source" position={Position.Right} id="TriggerSource" rules={[
|
|
allowOnlyConnectionsFromHandle([{nodeType:"phase",handleId:"data"}]),
|
|
]}/>
|
|
<SingleConnectionHandle
|
|
type="target"
|
|
position={Position.Bottom}
|
|
id="TriggerBeliefs"
|
|
style={{ left: '40%' }}
|
|
rules={[
|
|
allowOnlyConnectionsFromType(['basic_belief', "inferred_belief"]),
|
|
]}
|
|
/>
|
|
|
|
<MultiConnectionHandle
|
|
type="target"
|
|
position={Position.Bottom}
|
|
id="GoalTarget"
|
|
style={{ left: '60%' }}
|
|
rules={[
|
|
allowOnlyConnectionsFromType(['goal']),
|
|
]}
|
|
/>
|
|
|
|
<PlanEditorDialog
|
|
plan={data.plan}
|
|
onSave={(plan) => {
|
|
updateNodeData(props.id, {
|
|
...data,
|
|
plan,
|
|
});
|
|
}}
|
|
/>
|
|
</div>
|
|
</>;
|
|
}
|
|
|
|
/**
|
|
* Reduces each Trigger, including its children down into its core data.
|
|
* @param node - The Trigger 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 triggers.
|
|
*/
|
|
export function TriggerReduce(node: Node, nodes: Node[]) {
|
|
const data = node.data as TriggerNodeData;
|
|
const conditionNode = data.condition ? nodes.find((n)=>n.id===data.condition) : undefined
|
|
const conditionData = conditionNode ? BeliefGlobalReduce(conditionNode, nodes) : ""
|
|
return {
|
|
id: node.id,
|
|
name: node.data.name,
|
|
condition: conditionData, // Make sure we have a condition before reducing, or default to ""
|
|
plan: !data.plan ? "" : PlanReduce(nodes, data.plan), // Make sure we have a plan when reducing, or default to ""
|
|
}
|
|
|
|
}
|
|
|
|
|
|
export const TriggerTooltip = `
|
|
A trigger node is used to make Pepper execute a predefined plan -
|
|
consisting of one or more actions - when the connected beliefs are met`;
|
|
|
|
/**
|
|
* 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 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.
|
|
const nodes = useFlowStore.getState().nodes;
|
|
const otherNode = nodes.find((x) => x.id === _sourceNodeId)
|
|
if (!otherNode) return;
|
|
|
|
if (otherNode.type === 'basic_belief'|| otherNode.type ==='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)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 TriggerConnectionSource(_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 TriggerDisconnectionTarget(_thisNode: Node, _sourceNodeId: string) {
|
|
// no additional connection logic exists yet
|
|
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)
|
|
}
|
|
|
|
/**
|
|
* 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 TriggerDisconnectionSource(_thisNode: Node, _targetNodeId: string) {
|
|
// no additional connection logic exists yet
|
|
}
|
|
|
|
// Definitions for the possible triggers, being keywords and emotions
|
|
|
|
/** Represents a single keyword trigger entry. */
|
|
type Keyword = { id: string, keyword: string };
|
|
|
|
/** Properties for an emotion-type trigger node. */
|
|
export type EmotionTriggerNodeProps = {
|
|
type: "emotion";
|
|
value: string;
|
|
}
|
|
|
|
/** Props for a keyword-type trigger node. */
|
|
export type KeywordTriggerNodeProps = {
|
|
type: "keywords";
|
|
value: Keyword[];
|
|
}
|
|
|
|
/** Union type for all possible Trigger node configurations. */
|
|
export type TriggerNodeProps = EmotionTriggerNodeProps | KeywordTriggerNodeProps; |