From 216b136a759e2b1252ce260835c84bde2eb5d2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?= Date: Mon, 5 Jan 2026 16:38:06 +0100 Subject: [PATCH] chore: change goal text, correct output for gestures, allow step specific reducing, fix tests/ add tests for new things --- .../components/GestureValueEditor.tsx | 4 + .../visualProgrammingUI/components/Plan.tsx | 37 ++++++++- .../components/PlanEditor.tsx | 4 +- .../visualProgrammingUI/nodes/GoalNode.tsx | 2 +- .../components/GestureValueEditor.test.tsx | 5 +- .../components/PlanEditor.test.tsx | 78 ++++++++++++++++++- 6 files changed, 122 insertions(+), 8 deletions(-) diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/GestureValueEditor.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/GestureValueEditor.tsx index 67f9f16..3b5863a 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/components/GestureValueEditor.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/components/GestureValueEditor.tsx @@ -10,6 +10,7 @@ import styles from './GestureValueEditor.module.css' type GestureValueEditorProps = { value: string; setValue: (value: string) => void; + setType: (value: boolean) => void; placeholder?: string; }; @@ -443,6 +444,7 @@ const GESTURE_SINGLES = [ export default function GestureValueEditor({ value, setValue, + setType, placeholder = "Gesture name", }: GestureValueEditorProps) { @@ -465,10 +467,12 @@ export default function GestureValueEditor({ if (newMode === "single") { setValue(customValue || value); + setType(false); setFilteredSuggestions(GESTURE_SINGLES); setShowSuggestions(true); } else { // Clear value if it does not match a valid tag + setType(true); const isValidTag = GESTURE_TAGS.some( tag => tag.toLowerCase() === value.toLowerCase() ); diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/Plan.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/Plan.tsx index 864c27a..00fa88f 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/components/Plan.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/components/Plan.tsx @@ -17,7 +17,7 @@ export type Goal = { // Actions export type Action = SpeechAction | GestureAction | LLMAction export type SpeechAction = { id: string, text: string, type:"speech" } -export type GestureAction = { id: string, gesture: string, type:"gesture" } +export type GestureAction = { id: string, gesture: string, isTag: boolean, type:"gesture" } export type LLMAction = { id: string, goal: string, type:"llm" } export type ActionTypes = "speech" | "gesture" | "llm"; @@ -29,7 +29,40 @@ export function PlanReduce(plan?: Plan) { return { name: plan.name, id: plan.id, - steps: plan.steps, + steps: plan.steps.map((x) => StepReduce(x)) + } +} + + +// Extract the wanted information from a plan element. +function StepReduce(planElement: PlanElement) { + // We have different types of plan elements, requiring differnt types of output + switch (planElement.type) { + case ("speech"): + return { + id: planElement.id, + text: planElement.text, + } + case ("gesture"): + return { + id: planElement.id, + gesture: { + type: planElement.isTag ? "tag" : "single", + name: planElement.gesture + }, + } + case ("llm"): + return { + id: planElement.id, + goal: planElement.goal, + } + case ("goal"): + return { + id: planElement.id, + plan: planElement.plan, + can_fail: planElement.can_fail, + }; + default: } } diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx index ac41241..19b590b 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx @@ -21,6 +21,7 @@ export default function PlanEditorDialog({ const dialogRef = useRef(null); const [draftPlan, setDraftPlan] = useState(null); const [newActionType, setNewActionType] = useState("speech"); + const [newActionGestureType, setNewActionGestureType] = useState(true); const [newActionValue, setNewActionValue] = useState(""); const { setScrollable } = useFlowStore(); @@ -58,7 +59,7 @@ export default function PlanEditorDialog({ case "speech": return { id, text: newActionValue, type: "speech" }; case "gesture": - return { id, gesture: newActionValue, type: "gesture" }; + return { id, gesture: newActionValue, isTag: newActionGestureType, type: "gesture" }; case "llm": return { id, goal: newActionValue, type: "llm" }; } @@ -127,6 +128,7 @@ export default function PlanEditorDialog({ ) : ( diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx index 75b8b99..ad48a7d 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx @@ -65,7 +65,7 @@ export default function GoalNode({id, data}: NodeProps) {
- +
{data.plan && (
{planIterate ? "" : } diff --git a/test/pages/visProgPage/visualProgrammingUI/components/GestureValueEditor.test.tsx b/test/pages/visProgPage/visualProgrammingUI/components/GestureValueEditor.test.tsx index fc67f25..3bbc205 100644 --- a/test/pages/visProgPage/visualProgrammingUI/components/GestureValueEditor.test.tsx +++ b/test/pages/visProgPage/visualProgrammingUI/components/GestureValueEditor.test.tsx @@ -3,10 +3,11 @@ import userEvent from '@testing-library/user-event'; import { renderWithProviders, screen } from '../../../../test-utils/test-utils.tsx'; import GestureValueEditor from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/GestureValueEditor'; -function TestHarness({ initialValue = '', placeholder = 'Gesture name' } : { initialValue?: string, placeholder?: string }) { +function TestHarness({ initialValue = '', initialType=true, placeholder = 'Gesture name' } : { initialValue?: string, initialType?: boolean, placeholder?: string }) { const [value, setValue] = useState(initialValue); + const [_, setType] = useState(initialType) return ( - + ); } diff --git a/test/pages/visProgPage/visualProgrammingUI/components/PlanEditor.test.tsx b/test/pages/visProgPage/visualProgrammingUI/components/PlanEditor.test.tsx index a4979a5..63d5cfa 100644 --- a/test/pages/visProgPage/visualProgrammingUI/components/PlanEditor.test.tsx +++ b/test/pages/visProgPage/visualProgrammingUI/components/PlanEditor.test.tsx @@ -4,7 +4,7 @@ import { screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '../../../../test-utils/test-utils.tsx'; import PlanEditorDialog from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor'; -import type { Plan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/Plan'; +import { PlanReduce, type Plan } from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/Plan'; import '@testing-library/jest-dom'; // Mock structuredClone @@ -28,12 +28,48 @@ describe('PlanEditorDialog', () => { steps: [], }; + const extendedPlan: Plan = { + id: 'extended-plan-1', + name: 'extended test plan', + steps: [ + // Step 1: A wave tag gesture + { + id: 'firststep', + type: 'gesture', + isTag: true, + gesture: "hello" + }, + + // Step 2: A single tag gesture + { + id: 'secondstep', + type: 'gesture', + isTag: false, + gesture: "somefolder/somegesture" + }, + + // Step 3: A LLM action + { + id: 'thirdstep', + type: 'llm', + goal: 'ask the user something or whatever' + }, + + // Step 4: A speech action + { + id: 'fourthstep', + type: 'speech', + text: "I'm a cyborg ninja :>" + }, + ] + } + const planWithSteps: Plan = { id: 'plan-2', name: 'Existing Plan', steps: [ { id: 'step-1', text: 'Hello world', type: 'speech' as const }, - { id: 'step-2', gesture: 'Wave', type: 'gesture' as const }, + { id: 'step-2', gesture: 'Wave', isTag:true, type: 'gesture' as const }, ], }; @@ -429,4 +465,42 @@ describe('PlanEditorDialog', () => { expect(llmInput).toBeInTheDocument(); }); }); + + describe('Plan reducing', () => { + it('should correctly reduce the plan given the elements of the plan', () => { + const testplan = extendedPlan + const expectedResult = { + name: "extended test plan", + id: "extended-plan-1", + steps: [ + { + id: "firststep", + gesture: { + type: "tag", + name: "hello" + } + }, + { + id: "secondstep", + gesture: { + type: "single", + name: "somefolder/somegesture" + } + }, + { + id: "thirdstep", + goal: "ask the user something or whatever" + }, + { + id: "fourthstep", + text: "I'm a cyborg ninja :>" + } + ] + } + + const actualResult = PlanReduce(testplan) + + expect(actualResult).toEqual(expectedResult) + }); + }) }); \ No newline at end of file