diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/Plan.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/Plan.tsx index 0043c2b..197ca51 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/components/Plan.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/components/Plan.tsx @@ -67,10 +67,36 @@ function StepReduce(planElement: PlanElement, _nodes: Node[]) : Record step.type == "llm").length > 0; + return plan.steps.filter((step) => step.type == "llm").length > 0 || + ( + // Find the goal node of this step + plan.steps.filter((step) => step.type == "goal").map((goalStep) => { + const goalId = goalStep.id; + const goalNode = _nodes.find((x) => x.id === goalId); + // In case we don't find any valid plan, this node doesn't iterate + if (!goalNode || !goalNode.data.plan) return false; + // Otherwise, check if this node can fail - if so, we should have the option to iterate + return (goalNode && goalNode.data.plan && goalNode.data.can_fail) + }) + ).includes(true); +} + +/** + * Checks if any of the plan's goal steps has its can_fail value set to true. + * @param plan: plan to check + * @param _nodes: nodes in flow store. + */ +export function HasCheckingSubGoal(plan: Plan, _nodes: Node[]) { + const goalSteps = plan.steps.filter((x) => x.type == "goal"); + return goalSteps.map((goalStep) => { + // Find the goal node and check its can_fail data boolean. + const goalId = goalStep.id; + const goalNode = _nodes.find((x) => x.id === goalId); + return (goalNode && goalNode.data.can_fail) + }).includes(true); } /** diff --git a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx index 10ce06d..2c2d098 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/components/PlanEditor.tsx @@ -23,6 +23,7 @@ export default function PlanEditorDialog({ const [newActionType, setNewActionType] = useState("speech"); const [newActionGestureType, setNewActionGestureType] = useState(true); const [newActionValue, setNewActionValue] = useState(""); + const [hasInteractedWithPlan, setHasInteractedWithPlan] = useState(false) const { setScrollable } = useFlowStore(); const nodes = useFlowStore().nodes; @@ -56,6 +57,7 @@ export default function PlanEditorDialog({ const buildAction = (): Action => { const id = crypto.randomUUID(); + setHasInteractedWithPlan(true) switch (newActionType) { case "speech": return { id, text: newActionValue, type: "speech" }; @@ -103,7 +105,7 @@ export default function PlanEditorDialog({
{/* Left Side (Action Adder) */}

Add Action

- {(!plan && description && draftPlan.steps.length === 0) && (
+ {(!plan && description && draftPlan.steps.length === 0 && !hasInteractedWithPlan) && (
)} diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx index 06df1e8..0b09b4b 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/BasicBeliefNode.tsx @@ -120,7 +120,7 @@ export default function BasicBeliefNode(props: NodeProps) { wrapping = '"' break; case ("semantic"): - placeholder = "description..." + placeholder = "short description..." wrapping = '"' break; case ("object"): @@ -180,7 +180,7 @@ export default function BasicBeliefNode(props: NodeProps) {
)} diff --git a/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx b/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx index 601126a..449a422 100644 --- a/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx +++ b/src/pages/VisProgPage/visualProgrammingUI/nodes/GoalNode.tsx @@ -9,7 +9,7 @@ import { TextField } from '../../../../components/TextField'; import {MultiConnectionHandle} from "../components/RuleBasedHandle.tsx"; import {allowOnlyConnectionsFromHandle, allowOnlyConnectionsFromType} from "../HandleRules.ts"; import useFlowStore from '../VisProgStores'; -import {DoesPlanIterate, PlanReduce, type Plan } from '../components/Plan'; +import {DoesPlanIterate, HasCheckingSubGoal, PlanReduce, type Plan } from '../components/Plan'; import PlanEditorDialog from '../components/PlanEditor'; import { MultilineTextField } from '../../../../components/MultilineTextField'; import { defaultPlan } from '../components/Plan.default.ts'; @@ -45,10 +45,12 @@ export type GoalNode = Node */ export default function GoalNode({id, data}: NodeProps) { const {updateNodeData} = useFlowStore(); + const _nodes = useFlowStore().nodes; const text_input_id = `goal_${id}_text_input`; const checkbox_id = `goal_${id}_checkbox`; - const planIterate = DoesPlanIterate(data.plan); + const planIterate = DoesPlanIterate(_nodes, data.plan); + const hasCheckSubGoal = data.plan !== undefined && HasCheckingSubGoal(data.plan, _nodes) const setDescription = (value: string) => { updateNodeData(id, {...data, description: value}); @@ -75,7 +77,7 @@ export default function GoalNode({id, data}: NodeProps) { />
- {data.can_fail && (
+ {(data.can_fail || hasCheckSubGoal) && (
) { planIterate ? setFailable(e.target.checked) : setFailable(false)} />
@@ -137,7 +139,7 @@ export function GoalReduce(node: Node, _nodes: Node[]) { id: node.id, name: data.name, description: data.description, - can_fail: data.can_fail, + can_fail: data.can_fail || (data.plan && HasCheckingSubGoal(data.plan, _nodes)), plan: data.plan ? PlanReduce(_nodes, data.plan) : "", } }