finish generate logic

This commit is contained in:
Ishaan Dey 2024-05-03 00:52:01 -07:00
parent e6cf993b6a
commit 2bfaf428d9
5 changed files with 189 additions and 75 deletions

View File

@ -100,7 +100,6 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
})); }));
socket.on("createFile", (name) => __awaiter(void 0, void 0, void 0, function* () { socket.on("createFile", (name) => __awaiter(void 0, void 0, void 0, function* () {
const id = `projects/${data.id}/${name}`; const id = `projects/${data.id}/${name}`;
console.log("create file", id, name);
fs_1.default.writeFile(path_1.default.join(dirName, id), "", function (err) { fs_1.default.writeFile(path_1.default.join(dirName, id), "", function (err) {
if (err) if (err)
throw err; throw err;
@ -143,14 +142,12 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
callback(newFiles.files); callback(newFiles.files);
})); }));
socket.on("createTerminal", ({ id }) => { socket.on("createTerminal", ({ id }) => {
console.log("creating terminal, id=" + id);
const pty = (0, node_pty_1.spawn)(os_1.default.platform() === "win32" ? "cmd.exe" : "bash", [], { const pty = (0, node_pty_1.spawn)(os_1.default.platform() === "win32" ? "cmd.exe" : "bash", [], {
name: "xterm", name: "xterm",
cols: 100, cols: 100,
cwd: path_1.default.join(dirName, "projects", data.id), cwd: path_1.default.join(dirName, "projects", data.id),
}); });
const onData = pty.onData((data) => { const onData = pty.onData((data) => {
console.log(data);
socket.emit("terminalResponse", { socket.emit("terminalResponse", {
// data: Buffer.from(data, "utf-8").toString("base64"), // data: Buffer.from(data, "utf-8").toString("base64"),
data, data,
@ -165,11 +162,7 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
}; };
}); });
socket.on("terminalData", (id, data) => { socket.on("terminalData", (id, data) => {
// socket.on("terminalData", (data: string) => {
console.log(`Received data for terminal ${id}: ${data}`);
// pty.write(data)
if (!terminals[id]) { if (!terminals[id]) {
console.log("terminal not found");
console.log("terminals", terminals); console.log("terminals", terminals);
return; return;
} }
@ -180,6 +173,34 @@ io.on("connection", (socket) => __awaiter(void 0, void 0, void 0, function* () {
console.log("Error writing to terminal", e); console.log("Error writing to terminal", e);
} }
}); });
socket.on("generateCode", (fileName, code, line, instructions, callback) => __awaiter(void 0, void 0, void 0, function* () {
console.log("Generating code...");
const res = yield fetch(`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_USER_ID}/ai/run/@cf/meta/llama-3-8b-instruct`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.CF_API_TOKEN}`,
},
body: JSON.stringify({
messages: [
{
role: "system",
content: "You are an expert coding assistant. You read code from a file, and you suggest new code to add to the file. You may be given instructions on what to generate, which you should follow. You should generate code that is correct, efficient, and follows best practices. You should also generate code that is clear and easy to read.",
},
{
role: "user",
content: `The file is called ${fileName}. Here are my instructions on what to generate: ${instructions}. Suggest me code to insert at line ${line} in my file.
My code file content: ${code}
Return only the code, and nothing else. Do not include backticks.`,
},
],
}),
});
const json = yield res.json();
callback(json);
}));
socket.on("disconnect", () => { socket.on("disconnect", () => {
Object.entries(terminals).forEach((t) => { Object.entries(terminals).forEach((t) => {
const { terminal, onData, onExit } = t[1]; const { terminal, onData, onExit } = t[1];

View File

@ -116,7 +116,6 @@ io.on("connection", async (socket) => {
socket.on("createFile", async (name: string) => { socket.on("createFile", async (name: string) => {
const id = `projects/${data.id}/${name}` const id = `projects/${data.id}/${name}`
console.log("create file", id, name)
fs.writeFile(path.join(dirName, id), "", function (err) { fs.writeFile(path.join(dirName, id), "", function (err) {
if (err) throw err if (err) throw err
@ -170,7 +169,6 @@ io.on("connection", async (socket) => {
}) })
socket.on("createTerminal", ({ id }: { id: string }) => { socket.on("createTerminal", ({ id }: { id: string }) => {
console.log("creating terminal, id=" + id)
const pty = spawn(os.platform() === "win32" ? "cmd.exe" : "bash", [], { const pty = spawn(os.platform() === "win32" ? "cmd.exe" : "bash", [], {
name: "xterm", name: "xterm",
cols: 100, cols: 100,
@ -178,7 +176,6 @@ io.on("connection", async (socket) => {
}) })
const onData = pty.onData((data) => { const onData = pty.onData((data) => {
console.log(data)
socket.emit("terminalResponse", { socket.emit("terminalResponse", {
// data: Buffer.from(data, "utf-8").toString("base64"), // data: Buffer.from(data, "utf-8").toString("base64"),
data, data,
@ -197,12 +194,7 @@ io.on("connection", async (socket) => {
}) })
socket.on("terminalData", (id: string, data: string) => { socket.on("terminalData", (id: string, data: string) => {
// socket.on("terminalData", (data: string) => {
console.log(`Received data for terminal ${id}: ${data}`)
// pty.write(data)
if (!terminals[id]) { if (!terminals[id]) {
console.log("terminal not found")
console.log("terminals", terminals) console.log("terminals", terminals)
return return
} }
@ -214,6 +206,49 @@ io.on("connection", async (socket) => {
} }
}) })
socket.on(
"generateCode",
async (
fileName: string,
code: string,
line: number,
instructions: string,
callback
) => {
console.log("Generating code...")
const res = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_USER_ID}/ai/run/@cf/meta/llama-3-8b-instruct`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.CF_API_TOKEN}`,
},
body: JSON.stringify({
messages: [
{
role: "system",
content:
"You are an expert coding assistant. You read code from a file, and you suggest new code to add to the file. You may be given instructions on what to generate, which you should follow. You should generate code that is correct, efficient, and follows best practices. You should also generate code that is clear and easy to read.",
},
{
role: "user",
content: `The file is called ${fileName}. Here are my instructions on what to generate: ${instructions}. Suggest me code to insert at line ${line} in my file.
My code file content: ${code}
Return only the code, and nothing else. Do not include backticks.`,
},
],
}),
}
)
const json = await res.json()
callback(json)
}
)
socket.on("disconnect", () => { socket.on("disconnect", () => {
Object.entries(terminals).forEach((t) => { Object.entries(terminals).forEach((t) => {
const { terminal, onData, onExit } = t[1] const { terminal, onData, onExit } = t[1]

View File

@ -1,33 +1,41 @@
"use client" "use client"
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
import { Input } from "../ui/input"
import { Button } from "../ui/button" import { Button } from "../ui/button"
import { Check, Loader2, RotateCw, Sparkles, X } from "lucide-react" import { Check, Loader2, RotateCw, Sparkles, X } from "lucide-react"
import { Socket } from "socket.io-client"
import { Editor } from "@monaco-editor/react"
// import monaco from "monaco-editor"
export default function GenerateInput({ export default function GenerateInput({
cancel, socket,
submit,
width, width,
data,
editor,
onExpand, onExpand,
onAccept, onAccept,
}: { }: {
cancel: () => void socket: Socket
submit: (input: string) => void
width: number width: number
data: {
fileName: string
code: string
line: number
}
editor: {
language: string
}
onExpand: () => void onExpand: () => void
onAccept: (code: string) => void onAccept: (code: string) => void
}) { }) {
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
const [code, setCode] = useState(`function add(a: number, b: number): number { const [code, setCode] = useState("")
return a + b;
}
const result = add(2, 3);
console.log(result);`)
const [expanded, setExpanded] = useState(false) const [expanded, setExpanded] = useState(false)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState({
generate: false,
regenerate: false,
})
const [input, setInput] = useState("") const [input, setInput] = useState("")
const [currentPrompt, setCurrentPrompt] = useState("") const [currentPrompt, setCurrentPrompt] = useState("")
@ -37,17 +45,45 @@ export default function GenerateInput({
}, 0) }, 0)
}, []) }, [])
const handleGenerate = async () => { const handleGenerate = async ({
setLoading(true) regenerate = false,
}: {
regenerate?: boolean
}) => {
setLoading({ generate: !regenerate, regenerate })
setCurrentPrompt(input) setCurrentPrompt(input)
// const res = await generateCode() socket.emit(
await new Promise((resolve) => setTimeout(resolve, 1000)) "generateCode",
data.fileName,
data.code,
data.line,
regenerate ? currentPrompt : input,
(res: {
result: {
response: string
}
success: boolean
errors: any[]
messages: any[]
}) => {
if (!res.success) {
console.error(res.errors)
return
}
setExpanded(true) setCode(res.result.response)
onExpand() }
setLoading(false) )
} }
useEffect(() => {
if (code) {
setExpanded(true)
onExpand()
setLoading({ generate: false, regenerate: false })
}
}, [code])
return ( return (
<div className="w-full pr-4 space-y-2"> <div className="w-full pr-4 space-y-2">
<div className="flex items-center font-sans space-x-2"> <div className="flex items-center font-sans space-x-2">
@ -64,10 +100,10 @@ export default function GenerateInput({
<Button <Button
size="sm" size="sm"
disabled={loading || input === ""} disabled={loading.generate || loading.regenerate || input === ""}
onClick={handleGenerate} onClick={() => handleGenerate({})}
> >
{loading ? ( {loading.generate ? (
<> <>
<Loader2 className="animate-spin h-3 w-3 mr-2" /> <Loader2 className="animate-spin h-3 w-3 mr-2" />
Generating... Generating...
@ -83,20 +119,55 @@ export default function GenerateInput({
{expanded ? ( {expanded ? (
<> <>
<div className="rounded-md border border-muted-foreground w-full h-28 overflow-y-scroll p-2"> <div className="rounded-md border border-muted-foreground w-full h-28 overflow-y-scroll p-2">
<pre>{code}</pre> <Editor
height="100%"
defaultLanguage={editor.language}
value={code}
options={{
minimap: {
enabled: false,
},
scrollBeyondLastLine: false,
fontFamily: "var(--font-geist-mono)",
domReadOnly: true,
readOnly: true,
lineNumbers: "off",
glyphMargin: false,
folding: false,
// Undocumented see https://github.com/Microsoft/vscode/issues/30795#issuecomment-410998882
lineDecorationsWidth: 0,
lineNumbersMinChars: 0,
}}
theme="vs-dark"
/>
</div> </div>
<div className="flex space-x-2"> <div className="flex space-x-2 font-sans">
<Button onClick={() => onAccept(code)} size="sm"> <Button
disabled={loading.generate || loading.regenerate}
onClick={() => onAccept(code)}
size="sm"
>
<Check className="h-3 w-3 mr-2" /> <Check className="h-3 w-3 mr-2" />
Accept Accept
</Button> </Button>
<Button <Button
onClick={() => handleGenerate({ regenerate: true })}
disabled={loading.generate || loading.regenerate}
variant="outline" variant="outline"
size="sm" size="sm"
className="bg-transparent border-muted-foreground" className="bg-transparent border-muted-foreground"
> >
<RotateCw className="h-3 w-3 mr-2" /> {loading.regenerate ? (
Re-Generate <>
<Loader2 className="animate-spin h-3 w-3 mr-2" />
Generating...
</>
) : (
<>
<RotateCw className="h-3 w-3 mr-2" />
Re-Generate
</>
)}
</Button> </Button>
</div> </div>
</> </>

View File

@ -2,7 +2,7 @@
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react" import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
import monaco from "monaco-editor" import monaco from "monaco-editor"
import { use, useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
// import theme from "./theme.json" // import theme from "./theme.json"
import { import {
@ -372,9 +372,16 @@ export default function CodeEditor({
<div className="z-50 p-1" ref={generateWidgetRef}> <div className="z-50 p-1" ref={generateWidgetRef}>
{generate.show ? ( {generate.show ? (
<GenerateInput <GenerateInput
cancel={() => {}} socket={socket}
submit={(str: string) => {}}
width={generate.width - 90} width={generate.width - 90}
data={{
fileName: tabs.find((t) => t.id === activeId)?.name ?? "",
code: editorRef.current?.getValue() ?? "",
line: generate.line,
}}
editor={{
language: editorLanguage,
}}
onExpand={() => { onExpand={() => {
editorRef.current?.changeViewZones(function (changeAccessor) { editorRef.current?.changeViewZones(function (changeAccessor) {
changeAccessor.removeZone(generate.id) changeAccessor.removeZone(generate.id)
@ -391,13 +398,19 @@ export default function CodeEditor({
}) })
}} }}
onAccept={(code: string) => { onAccept={(code: string) => {
const line = generate.line
setGenerate((prev) => { setGenerate((prev) => {
return { return {
...prev, ...prev,
show: !prev.show, show: !prev.show,
} }
}) })
console.log("accepted:", code) const file = editorRef.current?.getValue()
const lines = file?.split("\n") || []
lines.splice(line - 1, 0, code)
const updatedFile = lines.join("\n")
editorRef.current?.setValue(updatedFile)
}} }}
/> />
) : null} ) : null}

View File

@ -24,7 +24,7 @@ export async function updateSandbox(body: {
name?: string name?: string
visibility?: "public" | "private" visibility?: "public" | "private"
}) { }) {
const res = await fetch("http://localhost:8787/api/sandbox", { await fetch("http://localhost:8787/api/sandbox", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -36,7 +36,7 @@ export async function updateSandbox(body: {
} }
export async function deleteSandbox(id: string) { export async function deleteSandbox(id: string) {
const res = await fetch(`http://localhost:8787/api/sandbox?id=${id}`, { await fetch(`http://localhost:8787/api/sandbox?id=${id}`, {
method: "DELETE", method: "DELETE",
}) })
@ -62,7 +62,7 @@ export async function shareSandbox(sandboxId: string, email: string) {
} }
export async function unshareSandbox(sandboxId: string, userId: string) { export async function unshareSandbox(sandboxId: string, userId: string) {
const res = await fetch("http://localhost:8787/api/sandbox/share", { await fetch("http://localhost:8787/api/sandbox/share", {
method: "DELETE", method: "DELETE",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@ -72,29 +72,3 @@ export async function unshareSandbox(sandboxId: string, userId: string) {
revalidatePath(`/code/${sandboxId}`) revalidatePath(`/code/${sandboxId}`)
} }
export async function generateCode(code: string, line: number) {
const res = await fetch(
"https://api.cloudflare.com/client/v4/accounts/d18f2f848da38e37adc9a34eab3d5ae2/ai/run/@cf/meta/llama-3-8b-instruct",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.CF_API_TOKEN}`,
},
body: JSON.stringify({
messages: [
{
role: "system",
content:
"You are an expert coding assistant who reads from an existing code file, and suggests code to add to the file.",
},
{
role: "user",
content: "", //todo
},
],
}),
}
)
}