implement components
This commit is contained in:
@@ -6,9 +6,11 @@ import { stockbrokerGraph } from "./stockbroker";
|
|||||||
import { ChatOpenAI } from "@langchain/openai";
|
import { ChatOpenAI } from "@langchain/openai";
|
||||||
import { tripPlannerGraph } from "./trip-planner";
|
import { tripPlannerGraph } from "./trip-planner";
|
||||||
import { formatMessages } from "./utils/format-messages";
|
import { formatMessages } from "./utils/format-messages";
|
||||||
|
import { graph as openCodeGraph } from "./open-code";
|
||||||
|
|
||||||
const allToolDescriptions = `- stockbroker: can fetch the price of a ticker, purchase/sell a ticker, or get the user's portfolio
|
const allToolDescriptions = `- stockbroker: can fetch the price of a ticker, purchase/sell a ticker, or get the user's portfolio
|
||||||
- tripPlanner: helps the user plan their trip. it can suggest restaurants, and places to stay in any given location.`;
|
- tripPlanner: helps the user plan their trip. it can suggest restaurants, and places to stay in any given location.
|
||||||
|
- openCode: can write code for the user. call this tool when the user asks you to write code`;
|
||||||
|
|
||||||
async function router(
|
async function router(
|
||||||
state: GenerativeUIState,
|
state: GenerativeUIState,
|
||||||
@@ -19,7 +21,7 @@ ${allToolDescriptions}
|
|||||||
`;
|
`;
|
||||||
const routerSchema = z.object({
|
const routerSchema = z.object({
|
||||||
route: z
|
route: z
|
||||||
.enum(["stockbroker", "tripPlanner", "generalInput"])
|
.enum(["stockbroker", "tripPlanner", "openCode", "generalInput"])
|
||||||
.describe(routerDescription),
|
.describe(routerDescription),
|
||||||
});
|
});
|
||||||
const routerTool = {
|
const routerTool = {
|
||||||
@@ -73,7 +75,7 @@ Please pick the proper route based on the most recent message, in the context of
|
|||||||
|
|
||||||
function handleRoute(
|
function handleRoute(
|
||||||
state: GenerativeUIState,
|
state: GenerativeUIState,
|
||||||
): "stockbroker" | "tripPlanner" | "generalInput" {
|
): "stockbroker" | "tripPlanner" | "openCode" | "generalInput" {
|
||||||
return state.next;
|
return state.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,16 +106,19 @@ const builder = new StateGraph(GenerativeUIAnnotation)
|
|||||||
.addNode("router", router)
|
.addNode("router", router)
|
||||||
.addNode("stockbroker", stockbrokerGraph)
|
.addNode("stockbroker", stockbrokerGraph)
|
||||||
.addNode("tripPlanner", tripPlannerGraph)
|
.addNode("tripPlanner", tripPlannerGraph)
|
||||||
|
.addNode("openCode", openCodeGraph)
|
||||||
.addNode("generalInput", handleGeneralInput)
|
.addNode("generalInput", handleGeneralInput)
|
||||||
|
|
||||||
.addConditionalEdges("router", handleRoute, [
|
.addConditionalEdges("router", handleRoute, [
|
||||||
"stockbroker",
|
"stockbroker",
|
||||||
"tripPlanner",
|
"tripPlanner",
|
||||||
|
"openCode",
|
||||||
"generalInput",
|
"generalInput",
|
||||||
])
|
])
|
||||||
.addEdge(START, "router")
|
.addEdge(START, "router")
|
||||||
.addEdge("stockbroker", END)
|
.addEdge("stockbroker", END)
|
||||||
.addEdge("tripPlanner", END)
|
.addEdge("tripPlanner", END)
|
||||||
|
.addEdge("openCode", END)
|
||||||
.addEdge("generalInput", END);
|
.addEdge("generalInput", END);
|
||||||
|
|
||||||
export const graph = builder.compile();
|
export const graph = builder.compile();
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
import { END, START, StateGraph } from "@langchain/langgraph";
|
import { END, START, StateGraph } from "@langchain/langgraph";
|
||||||
import { OpenCodeAnnotation, OpenCodeState } from "./types";
|
import { OpenCodeAnnotation } from "./types";
|
||||||
import { planner } from "./nodes/planner";
|
import { planner } from "./nodes/planner";
|
||||||
import { interrupt } from "./nodes/interrupt";
|
|
||||||
import { executor } from "./nodes/executor";
|
import { executor } from "./nodes/executor";
|
||||||
|
|
||||||
function handleRoutingFromExecutor(state: OpenCodeState): "executor" | "interrupt" {
|
|
||||||
const lastAIMessage = state.messages.findLast((m) => m.getType() === "ai");
|
|
||||||
if (lastAIMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRoutingFromInterrupt(state: OpenCodeState): "executor" | typeof END {}
|
|
||||||
|
|
||||||
const workflow = new StateGraph(OpenCodeAnnotation)
|
const workflow = new StateGraph(OpenCodeAnnotation)
|
||||||
.addNode("planner", planner)
|
.addNode("planner", planner)
|
||||||
.addNode("executor", executor)
|
.addNode("executor", executor)
|
||||||
.addNode("interrupt", interrupt)
|
|
||||||
.addEdge(START, "planner")
|
.addEdge(START, "planner")
|
||||||
.addEdge("planner", "executor")
|
.addEdge("planner", "executor")
|
||||||
.addConditionalEdges("executor", handleRoutingFromExecutor, ["executor", "interrupt"])
|
.addEdge("executor", END);
|
||||||
.addConditionalEdges("interrupt", handleRoutingFromInterrupt, ["executor", END])
|
|
||||||
|
export const graph = workflow.compile();
|
||||||
|
graph.name = "Open Code Graph";
|
||||||
|
|||||||
@@ -1,5 +1,101 @@
|
|||||||
|
import fs from "fs/promises";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { AIMessage } from "@langchain/langgraph-sdk";
|
||||||
import { OpenCodeState, OpenCodeUpdate } from "../types";
|
import { OpenCodeState, OpenCodeUpdate } from "../types";
|
||||||
|
import { LangGraphRunnableConfig } from "@langchain/langgraph";
|
||||||
|
import ComponentMap from "../../uis";
|
||||||
|
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
|
||||||
|
import { PLAN } from "./planner";
|
||||||
|
|
||||||
export async function executor(state: OpenCodeState): Promise<OpenCodeUpdate> {
|
export async function executor(
|
||||||
throw new Error("Not implemented" + state);
|
state: OpenCodeState,
|
||||||
|
config: LangGraphRunnableConfig,
|
||||||
|
): Promise<OpenCodeUpdate> {
|
||||||
|
const ui = typedUi<typeof ComponentMap>(config);
|
||||||
|
|
||||||
|
const numOfUpdateFileCalls = state.messages.filter(
|
||||||
|
(m) =>
|
||||||
|
m.getType() === "ai" &&
|
||||||
|
(m as unknown as AIMessage).tool_calls?.some(
|
||||||
|
(tc) => tc.name === "update_file",
|
||||||
|
),
|
||||||
|
).length;
|
||||||
|
const planItem = PLAN[numOfUpdateFileCalls - 1];
|
||||||
|
|
||||||
|
let updateFileContents = "";
|
||||||
|
switch (numOfUpdateFileCalls) {
|
||||||
|
case 0:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-1.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-2.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-3.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-4.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-5.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
updateFileContents = await fs.readFile(
|
||||||
|
"agent/open-code/nodes/plan-code/step-6.txt",
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
updateFileContents = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updateFileContents) {
|
||||||
|
throw new Error("No file updates found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolCallId = uuidv4();
|
||||||
|
const aiMessage: AIMessage = {
|
||||||
|
type: "ai",
|
||||||
|
id: uuidv4(),
|
||||||
|
content: "",
|
||||||
|
tool_calls: [
|
||||||
|
{
|
||||||
|
name: "update_file",
|
||||||
|
args: {
|
||||||
|
args: {
|
||||||
|
new_file_content: updateFileContents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
id: toolCallId,
|
||||||
|
type: "tool_call",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.write("proposed-change", {
|
||||||
|
toolCallId,
|
||||||
|
change: updateFileContents,
|
||||||
|
planItem,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [aiMessage],
|
||||||
|
ui: ui.collect as OpenCodeUpdate["ui"],
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { OpenCodeState, OpenCodeUpdate } from "../types";
|
|
||||||
|
|
||||||
export async function interrupt(state: OpenCodeState): Promise<OpenCodeUpdate> {
|
|
||||||
throw new Error("Not implemented" + state);
|
|
||||||
}
|
|
||||||
5
agent/open-code/nodes/plan-code/step-1.txt
Normal file
5
agent/open-code/nodes/plan-code/step-1.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
```bash
|
||||||
|
npx create-react-app todo-app --template typescript
|
||||||
|
cd todo-app
|
||||||
|
mkdir -p src/{components,styles,utils}
|
||||||
|
```
|
||||||
21
agent/open-code/nodes/plan-code/step-2.txt
Normal file
21
agent/open-code/nodes/plan-code/step-2.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
```tsx
|
||||||
|
// src/components/TodoItem.tsx
|
||||||
|
import React from 'react';
|
||||||
|
import styles from '../styles/TodoItem.module.css';
|
||||||
|
|
||||||
|
interface TodoItemProps {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
completed: boolean;
|
||||||
|
onToggle: (id: string) => void;
|
||||||
|
onDelete: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TodoItem: React.FC<TodoItemProps> = ({ id, text, completed, onToggle, onDelete }) => (
|
||||||
|
<div className={styles.todoItem}>
|
||||||
|
<input type='checkbox' checked={completed} onChange={() => onToggle(id)} />
|
||||||
|
<span className={completed ? styles.completed : ''}>{text}</span>
|
||||||
|
<button onClick={() => onDelete(id)}>Delete</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
22
agent/open-code/nodes/plan-code/step-3.txt
Normal file
22
agent/open-code/nodes/plan-code/step-3.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
```tsx
|
||||||
|
// src/context/TodoContext.tsx
|
||||||
|
import React, { createContext, useContext, useReducer } from 'react';
|
||||||
|
|
||||||
|
type Todo = { id: string; text: string; completed: boolean; };
|
||||||
|
|
||||||
|
type TodoState = { todos: Todo[]; };
|
||||||
|
type TodoAction =
|
||||||
|
| { type: 'ADD_TODO'; payload: string }
|
||||||
|
| { type: 'TOGGLE_TODO'; payload: string }
|
||||||
|
| { type: 'DELETE_TODO'; payload: string };
|
||||||
|
|
||||||
|
const TodoContext = createContext<{
|
||||||
|
state: TodoState;
|
||||||
|
dispatch: React.Dispatch<TodoAction>;
|
||||||
|
} | undefined>(undefined);
|
||||||
|
|
||||||
|
export const TodoProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const [state, dispatch] = useReducer(todoReducer, { todos: [] });
|
||||||
|
return <TodoContext.Provider value={{ state, dispatch }}>{children}</TodoContext.Provider>;
|
||||||
|
};
|
||||||
|
```
|
||||||
33
agent/open-code/nodes/plan-code/step-4.txt
Normal file
33
agent/open-code/nodes/plan-code/step-4.txt
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
```tsx
|
||||||
|
// src/components/AddTodo.tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import styles from '../styles/AddTodo.module.css';
|
||||||
|
|
||||||
|
export const AddTodo: React.FC<{ onAdd: (text: string) => void }> = ({ onAdd }) => {
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!text.trim()) {
|
||||||
|
setError('Todo text cannot be empty');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onAdd(text.trim());
|
||||||
|
setText('');
|
||||||
|
setError('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit} className={styles.form}>
|
||||||
|
<input
|
||||||
|
value={text}
|
||||||
|
onChange={(e) => setText(e.target.value)}
|
||||||
|
placeholder='Add a new todo'
|
||||||
|
/>
|
||||||
|
{error && <div className={styles.error}>{error}</div>}
|
||||||
|
<button type='submit'>Add Todo</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
22
agent/open-code/nodes/plan-code/step-5.txt
Normal file
22
agent/open-code/nodes/plan-code/step-5.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
```tsx
|
||||||
|
// src/components/TodoFilters.tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type FilterType = 'all' | 'active' | 'completed';
|
||||||
|
|
||||||
|
export const TodoFilters: React.FC<{
|
||||||
|
currentFilter: FilterType;
|
||||||
|
onFilterChange: (filter: FilterType) => void;
|
||||||
|
onSortChange: (ascending: boolean) => void;
|
||||||
|
}> = ({ currentFilter, onFilterChange, onSortChange }) => (
|
||||||
|
<div>
|
||||||
|
<select value={currentFilter} onChange={(e) => onFilterChange(e.target.value as FilterType)}>
|
||||||
|
<option value='all'>All</option>
|
||||||
|
<option value='active'>Active</option>
|
||||||
|
<option value='completed'>Completed</option>
|
||||||
|
</select>
|
||||||
|
<button onClick={() => onSortChange(true)}>Sort A-Z</button>
|
||||||
|
<button onClick={() => onSortChange(false)}>Sort Z-A</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
13
agent/open-code/nodes/plan-code/step-6.txt
Normal file
13
agent/open-code/nodes/plan-code/step-6.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
```tsx
|
||||||
|
// src/utils/storage.ts
|
||||||
|
const STORAGE_KEY = 'todos';
|
||||||
|
|
||||||
|
export const saveTodos = (todos: Todo[]) => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadTodos = (): Todo[] => {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
|
return stored ? JSON.parse(stored) : [];
|
||||||
|
};
|
||||||
|
```
|
||||||
0
agent/open-code/nodes/plan-code/step-7.txt
Normal file
0
agent/open-code/nodes/plan-code/step-7.txt
Normal file
@@ -1,25 +1,60 @@
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { AIMessage } from "@langchain/langgraph-sdk";
|
import { AIMessage, ToolMessage } from "@langchain/langgraph-sdk";
|
||||||
import { OpenCodeState, OpenCodeUpdate } from "../types";
|
import { OpenCodeState, OpenCodeUpdate } from "../types";
|
||||||
|
import { DO_NOT_RENDER_ID_PREFIX } from "@/lib/ensure-tool-responses";
|
||||||
|
import { LangGraphRunnableConfig } from "@langchain/langgraph";
|
||||||
|
import ComponentMap from "../../uis";
|
||||||
|
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
|
||||||
|
|
||||||
export async function planner(state: OpenCodeState): Promise<OpenCodeUpdate> {
|
export const PLAN = [
|
||||||
|
"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.",
|
||||||
|
"Implement state management using React Context to handle todo items, including actions for adding, updating, and deleting todos.",
|
||||||
|
"Add form functionality for creating new todos with input validation and error handling.",
|
||||||
|
"Create filtering and sorting capabilities to allow users to view completed, active, or all todos.",
|
||||||
|
"Implement local storage integration to persist todo items between page refreshes.",
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function planner(
|
||||||
|
_state: OpenCodeState,
|
||||||
|
config: LangGraphRunnableConfig,
|
||||||
|
): Promise<OpenCodeUpdate> {
|
||||||
|
const ui = typedUi<typeof ComponentMap>(config);
|
||||||
|
|
||||||
|
const toolCallId = uuidv4();
|
||||||
const aiMessage: AIMessage = {
|
const aiMessage: AIMessage = {
|
||||||
type: "ai",
|
type: "ai",
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
content: "",
|
content: "I've come up with a detailed plan for building the todo app.",
|
||||||
tool_calls: [
|
tool_calls: [
|
||||||
{
|
{
|
||||||
name: "update_file",
|
name: "plan",
|
||||||
args: {
|
args: {
|
||||||
args: {
|
args: {
|
||||||
new_file_content: "ADD_CODE_HERE"
|
plan: PLAN,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
id: uuidv4(),
|
id: toolCallId,
|
||||||
type: "tool_call",
|
type: "tool_call",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
const toolMessage = {}
|
ui.write("code-plan", {
|
||||||
|
toolCallId,
|
||||||
|
plan: PLAN,
|
||||||
|
});
|
||||||
|
|
||||||
|
const toolMessage: ToolMessage = {
|
||||||
|
type: "tool",
|
||||||
|
id: `${DO_NOT_RENDER_ID_PREFIX}${uuidv4()}`,
|
||||||
|
tool_call_id: toolCallId,
|
||||||
|
content: "User has approved the plan.",
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
messages: [aiMessage, toolMessage],
|
||||||
|
ui: ui.collect as OpenCodeUpdate["ui"],
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ export const OpenCodeAnnotation = Annotation.Root({
|
|||||||
messages: GenerativeUIAnnotation.spec.messages,
|
messages: GenerativeUIAnnotation.spec.messages,
|
||||||
ui: GenerativeUIAnnotation.spec.ui,
|
ui: GenerativeUIAnnotation.spec.ui,
|
||||||
timestamp: GenerativeUIAnnotation.spec.timestamp,
|
timestamp: GenerativeUIAnnotation.spec.timestamp,
|
||||||
next: Annotation<"executor" | "interrupt">
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type OpenCodeState = typeof OpenCodeAnnotation.State;
|
export type OpenCodeState = typeof OpenCodeAnnotation.State;
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export const GenerativeUIAnnotation = Annotation.Root({
|
|||||||
UIMessage | RemoveUIMessage | (UIMessage | RemoveUIMessage)[]
|
UIMessage | RemoveUIMessage | (UIMessage | RemoveUIMessage)[]
|
||||||
>({ default: () => [], reducer: uiMessageReducer }),
|
>({ default: () => [], reducer: uiMessageReducer }),
|
||||||
timestamp: Annotation<number>,
|
timestamp: Annotation<number>,
|
||||||
next: Annotation<"stockbroker" | "tripPlanner" | "generalInput">(),
|
next: Annotation<
|
||||||
|
"stockbroker" | "tripPlanner" | "openCode" | "generalInput"
|
||||||
|
>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type GenerativeUIState = typeof GenerativeUIAnnotation.State;
|
export type GenerativeUIState = typeof GenerativeUIAnnotation.State;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import BookAccommodation from "./trip-planner/book-accommodation";
|
|||||||
import RestaurantsList from "./trip-planner/restaurants-list";
|
import RestaurantsList from "./trip-planner/restaurants-list";
|
||||||
import BookRestaurant from "./trip-planner/book-restaurant";
|
import BookRestaurant from "./trip-planner/book-restaurant";
|
||||||
import BuyStock from "./stockbroker/buy-stock";
|
import BuyStock from "./stockbroker/buy-stock";
|
||||||
|
import Plan from "./open-code/plan";
|
||||||
|
import ProposedChange from "./open-code/proposed-change";
|
||||||
|
|
||||||
const ComponentMap = {
|
const ComponentMap = {
|
||||||
"stock-price": StockPrice,
|
"stock-price": StockPrice,
|
||||||
@@ -14,5 +16,7 @@ const ComponentMap = {
|
|||||||
"restaurants-list": RestaurantsList,
|
"restaurants-list": RestaurantsList,
|
||||||
"book-restaurant": BookRestaurant,
|
"book-restaurant": BookRestaurant,
|
||||||
"buy-stock": BuyStock,
|
"buy-stock": BuyStock,
|
||||||
|
"code-plan": Plan,
|
||||||
|
"proposed-change": ProposedChange,
|
||||||
} as const;
|
} as const;
|
||||||
export default ComponentMap;
|
export default ComponentMap;
|
||||||
|
|||||||
1
agent/uis/open-code/plan/index.css
Normal file
1
agent/uis/open-code/plan/index.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
21
agent/uis/open-code/plan/index.tsx
Normal file
21
agent/uis/open-code/plan/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
interface PlanProps {
|
||||||
|
toolCallId: string;
|
||||||
|
plan: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Plan(props: PlanProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 w-full max-w-xl p-4 border-[1px] rounded-xl border-slate-500">
|
||||||
|
<p className="text-lg font-medium">Plan</p>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{props.plan.map((step, index) => (
|
||||||
|
<p key={index} className="font-mono">
|
||||||
|
{index + 1}. {step}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
122
agent/uis/open-code/proposed-change/index.css
Normal file
122
agent/uis/open-code/proposed-change/index.css
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.145 0 0);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.145 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.145 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.985 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.396 0.141 25.723);
|
||||||
|
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||||
|
--border: oklch(0.269 0 0);
|
||||||
|
--input: oklch(0.269 0 0);
|
||||||
|
--ring: oklch(0.439 0 0);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(0.269 0 0);
|
||||||
|
--sidebar-ring: oklch(0.439 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
46
agent/uis/open-code/proposed-change/index.tsx
Normal file
46
agent/uis/open-code/proposed-change/index.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import "./index.css";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
|
import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||||
|
|
||||||
|
interface ProposedChangeProps {
|
||||||
|
toolCallId: string;
|
||||||
|
change: string;
|
||||||
|
planItem: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProposedChange(props: ProposedChangeProps) {
|
||||||
|
const handleReject = () => {};
|
||||||
|
const handleAccept = () => {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 w-full max-w-xl p-4 border-[1px] rounded-xl border-slate-200">
|
||||||
|
<p className="text-lg font-medium">Proposed Change</p>
|
||||||
|
<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 className="flex gap-2 items-center w-full">
|
||||||
|
<Button variant="destructive" onClick={handleReject}>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleAccept}>Accept</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user