2024-04-29 00:50:25 -04:00
import fs from "fs"
2024-04-29 02:19:27 -04:00
import os from "os"
2024-04-29 00:50:25 -04:00
import path from "path"
2024-04-30 00:24:32 -04:00
import express , { Express } from "express"
2024-04-18 16:40:08 -04:00
import dotenv from "dotenv"
import { createServer } from "http"
import { Server } from "socket.io"
import { z } from "zod"
2024-04-21 22:55:49 -04:00
import { User } from "./types"
2024-04-30 01:56:43 -04:00
import {
createFile ,
deleteFile ,
getSandboxFiles ,
renameFile ,
saveFile ,
} from "./utils"
2024-04-29 21:36:33 -04:00
import { IDisposable , IPty , spawn } from "node-pty"
2024-04-18 16:40:08 -04:00
dotenv . config ( )
const app : Express = express ( )
const port = process . env . PORT || 4000
// app.use(cors())
const httpServer = createServer ( app )
const io = new Server ( httpServer , {
cors : {
origin : "*" ,
} ,
} )
2024-04-29 21:36:33 -04:00
const terminals : {
[ id : string ] : { terminal : IPty ; onData : IDisposable ; onExit : IDisposable }
} = { }
2024-04-28 20:06:47 -04:00
2024-04-29 00:50:25 -04:00
const dirName = path . join ( __dirname , ".." )
2024-04-21 22:55:49 -04:00
const handshakeSchema = z . object ( {
userId : z.string ( ) ,
sandboxId : z.string ( ) ,
EIO : z.string ( ) ,
transport : z.string ( ) ,
} )
2024-04-18 16:40:08 -04:00
io . use ( async ( socket , next ) = > {
const q = socket . handshake . query
2024-04-21 22:55:49 -04:00
const parseQuery = handshakeSchema . safeParse ( q )
if ( ! parseQuery . success ) {
console . log ( "Invalid request." )
2024-04-18 16:40:08 -04:00
next ( new Error ( "Invalid request." ) )
2024-04-21 22:55:49 -04:00
return
}
2024-04-26 21:57:30 -04:00
const { sandboxId , userId } = parseQuery . data
2024-04-26 02:10:37 -04:00
const dbUser = await fetch ( ` http://localhost:8787/api/user?id= ${ userId } ` )
2024-04-21 22:55:49 -04:00
const dbUserJSON = ( await dbUser . json ( ) ) as User
if ( ! dbUserJSON ) {
console . log ( "DB error." )
next ( new Error ( "DB error." ) )
return
2024-04-18 16:40:08 -04:00
}
2024-04-26 02:10:37 -04:00
const sandbox = dbUserJSON . sandbox . find ( ( s ) = > s . id === sandboxId )
2024-04-18 16:40:08 -04:00
2024-04-21 22:55:49 -04:00
if ( ! sandbox ) {
console . log ( "Invalid credentials." )
2024-04-18 16:40:08 -04:00
next ( new Error ( "Invalid credentials." ) )
2024-04-21 22:55:49 -04:00
return
}
2024-04-26 02:10:37 -04:00
socket . data = {
id : sandboxId ,
userId ,
2024-04-18 16:40:08 -04:00
}
next ( )
} )
io . on ( "connection" , async ( socket ) = > {
2024-04-21 22:55:49 -04:00
const data = socket . data as {
userId : string
2024-04-26 02:10:37 -04:00
id : string
2024-04-21 22:55:49 -04:00
}
2024-04-26 02:10:37 -04:00
const sandboxFiles = await getSandboxFiles ( data . id )
2024-04-29 00:50:25 -04:00
sandboxFiles . fileData . forEach ( ( file ) = > {
const filePath = path . join ( dirName , file . id )
fs . mkdirSync ( path . dirname ( filePath ) , { recursive : true } )
fs . writeFile ( filePath , file . data , function ( err ) {
if ( err ) throw err
} )
} )
2024-04-18 16:40:08 -04:00
2024-04-26 21:57:30 -04:00
socket . emit ( "loaded" , sandboxFiles . files )
2024-04-27 19:12:25 -04:00
2024-04-27 00:20:17 -04:00
socket . on ( "getFile" , ( fileId : string , callback ) = > {
const file = sandboxFiles . fileData . find ( ( f ) = > f . id === fileId )
if ( ! file ) return
callback ( file . data )
} )
2024-04-27 19:12:25 -04:00
// todo: send diffs + debounce for efficiency
socket . on ( "saveFile" , async ( fileId : string , body : string ) = > {
const file = sandboxFiles . fileData . find ( ( f ) = > f . id === fileId )
if ( ! file ) return
file . data = body
2024-04-29 00:50:25 -04:00
fs . writeFile ( path . join ( dirName , file . id ) , body , function ( err ) {
if ( err ) throw err
} )
2024-04-27 19:12:25 -04:00
await saveFile ( fileId , body )
2024-04-27 16:41:25 -04:00
} )
2024-04-27 19:12:25 -04:00
2024-04-29 00:50:25 -04:00
socket . on ( "createFile" , async ( name : string ) = > {
const id = ` projects/ ${ data . id } / ${ name } `
fs . writeFile ( path . join ( dirName , id ) , "" , function ( err ) {
if ( err ) throw err
} )
sandboxFiles . files . push ( {
id ,
name ,
type : "file" ,
} )
sandboxFiles . fileData . push ( {
id ,
data : "" ,
} )
await createFile ( id )
} )
2024-04-27 14:23:09 -04:00
socket . on ( "renameFile" , async ( fileId : string , newName : string ) = > {
const file = sandboxFiles . fileData . find ( ( f ) = > f . id === fileId )
if ( ! file ) return
file . id = newName
2024-04-27 19:12:25 -04:00
2024-04-29 00:50:25 -04:00
const parts = fileId . split ( "/" )
const newFileId = parts . slice ( 0 , parts . length - 1 ) . join ( "/" ) + "/" + newName
fs . rename (
path . join ( dirName , fileId ) ,
path . join ( dirName , newFileId ) ,
function ( err ) {
if ( err ) throw err
}
)
await renameFile ( fileId , newFileId , file . data )
2024-04-27 14:23:09 -04:00
} )
2024-04-28 20:06:47 -04:00
2024-04-30 01:56:43 -04:00
socket . on ( "deleteFile" , async ( fileId : string , callback ) = > {
const file = sandboxFiles . fileData . find ( ( f ) = > f . id === fileId )
if ( ! file ) return
fs . unlink ( path . join ( dirName , fileId ) , function ( err ) {
if ( err ) throw err
} )
sandboxFiles . fileData = sandboxFiles . fileData . filter ( ( f ) = > f . id !== fileId )
await deleteFile ( fileId )
const newFiles = await getSandboxFiles ( data . id )
callback ( newFiles . files )
} )
2024-04-28 20:06:47 -04:00
socket . on ( "createTerminal" , ( { id } : { id : string } ) = > {
2024-04-29 02:19:27 -04:00
const pty = spawn ( os . platform ( ) === "win32" ? "cmd.exe" : "bash" , [ ] , {
name : "xterm" ,
cols : 100 ,
cwd : path.join ( dirName , "projects" , data . id ) ,
} )
2024-04-29 21:36:33 -04:00
const onData = pty . onData ( ( data ) = > {
2024-04-29 02:19:27 -04:00
socket . emit ( "terminalResponse" , {
// data: Buffer.from(data, "utf-8").toString("base64"),
data ,
} )
} )
2024-04-29 21:36:33 -04:00
const onExit = pty . onExit ( ( code ) = > console . log ( "exit :(" , code ) )
2024-04-29 02:19:27 -04:00
2024-04-30 22:48:36 -04:00
pty . write ( "clear\r" )
2024-04-29 21:36:33 -04:00
terminals [ id ] = {
terminal : pty ,
onData ,
onExit ,
}
2024-04-28 20:06:47 -04:00
} )
2024-04-29 02:19:27 -04:00
socket . on ( "terminalData" , ( id : string , data : string ) = > {
2024-04-28 20:06:47 -04:00
if ( ! terminals [ id ] ) {
console . log ( "terminals" , terminals )
return
}
2024-04-29 02:19:27 -04:00
try {
2024-04-29 21:36:33 -04:00
terminals [ id ] . terminal . write ( data )
2024-04-29 02:19:27 -04:00
} catch ( e ) {
console . log ( "Error writing to terminal" , e )
}
2024-04-28 20:06:47 -04:00
} )
2024-05-03 00:52:01 -07:00
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 :
2024-05-03 12:56:44 -07:00
"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. When you generate code, you should only return the code, and nothing else. You should not include backticks in the code you generate." ,
2024-05-03 00:52:01 -07:00
} ,
{
role : "user" ,
2024-05-03 12:56:44 -07:00
content : ` The file is called ${ fileName } . ` ,
} ,
{
role : "user" ,
content : ` Here are my instructions on what to generate: ${ instructions } . ` ,
} ,
{
role : "user" ,
content : ` Suggest me code to insert at line ${ line } in my file. Give only the code, and NOTHING else. DO NOT include backticks in your response. My code file content is as follows
$ { code } ` ,
2024-05-03 00:52:01 -07:00
} ,
] ,
} ) ,
}
)
const json = await res . json ( )
callback ( json )
}
)
2024-04-29 21:36:33 -04:00
socket . on ( "disconnect" , ( ) = > {
Object . entries ( terminals ) . forEach ( ( t ) = > {
const { terminal , onData , onExit } = t [ 1 ]
if ( os . platform ( ) !== "win32" ) terminal . kill ( )
onData . dispose ( )
onExit . dispose ( )
delete terminals [ t [ 0 ] ]
} )
} )
2024-04-18 16:40:08 -04:00
} )
httpServer . listen ( port , ( ) = > {
console . log ( ` Server running on port ${ port } ` )
} )