cr
This commit is contained in:
@@ -13,17 +13,19 @@ export async function executor(
|
|||||||
): Promise<OpenCodeUpdate> {
|
): Promise<OpenCodeUpdate> {
|
||||||
const ui = typedUi<typeof ComponentMap>(config);
|
const ui = typedUi<typeof ComponentMap>(config);
|
||||||
|
|
||||||
const numOfUpdateFileCalls = state.messages.filter(
|
const lastPlanToolCall = state.messages.findLast(
|
||||||
(m) =>
|
(m) =>
|
||||||
m.getType() === "ai" &&
|
m.getType() === "ai" &&
|
||||||
(m as unknown as AIMessage).tool_calls?.some(
|
(m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"),
|
||||||
(tc) => tc.name === "update_file",
|
) as AIMessage | undefined;
|
||||||
),
|
const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args?.args;
|
||||||
).length;
|
const numOfExecutedPlanItems: number =
|
||||||
const planItem = PLAN[numOfUpdateFileCalls - 1];
|
planToolCallArgs?.executedPlans?.length ?? 0;
|
||||||
|
|
||||||
|
const planItem = PLAN[numOfExecutedPlanItems - 1];
|
||||||
|
|
||||||
let updateFileContents = "";
|
let updateFileContents = "";
|
||||||
switch (numOfUpdateFileCalls) {
|
switch (numOfExecutedPlanItems) {
|
||||||
case 0:
|
case 0:
|
||||||
updateFileContents = await fs.readFile(
|
updateFileContents = await fs.readFile(
|
||||||
"agent/open-code/nodes/plan-code/step-1.txt",
|
"agent/open-code/nodes/plan-code/step-1.txt",
|
||||||
|
|||||||
@@ -16,22 +16,37 @@ export const PLAN = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export async function planner(
|
export async function planner(
|
||||||
_state: OpenCodeState,
|
state: OpenCodeState,
|
||||||
config: LangGraphRunnableConfig,
|
config: LangGraphRunnableConfig,
|
||||||
): Promise<OpenCodeUpdate> {
|
): Promise<OpenCodeUpdate> {
|
||||||
const ui = typedUi<typeof ComponentMap>(config);
|
const ui = typedUi<typeof ComponentMap>(config);
|
||||||
|
|
||||||
|
const lastPlanToolCall = state.messages.findLast(
|
||||||
|
(m) =>
|
||||||
|
m.getType() === "ai" &&
|
||||||
|
(m as unknown as AIMessage).tool_calls?.some((tc) => tc.name === "plan"),
|
||||||
|
) as AIMessage | undefined;
|
||||||
|
const planToolCallArgs = lastPlanToolCall?.tool_calls?.[0]?.args?.args;
|
||||||
|
const executedPlans = planToolCallArgs?.executedPlans ?? [];
|
||||||
|
const remainingPlans = planToolCallArgs?.remainingPlans ?? PLAN;
|
||||||
|
|
||||||
|
const content =
|
||||||
|
executedPlans.length > 0
|
||||||
|
? `I've updated the plan list based on the executed plans.`
|
||||||
|
: `I've come up with a detailed plan for building the todo app.`;
|
||||||
|
|
||||||
const toolCallId = uuidv4();
|
const toolCallId = uuidv4();
|
||||||
const aiMessage: AIMessage = {
|
const aiMessage: AIMessage = {
|
||||||
type: "ai",
|
type: "ai",
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
content: "I've come up with a detailed plan for building the todo app.",
|
content,
|
||||||
tool_calls: [
|
tool_calls: [
|
||||||
{
|
{
|
||||||
name: "plan",
|
name: "plan",
|
||||||
args: {
|
args: {
|
||||||
args: {
|
args: {
|
||||||
plan: PLAN,
|
executedPlans,
|
||||||
|
remainingPlans,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
id: toolCallId,
|
id: toolCallId,
|
||||||
@@ -42,7 +57,8 @@ export async function planner(
|
|||||||
|
|
||||||
ui.write("code-plan", {
|
ui.write("code-plan", {
|
||||||
toolCallId,
|
toolCallId,
|
||||||
plan: PLAN,
|
executedPlans,
|
||||||
|
remainingPlans,
|
||||||
});
|
});
|
||||||
|
|
||||||
const toolMessage: ToolMessage = {
|
const toolMessage: ToolMessage = {
|
||||||
|
|||||||
@@ -2,19 +2,35 @@ import "./index.css";
|
|||||||
|
|
||||||
interface PlanProps {
|
interface PlanProps {
|
||||||
toolCallId: string;
|
toolCallId: string;
|
||||||
plan: string[];
|
executedPlans: string[];
|
||||||
|
remainingPlans: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
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-4 border-[1px] rounded-xl border-slate-500">
|
<div className="flex flex-col gap-4 w-full max-w-xl p-6 border-[1px] rounded-xl border-slate-500">
|
||||||
<p className="text-lg font-medium">Plan</p>
|
<h2 className="text-2xl font-semibold text-center mb-2">Code Plan</h2>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="grid grid-cols-2 divide-x divide-slate-300">
|
||||||
{props.plan.map((step, index) => (
|
<div className="flex flex-col gap-2 pr-6">
|
||||||
<p key={index} className="font-mono">
|
<h3 className="text-lg font-medium mb-4 text-slate-700">
|
||||||
{index + 1}. {step}
|
Executed Plans
|
||||||
</p>
|
</h3>
|
||||||
))}
|
{props.executedPlans.map((step, index) => (
|
||||||
|
<p key={index} className="font-mono text-sm">
|
||||||
|
{index + 1}. {step}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 pl-6">
|
||||||
|
<h3 className="text-lg font-medium mb-4 text-slate-700">
|
||||||
|
Remaining Plans
|
||||||
|
</h3>
|
||||||
|
{props.remainingPlans.map((step, index) => (
|
||||||
|
<p key={index} className="font-mono text-sm">
|
||||||
|
{props.executedPlans.length + index + 1}. {step}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||||
|
import { UIMessage, useStreamContext } from "@langchain/langgraph-sdk/react-ui";
|
||||||
|
import { Message } from "@langchain/langgraph-sdk";
|
||||||
|
import { DO_NOT_RENDER_ID_PREFIX } from "@/lib/ensure-tool-responses";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
interface ProposedChangeProps {
|
interface ProposedChangeProps {
|
||||||
toolCallId: string;
|
toolCallId: string;
|
||||||
@@ -11,12 +16,72 @@ interface ProposedChangeProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ProposedChange(props: ProposedChangeProps) {
|
export default function ProposedChange(props: ProposedChangeProps) {
|
||||||
const handleReject = () => {};
|
const [isAccepted, setIsAccepted] = useState(false);
|
||||||
const handleAccept = () => {};
|
|
||||||
|
const thread = useStreamContext<
|
||||||
|
{ messages: Message[]; ui: UIMessage[] },
|
||||||
|
{ MetaType: { ui: UIMessage | undefined } }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const handleReject = () => {
|
||||||
|
alert("Rejected. (just kidding, you can't reject me silly!)");
|
||||||
|
};
|
||||||
|
const handleAccept = () => {
|
||||||
|
const content = "User accepted the proposed change. Please continue.";
|
||||||
|
thread.submit({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
type: "tool",
|
||||||
|
tool_call_id: props.toolCallId,
|
||||||
|
id: `${DO_NOT_RENDER_ID_PREFIX}${uuidv4()}`,
|
||||||
|
name: "buy-stock",
|
||||||
|
content,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "human",
|
||||||
|
content: `Accepted change.`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsAccepted(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isAccepted) {
|
||||||
|
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 items-start justify-start gap-2">
|
||||||
|
<p className="text-lg font-medium">Accepted Change</p>
|
||||||
|
<p className="text-sm font-mono">{props.planItem}</p>
|
||||||
|
</div>
|
||||||
|
<ReactMarkdown
|
||||||
|
children={props.change}
|
||||||
|
components={{
|
||||||
|
code(props) {
|
||||||
|
const { children, className, node: _node } = props;
|
||||||
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
|
return match ? (
|
||||||
|
<SyntaxHighlighter
|
||||||
|
children={String(children).replace(/\n$/, "")}
|
||||||
|
language={match[1]}
|
||||||
|
style={coldarkDark}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<code className={className}>{children}</code>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 w-full max-w-xl p-4 border-[1px] rounded-xl border-slate-200">
|
<div className="flex flex-col gap-4 w-full max-w-4xl p-4 border-[1px] rounded-xl border-slate-200">
|
||||||
<p className="text-lg font-medium">Proposed Change</p>
|
<div className="flex flex-col items-start justify-start gap-2">
|
||||||
|
<p className="text-lg font-medium">Proposed Change</p>
|
||||||
|
<p className="text-sm font-mono">{props.planItem}</p>
|
||||||
|
</div>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
children={props.change}
|
children={props.change}
|
||||||
components={{
|
components={{
|
||||||
@@ -36,10 +101,16 @@ export default function ProposedChange(props: ProposedChangeProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-2 items-center w-full">
|
<div className="flex gap-2 items-center w-full">
|
||||||
<Button variant="destructive" onClick={handleReject}>
|
<Button
|
||||||
|
className="cursor-pointer"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleReject}
|
||||||
|
>
|
||||||
Reject
|
Reject
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleAccept}>Accept</Button>
|
<Button className="cursor-pointer" onClick={handleAccept}>
|
||||||
|
Accept
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user