From 3314821e195d9b06c80e9eccc38d6b267afaf98a Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 13 Jun 2025 17:08:26 -0700 Subject: [PATCH] fix: Improve plan interrupt UI (#167) * fix: Improve plan interrupt UI * format * remove old interrupt code * cr * bump dep * bump deps * fix deps * cr * cr * cr --- apps/open-swe/package.json | 4 +- apps/open-swe/src/subgraphs/planner/types.ts | 21 +-- apps/web/package.json | 4 +- apps/web/src/components/plan/plan-viewer.tsx | 154 ++++++++++++++---- .../web/src/components/plan/proposed-plan.tsx | 46 ++++++ .../components/inbox-item-input.tsx | 111 ++----------- .../agent-inbox/hooks/useProposedPlan.tsx | 81 +++++++++ .../components/thread/agent-inbox/index.tsx | 7 + apps/web/src/components/thread/index.tsx | 33 +++- apps/web/src/lib/plan-utils.ts | 26 ++- package.json | 3 +- packages/shared/package.json | 4 +- packages/shared/src/open-swe/types.ts | 21 +-- yarn.lock | 42 ++--- 14 files changed, 375 insertions(+), 182 deletions(-) create mode 100644 apps/web/src/components/plan/proposed-plan.tsx create mode 100644 apps/web/src/components/thread/agent-inbox/hooks/useProposedPlan.tsx diff --git a/apps/open-swe/package.json b/apps/open-swe/package.json index 4d7206b5..251f7ec1 100644 --- a/apps/open-swe/package.json +++ b/apps/open-swe/package.json @@ -26,7 +26,7 @@ "@langchain/anthropic": "^0.3.20", "@langchain/core": "^0.3.56", "@langchain/google-genai": "^0.2.9", - "@langchain/langgraph": "^0.3.1", + "@langchain/langgraph": "^0.3.3", "@langchain/langgraph-sdk": "^0.0.83", "@langchain/openai": "^0.5.10", "@octokit/rest": "^22.0.0", @@ -35,7 +35,7 @@ "langchain": "^0.3.26", "langsmith": "^0.3.29", "uuid": "^11.0.5", - "zod": "^3.23.8" + "zod": "^3.25.32" }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", diff --git a/apps/open-swe/src/subgraphs/planner/types.ts b/apps/open-swe/src/subgraphs/planner/types.ts index 8c9532ab..41ec633a 100644 --- a/apps/open-swe/src/subgraphs/planner/types.ts +++ b/apps/open-swe/src/subgraphs/planner/types.ts @@ -6,19 +6,16 @@ import { GraphAnnotation } from "@open-swe/shared/open-swe/types"; import { withLangGraph } from "@langchain/langgraph/zod"; export const PlannerGraphStateObj = GraphAnnotation.extend({ - plannerMessages: withLangGraph( - z.custom(), - { - reducer: { - schema: z.custom(), - fn: messagesStateReducer, - }, - jsonSchemaExtra: { - langgraph_type: "messages", - }, - default: () => [], + plannerMessages: withLangGraph(z.custom(), { + reducer: { + schema: z.custom(), + fn: messagesStateReducer, }, - ), + jsonSchemaExtra: { + langgraph_type: "messages", + }, + default: () => [], + }), }); export type PlannerGraphState = z.infer; diff --git a/apps/web/package.json b/apps/web/package.json index d5bbc5c1..175f95b2 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@langchain/core": "^0.3.57", - "@langchain/langgraph": "^0.3.1", + "@langchain/langgraph": "^0.3.3", "@langchain/langgraph-sdk": "^0.0.83", "@open-swe/shared": "*", "@radix-ui/react-alert-dialog": "^1.1.14", @@ -65,7 +65,7 @@ "tailwindcss-animate": "^1.0.7", "use-stick-to-bottom": "^1.0.46", "uuid": "^11.1.0", - "zod": "^3.24.2", + "zod": "^3.25.32", "zustand": "^5.0.5" }, "devDependencies": { diff --git a/apps/web/src/components/plan/plan-viewer.tsx b/apps/web/src/components/plan/plan-viewer.tsx index b6fee228..71f5bcf6 100644 --- a/apps/web/src/components/plan/plan-viewer.tsx +++ b/apps/web/src/components/plan/plan-viewer.tsx @@ -1,18 +1,26 @@ -import { Check, Clock, Play } from "lucide-react"; +import { Check, Clock, Pencil, Play, Trash } from "lucide-react"; import { cn } from "@/lib/utils"; import { PlanItem } from "@open-swe/shared/open-swe/types"; +import { TooltipIconButton } from "../ui/tooltip-icon-button"; +import { Dispatch, SetStateAction, useState } from "react"; +import { Textarea } from "../ui/textarea"; +import { Button } from "../ui/button"; interface PlanViewerProps { planItems: PlanItem[]; className?: string; isProposedPlan?: boolean; + setPlanItems?: Dispatch>; } export function PlanViewer({ planItems, className, isProposedPlan, + setPlanItems, }: PlanViewerProps) { + const [isEditing, setIsEditing] = useState>({}); + const [newPlanItem, setNewPlanItem] = useState(""); const currentTaskIndex = planItems .filter((item) => !item.completed) .reduce( @@ -77,37 +85,96 @@ export function PlanViewer({ {/* Task Content */}
-
- - Step {item.index + 1} - - {status === "current" && ( - - In Progress - - )} - {status === "completed" && ( - - Done - - )} - {status === "proposed" && ( - - Proposed +
+
+ + Step {item.index + 1} + {status === "current" && ( + + In Progress + + )} + {status === "completed" && ( + + Done + + )} + {status === "proposed" && ( + + Proposed + + )} +
+ {isProposedPlan && ( +
+ + setIsEditing((p) => ({ + ...p, + [item.index]: !p[item.index], + })) + } + tooltip={ + isEditing[item.index] ? `Save Plan` : `Edit Plan` + } + > + {isEditing[item.index] ? ( + + ) : ( + + )} + + + setPlanItems?.((prev) => { + const newArr = prev.filter( + (i) => i.index !== item.index, + ); + return newArr.map((i, index) => ({ + ...i, + index, + })); + }) + } + tooltip="Delete Plan" + className="transition-colors hover:text-red-500" + > + + +
)}
-

- {item.plan} -

+ {isEditing[item.index] ? ( +