improve components

This commit is contained in:
bracesproul
2025-03-07 12:43:22 -08:00
parent 5d4e33bb52
commit 0607f042e7
5 changed files with 68 additions and 28 deletions

View File

@@ -5,7 +5,6 @@ import { OpenCodeState, OpenCodeUpdate } from "../types";
import { LangGraphRunnableConfig } from "@langchain/langgraph"; import { LangGraphRunnableConfig } from "@langchain/langgraph";
import ComponentMap from "../../uis"; import ComponentMap from "../../uis";
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server"; import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
import { PLAN } from "./planner";
export async function executor( export async function executor(
state: OpenCodeState, state: OpenCodeState,
@@ -18,11 +17,25 @@ export async function executor(
m.getType() === "ai" && m.getType() === "ai" &&
(m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"), (m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"),
) as AIMessage | undefined; ) as AIMessage | undefined;
const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args?.args; const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args as Record<
const numOfExecutedPlanItems: number = string,
planToolCallArgs?.executedPlans?.length ?? 0; any
>;
const nextPlanItem = planToolCallArgs?.remainingPlans?.[0] as
| string
| undefined;
const numOfExecutedPlanItems = planToolCallArgs?.executedPlans?.length ?? 0;
const planItem = PLAN[numOfExecutedPlanItems - 1]; if (!nextPlanItem) {
// All plans have been executed
const successfullyFinishedMsg: AIMessage = {
type: "ai",
id: uuidv4(),
content:
"Successfully completed all the steps in the plan. Please let me know if you need anything else!",
};
return { messages: [successfullyFinishedMsg] };
}
let updateFileContents = ""; let updateFileContents = "";
switch (numOfExecutedPlanItems) { switch (numOfExecutedPlanItems) {
@@ -79,9 +92,8 @@ export async function executor(
{ {
name: "update_file", name: "update_file",
args: { args: {
args: { new_file_content: updateFileContents as any,
new_file_content: updateFileContents, executed_plan_item: nextPlanItem as any,
},
}, },
id: toolCallId, id: toolCallId,
type: "tool_call", type: "tool_call",
@@ -92,7 +104,7 @@ export async function executor(
ui.write("proposed-change", { ui.write("proposed-change", {
toolCallId, toolCallId,
change: updateFileContents, change: updateFileContents,
planItem, planItem: nextPlanItem,
}); });
return { return {

View File

@@ -6,7 +6,7 @@ import { LangGraphRunnableConfig } from "@langchain/langgraph";
import ComponentMap from "../../uis"; import ComponentMap from "../../uis";
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server"; import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
export const PLAN = [ const PLAN = [
"Set up project scaffolding using Create React App and implement basic folder structure for components, styles, and utilities.", "Set up project scaffolding using Create React App and implement basic folder structure for components, styles, and utilities.",
"Create reusable UI components for TodoItem, including styling with CSS modules.", "Create reusable UI components for TodoItem, including styling with CSS modules.",
"Implement state management using React Context to handle todo items, including actions for adding, updating, and deleting todos.", "Implement state management using React Context to handle todo items, including actions for adding, updating, and deleting todos.",
@@ -21,17 +21,34 @@ export async function planner(
): Promise<OpenCodeUpdate> { ): Promise<OpenCodeUpdate> {
const ui = typedUi<typeof ComponentMap>(config); const ui = typedUi<typeof ComponentMap>(config);
const lastUpdateCodeToolCall = state.messages.findLast(
(m) =>
m.getType() === "ai" &&
(m as unknown as AIMessage).tool_calls?.some(
(tc) => tc.name === "update_file",
),
) as AIMessage | undefined;
const lastPlanToolCall = state.messages.findLast( const lastPlanToolCall = state.messages.findLast(
(m) => (m) =>
m.getType() === "ai" && m.getType() === "ai" &&
(m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"), (m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"),
) as AIMessage | undefined; ) as AIMessage | undefined;
const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args?.args;
const executedPlans = planToolCallArgs?.executedPlans ?? [];
const remainingPlans = planToolCallArgs?.remainingPlans ?? PLAN;
const content = const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args as Record<
executedPlans.length > 0 string,
any
>;
const executedPlans: string[] = planToolCallArgs?.executedPlans ?? [];
let remainingPlans: string[] = planToolCallArgs?.remainingPlans ?? PLAN;
const executedPlanItem = lastUpdateCodeToolCall?.tool_calls?.[0]?.args
?.executed_plan_item as string | undefined;
if (executedPlanItem) {
executedPlans.push(executedPlanItem);
remainingPlans = remainingPlans.filter((p) => p !== executedPlanItem);
}
const content = executedPlanItem
? `I've updated the plan list based on the executed plans.` ? `I've updated the plan list based on the executed plans.`
: `I've come up with a detailed plan for building the todo app.`; : `I've come up with a detailed plan for building the todo app.`;
@@ -43,12 +60,10 @@ export async function planner(
tool_calls: [ tool_calls: [
{ {
name: "plan", name: "plan",
args: {
args: { args: {
executedPlans, executedPlans,
remainingPlans, remainingPlans,
}, },
},
id: toolCallId, id: toolCallId,
type: "tool_call", type: "tool_call",
}, },

View File

@@ -8,9 +8,9 @@ interface PlanProps {
export default function Plan(props: PlanProps) { export default function Plan(props: PlanProps) {
return ( return (
<div className="flex flex-col gap-4 w-full max-w-xl p-6 border-[1px] rounded-xl border-slate-500"> <div className="flex flex-col gap-4 w-full max-w-4xl p-6 border-[1px] rounded-xl border-slate-500">
<h2 className="text-2xl font-semibold text-center mb-2">Code Plan</h2> <h2 className="text-2xl font-semibold text-center mb-2">Code Plan</h2>
<div className="grid grid-cols-2 divide-x divide-slate-300"> <div className="grid grid-cols-2 divide-x divide-slate-300 w-full">
<div className="flex flex-col gap-2 pr-6"> <div className="flex flex-col gap-2 pr-6">
<h3 className="text-lg font-medium mb-4 text-slate-700"> <h3 className="text-lg font-medium mb-4 text-slate-700">
Executed Plans Executed Plans

View File

@@ -7,7 +7,8 @@ import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { UIMessage, useStreamContext } from "@langchain/langgraph-sdk/react-ui"; import { UIMessage, useStreamContext } from "@langchain/langgraph-sdk/react-ui";
import { Message } from "@langchain/langgraph-sdk"; import { Message } from "@langchain/langgraph-sdk";
import { DO_NOT_RENDER_ID_PREFIX } from "@/lib/ensure-tool-responses"; import { DO_NOT_RENDER_ID_PREFIX } from "@/lib/ensure-tool-responses";
import { useState } from "react"; import { useEffect, useState } from "react";
import { getToolResponse } from "../../utils/get-tool-response";
interface ProposedChangeProps { interface ProposedChangeProps {
toolCallId: string; toolCallId: string;
@@ -15,6 +16,9 @@ interface ProposedChangeProps {
planItem: string; planItem: string;
} }
const ACCEPTED_CHANGE_CONTENT =
"User accepted the proposed change. Please continue.";
export default function ProposedChange(props: ProposedChangeProps) { export default function ProposedChange(props: ProposedChangeProps) {
const [isAccepted, setIsAccepted] = useState(false); const [isAccepted, setIsAccepted] = useState(false);
@@ -27,7 +31,6 @@ export default function ProposedChange(props: ProposedChangeProps) {
alert("Rejected. (just kidding, you can't reject me silly!)"); alert("Rejected. (just kidding, you can't reject me silly!)");
}; };
const handleAccept = () => { const handleAccept = () => {
const content = "User accepted the proposed change. Please continue.";
thread.submit({ thread.submit({
messages: [ messages: [
{ {
@@ -35,7 +38,7 @@ export default function ProposedChange(props: ProposedChangeProps) {
tool_call_id: props.toolCallId, tool_call_id: props.toolCallId,
id: `${DO_NOT_RENDER_ID_PREFIX}${uuidv4()}`, id: `${DO_NOT_RENDER_ID_PREFIX}${uuidv4()}`,
name: "buy-stock", name: "buy-stock",
content, content: ACCEPTED_CHANGE_CONTENT,
}, },
{ {
type: "human", type: "human",
@@ -47,9 +50,17 @@ export default function ProposedChange(props: ProposedChangeProps) {
setIsAccepted(true); setIsAccepted(true);
}; };
useEffect(() => {
if (typeof window === "undefined" || isAccepted) return;
const toolResponse = getToolResponse(props.toolCallId, thread);
if (toolResponse && toolResponse.content === ACCEPTED_CHANGE_CONTENT) {
setIsAccepted(true);
}
}, []);
if (isAccepted) { if (isAccepted) {
return ( return (
<div className="flex flex-col gap-4 w-full max-w-xl p-4 border-[1px] rounded-xl border-green-300"> <div className="flex flex-col gap-4 w-full max-w-4xl p-4 border-[1px] rounded-xl border-green-300">
<div className="flex flex-col items-start justify-start gap-2"> <div className="flex flex-col items-start justify-start gap-2">
<p className="text-lg font-medium">Accepted Change</p> <p className="text-lg font-medium">Accepted Change</p>
<p className="text-sm font-mono">{props.planItem}</p> <p className="text-sm font-mono">{props.planItem}</p>

View File

@@ -84,7 +84,9 @@ export function HumanMessage({
onSubmit={handleSubmitEdit} onSubmit={handleSubmitEdit}
/> />
) : ( ) : (
<p className="text-right px-4 py-2 rounded-3xl bg-muted">{contentString}</p> <p className="text-right px-4 py-2 rounded-3xl bg-muted">
{contentString}
</p>
)} )}
<div <div