implement components
This commit is contained in:
@@ -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 { 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> {
|
||||
throw new Error("Not implemented" + state);
|
||||
}
|
||||
export async function executor(
|
||||
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 { AIMessage } from "@langchain/langgraph-sdk";
|
||||
import { AIMessage, ToolMessage } from "@langchain/langgraph-sdk";
|
||||
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 = {
|
||||
type: "ai",
|
||||
id: uuidv4(),
|
||||
content: "",
|
||||
content: "I've come up with a detailed plan for building the todo app.",
|
||||
tool_calls: [
|
||||
{
|
||||
name: "update_file",
|
||||
name: "plan",
|
||||
args: {
|
||||
args: {
|
||||
new_file_content: "ADD_CODE_HERE"
|
||||
plan: PLAN,
|
||||
},
|
||||
},
|
||||
id: uuidv4(),
|
||||
id: toolCallId,
|
||||
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(),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user