Add experiment logs to the monitoring page #48

Merged
0950726 merged 122 commits from feat/experiment-logs into dev 2026-01-28 10:16:00 +00:00
6 changed files with 61 additions and 12 deletions
Showing only changes of commit 8b40001038 - Show all commits

View File

@@ -119,7 +119,7 @@ const VisProgUI = () => {
<SaveLoadPanel></SaveLoadPanel> <SaveLoadPanel></SaveLoadPanel>
</Panel> </Panel>
<Panel position="bottom-center"> <Panel position="bottom-center">
<button onClick={() => undo()}>undo</button> <button onClick={() => undo()}>Undo</button>
<button onClick={() => redo()}>Redo</button> <button onClick={() => redo()}>Redo</button>
</Panel> </Panel>
<Controls/> <Controls/>
@@ -175,7 +175,7 @@ function VisProgPage() {
return ( return (
<> <>
<VisualProgrammingUI/> <VisualProgrammingUI/>
<button onClick={runProgram}>run program</button> <button onClick={runProgram}>Run Program</button>
</> </>
) )
} }

View File

@@ -107,4 +107,16 @@ export function useHandleRules(
// finally we return a function that evaluates all rules using the created context // finally we return a function that evaluates all rules using the created context
return evaluateRules(targetRules, connection, context); return evaluateRules(targetRules, connection, context);
}; };
}
export function validateConnectionWithRules(
connection: Connection,
context: ConnectionContext
): RuleResult {
const rules = useFlowStore.getState().getTargetRules(
connection.target!,
connection.targetHandle!
);
return evaluateRules(rules,connection, context);
} }

View File

@@ -9,6 +9,7 @@ import {
type XYPosition, type XYPosition,
} from '@xyflow/react'; } from '@xyflow/react';
import '@xyflow/react/dist/style.css'; import '@xyflow/react/dist/style.css';
import {type ConnectionContext, validateConnectionWithRules} from "./HandleRuleLogic.ts";
import type { FlowState } from './VisProgTypes'; import type { FlowState } from './VisProgTypes';
import { import {
NodeDefaults, NodeDefaults,
@@ -129,7 +130,41 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
* Handles reconnecting an edge between nodes. * Handles reconnecting an edge between nodes.
*/ */
onReconnect: (oldEdge, newConnection) => { onReconnect: (oldEdge, newConnection) => {
get().edgeReconnectSuccessful = true;
function createContext(
source: {id: string, handleId: string},
target: {id: string, handleId: string}
) : ConnectionContext {
const edges = get().edges;
const targetConnections = edges.filter(edge => edge.target === target.id && edge.targetHandle === target.handleId).length
return {
connectionCount: targetConnections,
source: source,
target: target
}
}
// connection validation
const context: ConnectionContext = oldEdge.source === newConnection.source
? createContext({id: newConnection.source, handleId: newConnection.sourceHandle!}, {id: newConnection.target, handleId: newConnection.targetHandle!})
: createContext({id: newConnection.target, handleId: newConnection.targetHandle!}, {id: newConnection.source, handleId: newConnection.sourceHandle!});
const result = validateConnectionWithRules(
newConnection,
context
);
if (!result.isSatisfied) {
set({
edges: get().edges.map(e =>
e.id === oldEdge.id ? oldEdge : e
),
});
return;
}
// further reconnect logic
set({ edgeReconnectSuccessful: true });
set({ edges: reconnectEdge(oldEdge, newConnection, get().edges) }); set({ edges: reconnectEdge(oldEdge, newConnection, get().edges) });
// We make sure to perform any required data updates on the newly reconnected nodes // We make sure to perform any required data updates on the newly reconnected nodes
@@ -188,7 +223,7 @@ const useFlowStore = create<FlowState>(UndoRedo((set, get) => ({
// Let's find our node to check if they have a special deletion function // Let's find our node to check if they have a special deletion function
const ourNode = get().nodes.find((n)=>n.id==nodeId); const ourNode = get().nodes.find((n)=>n.id==nodeId);
const ourFunction = Object.entries(NodeDeletes).find(([t])=>t==ourNode?.type)?.[1] const ourFunction = Object.entries(NodeDeletes).find(([t])=>t==ourNode?.type)?.[1]
// If there's no function, OR, our function tells us we can delete it, let's do so... // If there's no function, OR, our function tells us we can delete it, let's do so...
if (ourFunction == undefined || ourFunction()) { if (ourFunction == undefined || ourFunction()) {
set({ set({

View File

@@ -10,6 +10,7 @@ import {allowOnlyConnectionsFromHandle} from "../HandleRules.ts";
import useFlowStore from '../VisProgStores.tsx'; import useFlowStore from '../VisProgStores.tsx';
import { TextField } from '../../../../components/TextField.tsx'; import { TextField } from '../../../../components/TextField.tsx';
import { MultilineTextField } from '../../../../components/MultilineTextField.tsx'; import { MultilineTextField } from '../../../../components/MultilineTextField.tsx';
import {noMatchingLeftRightBelief} from "./BeliefGlobals.ts";
/** /**
* The default data structure for a BasicBelief node * The default data structure for a BasicBelief node
@@ -189,7 +190,8 @@ export default function BasicBeliefNode(props: NodeProps<BasicBeliefNode>) {
</div> </div>
)} )}
<MultiConnectionHandle type="source" position={Position.Right} id="source" rules={[ <MultiConnectionHandle type="source" position={Position.Right} id="source" rules={[
allowOnlyConnectionsFromHandle([{nodeType:"trigger",handleId:"TriggerBeliefs"}, {nodeType:"norm",handleId:"NormBeliefs"},{nodeType:"InferredBelief",handleId:"inferred_belief"}]), noMatchingLeftRightBelief,
allowOnlyConnectionsFromHandle([{nodeType:"trigger",handleId:"TriggerBeliefs"}, {nodeType:"norm",handleId:"NormBeliefs"},{nodeType:"InferredBelief",handleId:"inferred_belief"}]),
]}/> ]}/>
</div> </div>
</> </>

View File

@@ -5,7 +5,7 @@ import type { InferredBeliefNodeData } from "./InferredBeliefNode.tsx";
* Default data for this node * Default data for this node
*/ */
export const InferredBeliefNodeDefaults: InferredBeliefNodeData = { export const InferredBeliefNodeDefaults: InferredBeliefNodeData = {
label: "Inferred Belief", label: "AND/OR",
droppable: true, droppable: true,
inferredBelief: { inferredBelief: {
left: undefined, left: undefined,

View File

@@ -50,9 +50,9 @@ export default function TriggerNode(props: NodeProps<TriggerNode>) {
const setName= (value: string) => { const setName= (value: string) => {
updateNodeData(props.id, {...data, name: value}) updateNodeData(props.id, {...data, name: value})
} }
return <> return <>
<Toolbar nodeId={props.id} allowDelete={true}/> <Toolbar nodeId={props.id} allowDelete={true}/>
<div className={`${styles.defaultNode} ${styles.nodeTrigger} flex-col gap-sm`}> <div className={`${styles.defaultNode} ${styles.nodeTrigger} flex-col gap-sm`}>
<TextField <TextField
@@ -70,9 +70,9 @@ export default function TriggerNode(props: NodeProps<TriggerNode>) {
type="target" type="target"
position={Position.Bottom} position={Position.Bottom}
id="TriggerBeliefs" id="TriggerBeliefs"
style={{ left: '40%' }} style={{ left: '40%' }}
rules={[ rules={[
allowOnlyConnectionsFromType(['basic_belief', "inferred_belief"]), allowOnlyConnectionsFromType(['basic_belief']),
]} ]}
/> />
@@ -136,7 +136,7 @@ export function TriggerConnectionTarget(_thisNode: Node, _sourceNodeId: string)
const otherNode = nodes.find((x) => x.id === _sourceNodeId) const otherNode = nodes.find((x) => x.id === _sourceNodeId)
if (!otherNode) return; if (!otherNode) return;
if (otherNode.type === 'basic_belief'|| otherNode.type ==='inferred_belief') { if (otherNode.type === 'basic_belief' /* TODO: Add the option for an inferred belief */) {
data.condition = _sourceNodeId; data.condition = _sourceNodeId;
} }
@@ -172,7 +172,7 @@ export function TriggerDisconnectionTarget(_thisNode: Node, _sourceNodeId: strin
const data = _thisNode.data as TriggerNodeData; const data = _thisNode.data as TriggerNodeData;
// remove if the target of disconnection was our condition // remove if the target of disconnection was our condition
if (_sourceNodeId == data.condition) data.condition = undefined if (_sourceNodeId == data.condition) data.condition = undefined
data.plan = deleteGoalInPlanByID(structuredClone(data.plan) as Plan, _sourceNodeId) data.plan = deleteGoalInPlanByID(structuredClone(data.plan) as Plan, _sourceNodeId)
} }