Merge branch 'feat/monitoringpage-pim' into feat/monitoringpage
This commit is contained in:
@@ -1,201 +1,343 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import styles from './MonitoringPage.module.css';
|
||||
import useProgramStore from "../../utils/programStore.ts";
|
||||
import { GestureControls, SpeechPresets, DirectSpeechInput, StatusList, RobotConnected } from './MonitoringPageComponents.tsx';
|
||||
import { nextPhase, useExperimentLogger, useStatusLogger, pauseExperiment, playExperiment, type ExperimentStreamData, type GoalUpdate, type TriggerUpdate, type CondNormsStateUpdate, type PhaseUpdate } from ".//MonitoringPageAPI.ts"
|
||||
import { graphReducer, runProgramm } from '../VisProgPage/VisProg.tsx';
|
||||
|
||||
import type { NormNodeData} from '../VisProgPage/visualProgrammingUI/nodes/NormNode.tsx';
|
||||
import type { GoalNode } from '../VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx';
|
||||
import type { TriggerNode } from '../VisProgPage/visualProgrammingUI/nodes/TriggerNode.tsx';
|
||||
// Store & API
|
||||
import useProgramStore from "../../utils/programStore";
|
||||
import {
|
||||
nextPhase,
|
||||
useExperimentLogger,
|
||||
useStatusLogger,
|
||||
pauseExperiment,
|
||||
playExperiment,
|
||||
type ExperimentStreamData,
|
||||
type GoalUpdate,
|
||||
type TriggerUpdate,
|
||||
type CondNormsStateUpdate,
|
||||
type PhaseUpdate
|
||||
} from "./MonitoringPageAPI";
|
||||
import { graphReducer, runProgramm } from '../VisProgPage/VisProgLogic.ts';
|
||||
|
||||
// Types
|
||||
import type { NormNodeData } from '../VisProgPage/visualProgrammingUI/nodes/NormNode';
|
||||
import type { GoalNode } from '../VisProgPage/visualProgrammingUI/nodes/GoalNode';
|
||||
import type { TriggerNode } from '../VisProgPage/visualProgrammingUI/nodes/TriggerNode';
|
||||
|
||||
// Sub-components
|
||||
import {
|
||||
GestureControls,
|
||||
SpeechPresets,
|
||||
DirectSpeechInput,
|
||||
StatusList,
|
||||
RobotConnected
|
||||
} from './MonitoringPageComponents';
|
||||
|
||||
const MonitoringPage: React.FC = () => {
|
||||
// ----------------------------------------------------------------------
|
||||
// 1. State management
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Manages the state of the active experiment, including phase progression,
|
||||
* goal tracking, and stream event listeners.
|
||||
*/
|
||||
function useExperimentLogic() {
|
||||
const getPhaseIds = useProgramStore((s) => s.getPhaseIds);
|
||||
const getPhaseNames = useProgramStore((s) => s.getPhaseNames);
|
||||
const getNormsInPhase = useProgramStore((s) => s.getNormsInPhase);
|
||||
const getGoalsInPhase = useProgramStore((s) => s.getGoalsInPhase);
|
||||
const getTriggersInPhase = useProgramStore((s) => s.getTriggersInPhase);
|
||||
const setProgramState = useProgramStore((state) => state.setProgramState);
|
||||
|
||||
|
||||
// Can be used to block actions until feedback from CB.
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [activeIds, setActiveIds] = React.useState<Record<string, boolean>>({});
|
||||
const [goalIndex, setGoalIndex] = React.useState(0);
|
||||
const [isPlaying, setIsPlaying] = React.useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [activeIds, setActiveIds] = useState<Record<string, boolean>>({});
|
||||
const [goalIndex, setGoalIndex] = useState(0);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [phaseIndex, setPhaseIndex] = useState(0);
|
||||
const [isFinished, setIsFinished] = useState(false);
|
||||
|
||||
const phaseIds = getPhaseIds();
|
||||
const phaseNames = getPhaseNames();
|
||||
|
||||
const [phaseIndex, setPhaseIndex] = React.useState(0);
|
||||
|
||||
//see if we reached end node
|
||||
const [isFinished, setIsFinished] = React.useState(false);
|
||||
// --- Stream Handlers ---
|
||||
|
||||
const handleStreamUpdate = React.useCallback((data: ExperimentStreamData) => {
|
||||
// Check for phase updates
|
||||
const handleStreamUpdate = useCallback((data: ExperimentStreamData) => {
|
||||
if (data.type === 'phase_update' && data.id) {
|
||||
const payload = data as PhaseUpdate;
|
||||
console.log(`${data.type} received, id : ${data.id}`)
|
||||
console.log(`${data.type} received, id : ${data.id}`);
|
||||
|
||||
if (payload.id === "end") {
|
||||
setIsFinished(true);
|
||||
} else {
|
||||
setIsFinished(false);
|
||||
|
||||
const allIds = getPhaseIds();
|
||||
const newIndex = allIds.indexOf(payload.id);
|
||||
const newIndex = getPhaseIds().indexOf(payload.id);
|
||||
if (newIndex !== -1) {
|
||||
setPhaseIndex(newIndex);
|
||||
setGoalIndex(0);
|
||||
setPhaseIndex(newIndex);
|
||||
setGoalIndex(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data.type === 'goal_update') {
|
||||
const payload = data as GoalUpdate;
|
||||
const currentPhaseGoals = getGoalsInPhase(phaseIds[phaseIndex]) as GoalNode[];
|
||||
const gIndex = currentPhaseGoals.findIndex((g: GoalNode) => g.id === payload.id);
|
||||
console.log(`${data.type} received, id : ${data.id}`)
|
||||
if (gIndex == -1)
|
||||
{console.log(`goal to update with id ${payload.id} not found in current phase ${phaseNames[phaseIndex]}`)}
|
||||
else {
|
||||
//set current goal to the goal that is just started
|
||||
setGoalIndex(gIndex);
|
||||
const payload = data as GoalUpdate;
|
||||
const currentPhaseGoals = getGoalsInPhase(phaseIds[phaseIndex]) as GoalNode[];
|
||||
const gIndex = currentPhaseGoals.findIndex((g) => g.id === payload.id);
|
||||
|
||||
// All previous goals are set to "active" which means they are achieved
|
||||
setActiveIds((prev) => {
|
||||
const nextState = { ...prev };
|
||||
|
||||
// We loop until i is LESS than gIndex.
|
||||
// This leaves currentPhaseGoals[gIndex] as isActive: false.
|
||||
for (let i = 0; i < gIndex; i++) {
|
||||
nextState[currentPhaseGoals[i].id ] = true;
|
||||
}
|
||||
|
||||
return nextState;
|
||||
});
|
||||
|
||||
console.log(`Now pursuing goal: ${payload.id}. Previous goals marked achieved.`);
|
||||
}
|
||||
}
|
||||
console.log(`${data.type} received, id : ${data.id}`);
|
||||
|
||||
if (gIndex === -1) {
|
||||
console.warn(`Goal ${payload.id} not found in phase ${phaseNames[phaseIndex]}`);
|
||||
} else {
|
||||
setGoalIndex(gIndex);
|
||||
// Mark all previous goals as achieved
|
||||
setActiveIds((prev) => {
|
||||
const nextState = { ...prev };
|
||||
for (let i = 0; i < gIndex; i++) {
|
||||
nextState[currentPhaseGoals[i].id] = true;
|
||||
}
|
||||
return nextState;
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (data.type === 'trigger_update') {
|
||||
const payload = data as TriggerUpdate;
|
||||
setActiveIds((prev) => ({
|
||||
...prev,
|
||||
[payload.id]: payload.achieved
|
||||
}));
|
||||
}
|
||||
}, [getPhaseIds, getGoalsInPhase, phaseIds, phaseIndex]);
|
||||
const payload = data as TriggerUpdate;
|
||||
setActiveIds((prev) => ({ ...prev, [payload.id]: payload.achieved }));
|
||||
}
|
||||
}, [getPhaseIds, getGoalsInPhase, phaseIds, phaseIndex, phaseNames]);
|
||||
|
||||
const handleStatusUpdate = useCallback((data: unknown) => {
|
||||
|
||||
const handleStatusUpdate = React.useCallback((data: any) => {
|
||||
if (data.type === 'cond_norms_state_update') {
|
||||
const payload = data as CondNormsStateUpdate;
|
||||
if (payload.type !== 'cond_norms_state_update') return;
|
||||
|
||||
setActiveIds((prev) => {
|
||||
const hasChanges = payload.norms.some(
|
||||
(normUpdate) => prev[normUpdate.id] !== normUpdate.active
|
||||
);
|
||||
|
||||
if (!hasChanges) {
|
||||
return prev;
|
||||
}
|
||||
const hasChanges = payload.norms.some((u) => prev[u.id] !== u.active);
|
||||
if (!hasChanges) return prev;
|
||||
|
||||
const nextState = { ...prev };
|
||||
payload.norms.forEach((normUpdate) => {
|
||||
nextState[normUpdate.id] = normUpdate.active;
|
||||
});
|
||||
payload.norms.forEach((u) => { nextState[u.id] = u.active; });
|
||||
return nextState;
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
//For incoming phase, goals and trigger updates
|
||||
}, []);
|
||||
|
||||
// Connect listeners
|
||||
useExperimentLogger(handleStreamUpdate);
|
||||
//For pings that update conditional norms
|
||||
useStatusLogger(handleStatusUpdate);
|
||||
|
||||
const resetExperiment = React.useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// --- Actions ---
|
||||
|
||||
const phases = graphReducer();
|
||||
setProgramState({ phases });
|
||||
|
||||
//reset monitoring page
|
||||
setActiveIds({}); //remove active items
|
||||
setPhaseIndex(0); //Go to first phase
|
||||
setGoalIndex(0); // Reset goal indicator
|
||||
setIsFinished(false); // Reset experiment done
|
||||
|
||||
//inform backend
|
||||
await runProgramm();
|
||||
|
||||
console.log("Experiment & UI successfully reset to start.");
|
||||
} catch (err) {
|
||||
console.error("Failed to reset program:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [graphReducer, setProgramState]);
|
||||
|
||||
|
||||
if (phaseIds.length === 0) {
|
||||
return <p className={styles.empty}>No program loaded.</p>;
|
||||
}
|
||||
|
||||
const phaseId = phaseIds[phaseIndex];
|
||||
|
||||
const goals = (getGoalsInPhase(phaseId) as GoalNode[]).map(g => ({
|
||||
...g,
|
||||
achieved: activeIds[g.id] ?? false,
|
||||
}));
|
||||
|
||||
|
||||
|
||||
const triggers = (getTriggersInPhase(phaseId) as TriggerNode[]).map(t => ({
|
||||
...t,
|
||||
achieved: activeIds[t.id] ?? false,
|
||||
}));
|
||||
|
||||
const norms = (getNormsInPhase(phaseId) as NormNodeData[])
|
||||
.filter(n => !n.condition)
|
||||
.map(n => ({
|
||||
...n,
|
||||
label: n.norm,
|
||||
}));
|
||||
const conditionalNorms = (getNormsInPhase(phaseId) as (NormNodeData &{id: string})[])
|
||||
.filter(n => !!n.condition) // Only items with a condition
|
||||
.map(n => ({
|
||||
...n,
|
||||
achieved: activeIds[n.id] ?? false
|
||||
}));
|
||||
|
||||
// Handle logic of 'next' button.
|
||||
|
||||
const handleButton = async (button: string, _context?: string, _endpoint?: string) => {
|
||||
const resetExperiment = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
switch (button) {
|
||||
const phases = graphReducer();
|
||||
setProgramState({ phases });
|
||||
|
||||
setActiveIds({});
|
||||
setPhaseIndex(0);
|
||||
setGoalIndex(0);
|
||||
setIsFinished(false);
|
||||
|
||||
await runProgramm();
|
||||
console.log("Experiment & UI successfully reset.");
|
||||
} catch (err) {
|
||||
console.error("Failed to reset program:", err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [setProgramState]);
|
||||
|
||||
const handleControlAction = async (action: "pause" | "play" | "nextPhase" | "resetPhase") => {
|
||||
try {
|
||||
setLoading(true);
|
||||
switch (action) {
|
||||
case "pause":
|
||||
setIsPlaying(false);
|
||||
await pauseExperiment();
|
||||
break;
|
||||
case "play":
|
||||
setIsPlaying(true);
|
||||
await playExperiment();
|
||||
break;
|
||||
case "nextPhase":
|
||||
await nextPhase();
|
||||
break;
|
||||
case "resetExperiment":
|
||||
await resetExperiment();
|
||||
break;
|
||||
default:
|
||||
// Case for resetPhase if implemented in API
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
loading,
|
||||
isPlaying,
|
||||
isFinished,
|
||||
phaseIds,
|
||||
phaseNames,
|
||||
phaseIndex,
|
||||
goalIndex,
|
||||
activeIds,
|
||||
setActiveIds,
|
||||
resetExperiment,
|
||||
handleControlAction,
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 2. Smaller Presentation Components
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Visual indicator of progress through experiment phases.
|
||||
*/
|
||||
function PhaseProgressBar({
|
||||
phaseIds,
|
||||
phaseIndex,
|
||||
isFinished
|
||||
}: {
|
||||
phaseIds: string[],
|
||||
phaseIndex: number,
|
||||
isFinished: boolean
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.phaseProgress}>
|
||||
{phaseIds.map((id, index) => {
|
||||
let statusClass = "";
|
||||
if (isFinished || index < phaseIndex) statusClass = styles.completed;
|
||||
else if (index === phaseIndex) statusClass = styles.current;
|
||||
|
||||
return (
|
||||
<span key={id} className={`${styles.phase} ${statusClass}`}>
|
||||
{index + 1}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main control buttons (Play, Pause, Next, Reset).
|
||||
*/
|
||||
function ControlPanel({
|
||||
loading,
|
||||
isPlaying,
|
||||
onAction,
|
||||
onReset
|
||||
}: {
|
||||
loading: boolean,
|
||||
isPlaying: boolean,
|
||||
onAction: (a: "pause" | "play" | "nextPhase" | "resetPhase") => void,
|
||||
onReset: () => void
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.experimentControls}>
|
||||
<h3>Experiment Controls</h3>
|
||||
<div className={styles.controlsButtons}>
|
||||
<button
|
||||
className={!isPlaying ? styles.pausePlayActive : styles.pausePlayInactive}
|
||||
onClick={() => onAction("pause")}
|
||||
disabled={loading}
|
||||
>❚❚</button>
|
||||
|
||||
<button
|
||||
className={isPlaying ? styles.pausePlayActive : styles.pausePlayInactive}
|
||||
onClick={() => onAction("play")}
|
||||
disabled={loading}
|
||||
>▶</button>
|
||||
|
||||
<button
|
||||
className={styles.next}
|
||||
onClick={() => onAction("nextPhase")}
|
||||
disabled={loading}
|
||||
>⏭</button>
|
||||
|
||||
<button
|
||||
className={styles.restartPhase}
|
||||
onClick={() => onAction("resetPhase")}
|
||||
disabled={loading}
|
||||
>↩</button>
|
||||
|
||||
<button
|
||||
className={styles.restartExperiment}
|
||||
onClick={onReset}
|
||||
disabled={loading}
|
||||
>⟲</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays lists of Goals, Triggers, and Norms for the current phase.
|
||||
*/
|
||||
function PhaseDashboard({
|
||||
phaseId,
|
||||
activeIds,
|
||||
setActiveIds,
|
||||
goalIndex
|
||||
}: {
|
||||
phaseId: string,
|
||||
activeIds: Record<string, boolean>,
|
||||
setActiveIds: React.Dispatch<React.SetStateAction<Record<string, boolean>>>,
|
||||
goalIndex: number
|
||||
}) {
|
||||
const getGoals = useProgramStore((s) => s.getGoalsInPhase);
|
||||
const getTriggers = useProgramStore((s) => s.getTriggersInPhase);
|
||||
const getNorms = useProgramStore((s) => s.getNormsInPhase);
|
||||
|
||||
// Prepare data view models
|
||||
const goals = (getGoals(phaseId) as GoalNode[]).map(g => ({
|
||||
...g,
|
||||
achieved: activeIds[g.id] ?? false,
|
||||
}));
|
||||
|
||||
const triggers = (getTriggers(phaseId) as TriggerNode[]).map(t => ({
|
||||
...t,
|
||||
achieved: activeIds[t.id] ?? false,
|
||||
}));
|
||||
|
||||
const norms = (getNorms(phaseId) as NormNodeData[])
|
||||
.filter(n => !n.condition)
|
||||
.map(n => ({ ...n, label: n.norm }));
|
||||
|
||||
const conditionalNorms = (getNorms(phaseId) as (NormNodeData & { id: string })[])
|
||||
.filter(n => !!n.condition)
|
||||
.map(n => ({
|
||||
...n,
|
||||
achieved: activeIds[n.id] ?? false
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusList title="Goals" items={goals} type="goal" activeIds={activeIds} setActiveIds={setActiveIds} currentGoalIndex={goalIndex} />
|
||||
<StatusList title="Triggers" items={triggers} type="trigger" activeIds={activeIds} />
|
||||
<StatusList title="Norms" items={norms} type="norm" activeIds={activeIds} />
|
||||
<StatusList title="Conditional Norms" items={conditionalNorms} type="cond_norm" activeIds={activeIds} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// 3. Main Component
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const MonitoringPage: React.FC = () => {
|
||||
const {
|
||||
loading,
|
||||
isPlaying,
|
||||
isFinished,
|
||||
phaseIds,
|
||||
phaseNames,
|
||||
phaseIndex,
|
||||
goalIndex,
|
||||
activeIds,
|
||||
setActiveIds,
|
||||
resetExperiment,
|
||||
handleControlAction
|
||||
} = useExperimentLogic();
|
||||
|
||||
if (phaseIds.length === 0) {
|
||||
return <p className={styles.empty}>No program loaded.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.dashboardContainer}>
|
||||
{/* HEADER */}
|
||||
@@ -206,117 +348,45 @@ const resetExperiment = React.useCallback(async () => {
|
||||
{isFinished ? (
|
||||
<strong>Experiment finished</strong>
|
||||
) : (
|
||||
<>
|
||||
<strong>Phase {phaseIndex + 1}:</strong> {phaseNames[phaseIndex]}
|
||||
</>
|
||||
<><strong>Phase {phaseIndex + 1}:</strong> {phaseNames[phaseIndex]}</>
|
||||
)}
|
||||
</p>
|
||||
<div className={styles.phaseProgress}>
|
||||
{phaseIds.map((id, index) => {
|
||||
// Determine the status of the phase indicator
|
||||
let phaseStatusClass = "";
|
||||
|
||||
if (isFinished) {
|
||||
// If the whole experiment is done, all squares are green (completed)
|
||||
phaseStatusClass = styles.completed;
|
||||
} else if (index < phaseIndex) {
|
||||
// Past phases
|
||||
phaseStatusClass = styles.completed;
|
||||
} else if (index === phaseIndex) {
|
||||
// The current phase being worked on
|
||||
phaseStatusClass = styles.current;
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
key={id}
|
||||
className={`${styles.phase} ${phaseStatusClass}`}
|
||||
>
|
||||
{index + 1}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<PhaseProgressBar phaseIds={phaseIds} phaseIndex={phaseIndex} isFinished={isFinished} />
|
||||
</div>
|
||||
|
||||
<div className={styles.experimentControls}>
|
||||
<h3>Experiment Controls</h3>
|
||||
<div className={styles.controlsButtons}>
|
||||
{/*Pause button*/}
|
||||
<button
|
||||
className={`${!isPlaying ? styles.pausePlayActive : styles.pausePlayInactive}`}
|
||||
onClick={() => {
|
||||
setIsPlaying(false);
|
||||
handleButton("pause");}
|
||||
}
|
||||
disabled={loading}
|
||||
>❚❚</button>
|
||||
|
||||
{/*Play button*/}
|
||||
<button
|
||||
className={`${isPlaying ? styles.pausePlayActive : styles.pausePlayInactive}`}
|
||||
onClick={() => {
|
||||
setIsPlaying(true);
|
||||
handleButton("play");}
|
||||
}
|
||||
disabled={loading}
|
||||
>▶</button>
|
||||
|
||||
{/*Next button*/}
|
||||
<button
|
||||
className={styles.next}
|
||||
onClick={() => handleButton("nextPhase")}
|
||||
disabled={loading}
|
||||
>
|
||||
⏭
|
||||
</button>
|
||||
|
||||
{/*Restart Phase button*/}
|
||||
<button
|
||||
className={styles.restartPhase}
|
||||
onClick={() => handleButton("resetPhase")}
|
||||
disabled={loading}
|
||||
>
|
||||
↩
|
||||
</button>
|
||||
|
||||
{/*Restart Experiment button*/}
|
||||
<button
|
||||
className={styles.restartExperiment}
|
||||
onClick={() => resetExperiment()}
|
||||
disabled={loading}
|
||||
>
|
||||
⟲
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ControlPanel
|
||||
loading={loading}
|
||||
isPlaying={isPlaying}
|
||||
onAction={handleControlAction}
|
||||
onReset={resetExperiment}
|
||||
/>
|
||||
|
||||
<div className={styles.connectionStatus}>
|
||||
{RobotConnected()}
|
||||
<RobotConnected />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* MAIN GRID */}
|
||||
|
||||
<main className={styles.phaseOverview}>
|
||||
<section className={styles.phaseOverviewText}>
|
||||
<h3>Phase Overview</h3>
|
||||
</section>
|
||||
{isFinished ? (
|
||||
|
||||
{isFinished ? (
|
||||
<div className={styles.finishedMessage}>
|
||||
<p> All phases have been successfully completed.</p>
|
||||
<p>All phases have been successfully completed.</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<StatusList title="Goals" items={goals} type="goal" activeIds={activeIds} setActiveIds = {setActiveIds} currentGoalIndex={goalIndex} />
|
||||
<StatusList title="Triggers" items={triggers} type="trigger" activeIds={activeIds} />
|
||||
<StatusList title="Norms" items={norms} type="norm" activeIds={activeIds} />
|
||||
<StatusList title="Conditional Norms" items={conditionalNorms} type="cond_norm" activeIds={activeIds} />
|
||||
</>
|
||||
<PhaseDashboard
|
||||
phaseId={phaseIds[phaseIndex]}
|
||||
activeIds={activeIds}
|
||||
setActiveIds={setActiveIds}
|
||||
goalIndex={goalIndex}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* LOGS */}
|
||||
{/* LOGS TODO: add actual logs */}
|
||||
<aside className={styles.logs}>
|
||||
<h3>Logs</h3>
|
||||
<div className={styles.logHeader}>
|
||||
|
||||
@@ -111,7 +111,7 @@ export function useExperimentLogger(onUpdate?: (data: ExperimentStreamData) => v
|
||||
* A hook that listens to the status stream that updates active conditional norms
|
||||
* via updates sent from the backend
|
||||
*/
|
||||
export function useStatusLogger(onUpdate?: (data: any) => void) {
|
||||
export function useStatusLogger(onUpdate?: (data: ExperimentStreamData) => void) {
|
||||
const callbackRef = React.useRef(onUpdate);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -9,16 +9,15 @@ import {
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import {type CSSProperties, 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'
|
||||
import { NodeReduces, NodeTypes } from './visualProgrammingUI/NodeRegistry.ts';
|
||||
import {NodeTypes} from './visualProgrammingUI/NodeRegistry.ts';
|
||||
import SaveLoadPanel from './visualProgrammingUI/components/SaveLoadPanel.tsx';
|
||||
import MonitoringPage from '../MonitoringPage/MonitoringPage.tsx';
|
||||
import { graphReducer, runProgramm } from './VisProgLogic.ts';
|
||||
|
||||
// --| config starting params for flow |--
|
||||
|
||||
@@ -145,42 +144,6 @@ function VisualProgrammingUI() {
|
||||
);
|
||||
}
|
||||
|
||||
// currently outputs the prepared program to the console
|
||||
export function runProgramm() {
|
||||
const phases = graphReducer();
|
||||
const program = {phases}
|
||||
console.log(JSON.stringify(program, null, 2));
|
||||
fetch(
|
||||
"http://localhost:8000/program",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify(program),
|
||||
}
|
||||
).then((res) => {
|
||||
if (!res.ok) throw new Error("Failed communicating with the backend.")
|
||||
console.log("Successfully sent the program to the backend.");
|
||||
|
||||
// store reduced program in global program store for further use in the UI
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the graph into its phases' information and recursively calls their reducing function
|
||||
*/
|
||||
export function graphReducer() {
|
||||
const { nodes } = useFlowStore.getState();
|
||||
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
|
||||
|
||||
43
src/pages/VisProgPage/VisProgLogic.ts
Normal file
43
src/pages/VisProgPage/VisProgLogic.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import useProgramStore from "../../utils/programStore";
|
||||
import orderPhaseNodeArray from "../../utils/orderPhaseNodes";
|
||||
import useFlowStore from './visualProgrammingUI/VisProgStores';
|
||||
import { NodeReduces } from './visualProgrammingUI/NodeRegistry';
|
||||
import type { PhaseNode } from "./visualProgrammingUI/nodes/PhaseNode";
|
||||
|
||||
/**
|
||||
* Reduces the graph into its phases' information and recursively calls their reducing function
|
||||
*/
|
||||
export function graphReducer() {
|
||||
const { nodes } = useFlowStore.getState();
|
||||
return orderPhaseNodeArray(nodes.filter((n) => n.type == 'phase') as PhaseNode [])
|
||||
.map((n) => {
|
||||
const reducer = NodeReduces['phase'];
|
||||
return reducer(n, nodes)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs the prepared program to the console and sends it to the backend
|
||||
*/
|
||||
export function runProgramm() {
|
||||
const phases = graphReducer();
|
||||
const program = {phases}
|
||||
console.log(JSON.stringify(program, null, 2));
|
||||
fetch(
|
||||
"http://localhost:8000/program",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify(program),
|
||||
}
|
||||
).then((res) => {
|
||||
if (!res.ok) throw new Error("Failed communicating with the backend.")
|
||||
console.log("Successfully sent the program to the backend.");
|
||||
|
||||
// store reduced program in global program store for further use in the UI
|
||||
// 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);
|
||||
}
|
||||
@@ -136,7 +136,7 @@ export function TriggerConnectionTarget(_thisNode: Node, _sourceNodeId: string)
|
||||
const otherNode = nodes.find((x) => x.id === _sourceNodeId)
|
||||
if (!otherNode) return;
|
||||
|
||||
if (otherNode.type === 'basic_belief'||'inferred_belief') {
|
||||
if (otherNode.type === 'basic_belief'|| otherNode.type ==='inferred_belief') {
|
||||
data.condition = _sourceNodeId;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user