chore: gptd data typing and tests fixing

This commit is contained in:
Björn Otgaar
2026-01-13 12:03:49 +01:00
parent 79d889c10e
commit 108fdeeedc
2 changed files with 76 additions and 35 deletions

View File

@@ -2,10 +2,37 @@ import React from 'react';
import styles from './MonitoringPage.module.css';
import useProgramStore from "../../utils/programStore.ts";
import { GestureControls, SpeechPresets, DirectSpeechInput, StatusList, RobotConnected } from './Components';
import { nextPhase, useExperimentLogger, pauseExperiment, playExperiment, resetExperiment, resetPhase } from ".//MonitoringPageAPI.ts"
import { nextPhase, useExperimentLogger, pauseExperiment, playExperiment, resetExperiment, resetPhase, type ExperimentStreamData, type GoalUpdate, type TriggerUpdate, type CondNormsStateUpdate, type PhaseUpdate } from ".//MonitoringPageAPI.ts"
import type { NormNodeData } from '../VisProgPage/visualProgrammingUI/nodes/NormNode.tsx';
// Stream message types are defined in MonitoringPageAPI as `ExperimentStreamData`.
// Types for reduced program items (output from node reducers):
export type ReducedPlanStep = {
id: string;
text?: string;
gesture?: { type: string; name?: string };
goal?: string;
} & Record<string, unknown>;
export type ReducedPlan = { id: string; steps: ReducedPlanStep[] } | "";
export type ReducedGoal = { id: string; name: string; description?: string; can_fail?: boolean; plan?: ReducedPlan };
export type ReducedCondition = {
id: string;
keyword?: string;
emotion?: string;
object?: string;
name?: string;
description?: string;
} & Record<string, unknown>;
export type ReducedTrigger = { id: string; name: string; condition?: ReducedCondition | ""; plan?: ReducedPlan };
export type ReducedNorm = { id: string; label?: string; norm?: string; condition?: ReducedCondition | "" };
const MonitoringPage: React.FC = () => {
const getPhaseIds = useProgramStore((s) => s.getPhaseIds);
const getNormsInPhase = useProgramStore((s) => s.getNormsInPhase);
@@ -25,25 +52,27 @@ const MonitoringPage: React.FC = () => {
//see if we reached end node
const [isFinished, setIsFinished] = React.useState(false);
const handleStreamUpdate = React.useCallback((data: any) => {
const handleStreamUpdate = React.useCallback((data: ExperimentStreamData) => {
// Check for phase updates
if (data.type === 'phase_update' && data.phase_id) {
if (data.phase_id === "end") {
if (data.type === 'phase_update' && data.id) {
const payload = data as PhaseUpdate;
if (payload.id === "end") {
setIsFinished(true);
} else {
setIsFinished(false);
setIsFinished(false);
const allIds = getPhaseIds();
const newIndex = allIds.indexOf(data.phase_id);
const newIndex = allIds.indexOf(payload.id);
if (newIndex !== -1) {
setPhaseIndex(newIndex);
setGoalIndex(0);
}
}
}
}
else if (data.type === 'goal_update') {
const currentPhaseGoals = getGoalsInPhase(phaseIds[phaseIndex]) as any[];
const gIndex = currentPhaseGoals.findIndex((g: any) => g.id === data.id);
const payload = data as GoalUpdate;
const currentPhaseGoals = getGoalsInPhase(phaseIds[phaseIndex]) as ReducedGoal[];
const gIndex = currentPhaseGoals.findIndex((g: ReducedGoal) => g.id === payload.id);
if (gIndex !== -1) {
//set current goal to the goal that is just started
@@ -62,30 +91,32 @@ const MonitoringPage: React.FC = () => {
return nextState;
});
console.log(`Now pursuing goal: ${data.id}. Previous goals marked achieved.`);
console.log(`Now pursuing goal: ${payload.id}. Previous goals marked achieved.`);
}
}
else if (data.type === 'trigger_update') {
const payload = data as TriggerUpdate;
setActiveIds((prev) => ({
...prev,
[data.id]: data.achieved // data.id is de key, achieved is true/false
[payload.id]: payload.achieved
}));
}
else if (data.type === 'cond_norms_state_update') {
const payload = data as CondNormsStateUpdate;
setActiveIds((prev) => {
const nextState = { ...prev };
data.norms.forEach((normUpdate: { id: string; active: boolean }) => {
// payload.norms is typed on the union, so safe to use directly
payload.norms.forEach((normUpdate) => {
nextState[normUpdate.id] = normUpdate.active;
console.log(`Conditional norm ${normUpdate.id} set to active: ${normUpdate.active}`);
});
return nextState;
});
console.log("Updated conditional norms state:", data.norms);
console.log("Updated conditional norms state:", payload.norms);
}
}, [getPhaseIds]);
}, [getPhaseIds, getGoalsInPhase, phaseIds, phaseIndex]);
useExperimentLogger(handleStreamUpdate);
@@ -95,35 +126,36 @@ const MonitoringPage: React.FC = () => {
const phaseId = phaseIds[phaseIndex];
const goals = (getGoalsInPhase(phaseId) as any[]).map(g => ({
const goals = (getGoalsInPhase(phaseId) as ReducedGoal[]).map(g => ({
...g,
label: g.name,
achieved: activeIds[g.id] ?? false
label: g.name,
achieved: activeIds[g.id] ?? false,
}));
const triggers = (getTriggersInPhase(phaseId) as any[]).map(t => ({
const triggers = (getTriggersInPhase(phaseId) as ReducedTrigger[]).map(t => ({
...t,
label: (() => {
let prefix = "";
if (t.condition?.keyword) {
if (t.condition && typeof t.condition !== "string" && "keyword" in t.condition && typeof t.condition.keyword === "string") {
prefix = `if keywords said: "${t.condition.keyword}"`;
} else if (t.condition?.name) {
} else if (t.condition && typeof t.condition !== "string" && "name" in t.condition && typeof t.condition.name === "string") {
prefix = `if LLM belief: ${t.condition.name}`;
} else { //fallback
prefix = t.label || "Trigger";
prefix = t.name || "Trigger"; // use typed `name` as a reliable fallback
}
const stepLabels = t.plan?.steps?.map((step: any) => {
if (step.text) {
const stepLabels = (t.plan && typeof t.plan !== "string" ? t.plan.steps : []).map((step: ReducedPlanStep) => {
if ("text" in step && typeof step.text === "string") {
return `say: "${step.text}"`;
}
if (step.gesture) {
return `perform gesture: ${step.gesture.name || step.gesture.type}`;
}
if (step.goal) {
}
if ("gesture" in step && step.gesture) {
const g = step.gesture;
return `perform gesture: ${g.name || g.type}`;
}
if ("goal" in step && typeof step.goal === "string") {
return `perform LLM: ${step.goal}`;
}
return "Action"; // Fallback
@@ -144,15 +176,15 @@ const MonitoringPage: React.FC = () => {
...n,
label: n.norm,
}));
const conditionalNorms = (getNormsInPhase(phaseId) as any[])
const conditionalNorms = (getNormsInPhase(phaseId) as ReducedNorm[])
.filter(n => !!n.condition) // Only items with a condition
.map(n => ({
...n,
label: (() => {
let prefix = "";
if (n.condition?.keyword) {
if (n.condition && typeof n.condition !== "string" && "keyword" in n.condition && typeof n.condition.keyword === "string") {
prefix = `if keywords said: "${n.condition.keyword}"`;
} else if (n.condition?.name) {
} else if (n.condition && typeof n.condition !== "string" && "name" in n.condition && typeof n.condition.name === "string") {
prefix = `if LLM belief: ${n.condition.name}`;
}

View File

@@ -66,17 +66,26 @@ export async function playExperiment(): Promise<void> {
}
/**
* Types for the experiment stream messages
*/
export type PhaseUpdate = { type: 'phase_update'; id: string };
export type GoalUpdate = { type: 'goal_update'; id: string };
export type TriggerUpdate = { type: 'trigger_update'; id: string; achieved: boolean };
export type CondNormsStateUpdate = { type: 'cond_norms_state_update'; norms: { id: string; active: boolean }[] };
export type ExperimentStreamData = PhaseUpdate | GoalUpdate | TriggerUpdate | CondNormsStateUpdate | Record<string, unknown>;
/**
* A hook that listens to the experiment stream and logs data to the console.
* It does not render anything.
*/
export function useExperimentLogger(onUpdate?: (data: any) => void) {
export function useExperimentLogger(onUpdate?: (data: ExperimentStreamData) => void) {
useEffect(() => {
const eventSource = new EventSource(`${API_BASE}/experiment_stream`);
eventSource.onmessage = (event) => {
try {
const parsedData = JSON.parse(event.data);
const parsedData = JSON.parse(event.data) as ExperimentStreamData;
if (onUpdate) {
console.log(event.data);
onUpdate(parsedData);