Compare commits
43 Commits
refactor
...
fix/path-o
Author | SHA1 | Date | |
---|---|---|---|
2c058b259a | |||
5817b2ea48 | |||
6845e1fef9 | |||
f38919d6cf | |||
7aaa920815 | |||
6bfff62513 | |||
0b7cc51c6e | |||
a353863523 | |||
2f88ff6d58 | |||
48731848dd | |||
0509716f34 | |||
3fcfe5a3dc | |||
06118e98e9 | |||
4ebd6dea96 | |||
6e8eee246f | |||
8921cd83bb | |||
45097e0f20 | |||
62e6d64a52 | |||
982a6edc26 | |||
300de1f03a | |||
02deea9c93 | |||
653142dd1d | |||
0c6b2b0dfb | |||
31d74ddc2d | |||
62311faf51 | |||
208d17879f | |||
0067dc8c0c | |||
ec24e64b17 | |||
4fe749daf2 | |||
b8398cc4c2 | |||
0e4649b2c9 | |||
d74205c909 | |||
fa998d9069 | |||
b01934bd20 | |||
a1990a189c | |||
bf79893dfa | |||
47324f15bf | |||
f5b04f9f49 | |||
169319de14 | |||
2dbdf51fd3 | |||
7b2ed21288 | |||
f4a84bd4b6 | |||
171a9ce3c6 |
216
backend/ai/package-lock.json
generated
216
backend/ai/package-lock.json
generated
@ -7,6 +7,9 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "ai",
|
"name": "ai",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@anthropic-ai/sdk": "^0.27.2"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
||||||
"@cloudflare/workers-types": "^4.20240512.0",
|
"@cloudflare/workers-types": "^4.20240512.0",
|
||||||
@ -15,6 +18,28 @@
|
|||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@anthropic-ai/sdk": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-Q6gOx4fyHQ+NCSaVeXEKFZfoFWCR3ctUA+sK5oGB7RKUkzUvK64aYM7v1T9ekJKwn8TwRq6IGjqS31n9PbjCIA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^18.11.18",
|
||||||
|
"@types/node-fetch": "^2.6.4",
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"agentkeepalive": "^4.2.1",
|
||||||
|
"form-data-encoder": "1.7.2",
|
||||||
|
"formdata-node": "^4.3.2",
|
||||||
|
"node-fetch": "^2.6.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@anthropic-ai/sdk/node_modules/@types/node": {
|
||||||
|
"version": "18.19.50",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz",
|
||||||
|
"integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~5.26.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@cloudflare/kv-asset-handler": {
|
"node_modules/@cloudflare/kv-asset-handler": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.2.tgz",
|
||||||
@ -861,11 +886,19 @@
|
|||||||
"version": "20.12.11",
|
"version": "20.12.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz",
|
||||||
"integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==",
|
"integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node-fetch": {
|
||||||
|
"version": "2.6.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
|
||||||
|
"integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"form-data": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node-forge": {
|
"node_modules/@types/node-forge": {
|
||||||
"version": "1.3.11",
|
"version": "1.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz",
|
||||||
@ -944,6 +977,17 @@
|
|||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/abort-controller": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||||
|
"dependencies": {
|
||||||
|
"event-target-shim": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.11.3",
|
"version": "8.11.3",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||||
@ -965,6 +1009,17 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/agentkeepalive": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
|
||||||
|
"dependencies": {
|
||||||
|
"humanize-ms": "^1.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansi-styles": {
|
"node_modules/ansi-styles": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||||
@ -1008,6 +1063,11 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@ -1141,6 +1201,17 @@
|
|||||||
"integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
|
"integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "12.0.0",
|
"version": "12.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
|
||||||
@ -1214,6 +1285,14 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/devalue": {
|
"node_modules/devalue": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.3.tgz",
|
||||||
@ -1287,6 +1366,14 @@
|
|||||||
"@types/estree": "^1.0.0"
|
"@types/estree": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/event-target-shim": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/execa": {
|
"node_modules/execa": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
||||||
@ -1334,6 +1421,36 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data-encoder": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
|
||||||
|
},
|
||||||
|
"node_modules/formdata-node": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"node-domexception": "1.0.0",
|
||||||
|
"web-streams-polyfill": "4.0.0-beta.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/fs.realpath": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||||
@ -1452,6 +1569,14 @@
|
|||||||
"node": ">=16.17.0"
|
"node": ">=16.17.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/humanize-ms": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/inflight": {
|
"node_modules/inflight": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
@ -1610,6 +1735,25 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mimic-fn": {
|
"node_modules/mimic-fn": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||||
@ -1675,8 +1819,7 @@
|
|||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/mustache": {
|
"node_modules/mustache": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
@ -1705,6 +1848,43 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-domexception": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/jimmywarting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://paypal.me/jimmywarting"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-forge": {
|
"node_modules/node-forge": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||||
@ -2222,6 +2402,11 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
|
},
|
||||||
"node_modules/ts-json-schema-generator": {
|
"node_modules/ts-json-schema-generator": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.1.tgz",
|
||||||
@ -2292,8 +2477,7 @@
|
|||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "5.26.5",
|
"version": "5.26.5",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.2.11",
|
"version": "5.2.11",
|
||||||
@ -2827,6 +3011,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/web-streams-polyfill": {
|
||||||
|
"version": "4.0.0-beta.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
|
||||||
|
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
@ -15,5 +15,8 @@
|
|||||||
"typescript": "^5.0.4",
|
"typescript": "^5.0.4",
|
||||||
"vitest": "1.3.0",
|
"vitest": "1.3.0",
|
||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@anthropic-ai/sdk": "^0.27.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,77 @@
|
|||||||
|
import { Anthropic } from "@anthropic-ai/sdk";
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
AI: any
|
ANTHROPIC_API_KEY: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async fetch(request, env): Promise<Response> {
|
async fetch(request: Request, env: Env): Promise<Response> {
|
||||||
if (request.method !== "GET") {
|
if (request.method !== "GET") {
|
||||||
return new Response("Method Not Allowed", { status: 405 })
|
return new Response("Method Not Allowed", { status: 405 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(request.url)
|
const url = new URL(request.url);
|
||||||
const fileName = url.searchParams.get("fileName")
|
// const fileName = url.searchParams.get("fileName");
|
||||||
const instructions = url.searchParams.get("instructions")
|
// const line = url.searchParams.get("line");
|
||||||
const line = url.searchParams.get("line")
|
const instructions = url.searchParams.get("instructions");
|
||||||
const code = url.searchParams.get("code")
|
const code = url.searchParams.get("code");
|
||||||
|
|
||||||
const response = await env.AI.run("@cf/meta/llama-3-8b-instruct", {
|
const prompt = `
|
||||||
messages: [
|
Make the following changes to the code below:
|
||||||
{
|
- ${instructions}
|
||||||
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 may generate multiple lines of code if necessary. When you generate code, you should ONLY return the code, and nothing else. You MUST NOT include backticks in the code you generate.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: "user",
|
|
||||||
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}`,
|
Return the complete code chunk. Do not refer to other code files. Do not add code before or after the chunk. Start your reponse with \`\`\`, and end with \`\`\`. Do not include any other text.
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(response))
|
\`\`\`
|
||||||
},
|
${code}
|
||||||
} satisfies ExportedHandler<Env>
|
\`\`\`
|
||||||
|
`;
|
||||||
|
console.log(prompt);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const anthropic = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY });
|
||||||
|
|
||||||
|
interface TextBlock {
|
||||||
|
type: "text";
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolUseBlock {
|
||||||
|
type: "tool_use";
|
||||||
|
tool_use: {
|
||||||
|
// Add properties if needed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentBlock = TextBlock | ToolUseBlock;
|
||||||
|
|
||||||
|
function getTextContent(content: ContentBlock[]): string {
|
||||||
|
for (const block of content) {
|
||||||
|
if (block.type === "text") {
|
||||||
|
return block.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "No text content found";
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await anthropic.messages.create({
|
||||||
|
model: "claude-3-5-sonnet-20240620",
|
||||||
|
max_tokens: 1024,
|
||||||
|
messages: [{ role: "user", content: prompt }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = response.content as ContentBlock[];
|
||||||
|
const textBlockContent = getTextContent(message);
|
||||||
|
|
||||||
|
const pattern = /```[a-zA-Z]*\n([\s\S]*?)\n```/;
|
||||||
|
const match = textBlockContent.match(pattern);
|
||||||
|
|
||||||
|
const codeContent = match ? match[1] : "Error: Could not extract code.";
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ "response": codeContent }))
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
return new Response("Internal Server Error", { status: 500 });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
106
backend/server/package-lock.json
generated
106
backend/server/package-lock.json
generated
@ -12,7 +12,7 @@
|
|||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"e2b": "^0.16.1",
|
"e2b": "^0.16.2-beta.47",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"rate-limiter-flexible": "^5.0.3",
|
"rate-limiter-flexible": "^5.0.3",
|
||||||
"simple-git": "^3.25.0",
|
"simple-git": "^3.25.0",
|
||||||
@ -41,6 +41,28 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@bufbuild/protobuf": {
|
||||||
|
"version": "1.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
|
||||||
|
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag=="
|
||||||
|
},
|
||||||
|
"node_modules/@connectrpc/connect": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-vZeOkKaAjyV4+RH3+rJZIfDFJAfr+7fyYr6sLDKbYX3uuTVszhFe9/YKf5DNqrDb5cKdKVlYkGn6DTDqMitAnA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@bufbuild/protobuf": "^1.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@connectrpc/connect-web": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@connectrpc/connect-web/-/connect-web-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-13aO4psFbbm7rdOFGV0De2Za64DY/acMspgloDlcOKzLPPs0yZkhp1OOzAQeiAIr7BM/VOHIA3p8mF0inxCYTA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@bufbuild/protobuf": "^1.4.2",
|
||||||
|
"@connectrpc/connect": "1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
@ -443,6 +465,7 @@
|
|||||||
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
|
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-gyp-build": "^4.3.0"
|
"node-gyp-build": "^4.3.0"
|
||||||
},
|
},
|
||||||
@ -564,6 +587,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/compare-versions": {
|
||||||
|
"version": "6.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz",
|
||||||
|
"integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@ -737,23 +765,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/e2b": {
|
"node_modules/e2b": {
|
||||||
"version": "0.16.2",
|
"version": "0.16.2-beta.47",
|
||||||
"resolved": "https://registry.npmjs.org/e2b/-/e2b-0.16.2.tgz",
|
"resolved": "https://registry.npmjs.org/e2b/-/e2b-0.16.2-beta.47.tgz",
|
||||||
"integrity": "sha512-xKmVK4ipgVQPJ/uyyrfH9LnaawERRWt8U2UZhdhGfzdL/QU/OpBjuhoIbFCv1Uy6qXV4nIiJ6Nw4MBC4HmXf1g==",
|
"integrity": "sha512-tMPDYLMD+8+JyLPrsWft3NHBhK5YKOFOXzKMwpOKR5KvXOkd1silkArDwplmBUzN/eG/uRzWdtHZs9mHUQ5b9g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isomorphic-ws": "^5.0.0",
|
"@bufbuild/protobuf": "^1.10.0",
|
||||||
"normalize-path": "^3.0.0",
|
"@connectrpc/connect": "^1.4.0",
|
||||||
"openapi-typescript-fetch": "^1.1.3",
|
"@connectrpc/connect-web": "^1.4.0",
|
||||||
"path-browserify": "^1.0.1",
|
"compare-versions": "^6.1.0",
|
||||||
"platform": "^1.3.6",
|
"openapi-fetch": "^0.9.7",
|
||||||
"ws": "^8.15.1"
|
"platform": "^1.3.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"bufferutil": "^4.0.8",
|
|
||||||
"utf-8-validate": "^6.0.3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
@ -1195,14 +1219,6 @@
|
|||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/isomorphic-ws": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"ws": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
@ -1301,6 +1317,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
|
||||||
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
|
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"node-gyp-build": "bin.js",
|
"node-gyp-build": "bin.js",
|
||||||
"node-gyp-build-optional": "optional.js",
|
"node-gyp-build-optional": "optional.js",
|
||||||
@ -1383,6 +1400,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -1417,15 +1435,19 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/openapi-typescript-fetch": {
|
"node_modules/openapi-fetch": {
|
||||||
"version": "1.1.3",
|
"version": "0.9.8",
|
||||||
"resolved": "https://registry.npmjs.org/openapi-typescript-fetch/-/openapi-typescript-fetch-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.9.8.tgz",
|
||||||
"integrity": "sha512-smLZPck4OkKMNExcw8jMgrMOGgVGx2N/s6DbKL2ftNl77g5HfoGpZGFy79RBzU/EkaO0OZpwBnslfdBfh7ZcWg==",
|
"integrity": "sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg==",
|
||||||
"engines": {
|
"dependencies": {
|
||||||
"node": ">= 12.0.0",
|
"openapi-typescript-helpers": "^0.0.8"
|
||||||
"npm": ">= 7.0.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/openapi-typescript-helpers": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz",
|
||||||
|
"integrity": "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g=="
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@ -1434,11 +1456,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-browserify": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="
|
|
||||||
},
|
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
@ -2053,6 +2070,7 @@
|
|||||||
"integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==",
|
"integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-gyp-build": "^4.3.0"
|
"node-gyp-build": "^4.3.0"
|
||||||
},
|
},
|
||||||
@ -2098,26 +2116,6 @@
|
|||||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.18.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
|
||||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"e2b": "^0.16.1",
|
"e2b": "^0.16.2-beta.47",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"rate-limiter-flexible": "^5.0.3",
|
"rate-limiter-flexible": "^5.0.3",
|
||||||
"simple-git": "^3.25.0",
|
"simple-git": "^3.25.0",
|
||||||
|
@ -22,13 +22,13 @@ export class SecureGitClient {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a temporary directory
|
// Create a temporary directory
|
||||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-push-'));
|
tempDir = fs.mkdtempSync(path.posix.join(os.tmpdir(), 'git-push-'));
|
||||||
console.log(`Temporary directory created: ${tempDir}`);
|
console.log(`Temporary directory created: ${tempDir}`);
|
||||||
|
|
||||||
// Write files to the temporary directory
|
// Write files to the temporary directory
|
||||||
console.log(`Writing ${fileData.length} files.`);
|
console.log(`Writing ${fileData.length} files.`);
|
||||||
for (const { id, data } of fileData) {
|
for (const { id, data } of fileData) {
|
||||||
const filePath = path.join(tempDir, id);
|
const filePath = path.posix.join(tempDir, id);
|
||||||
const dirPath = path.dirname(filePath);
|
const dirPath = path.dirname(filePath);
|
||||||
|
|
||||||
if (!fs.existsSync(dirPath)) {
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
67
backend/server/src/Terminal.ts
Normal file
67
backend/server/src/Terminal.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Sandbox, ProcessHandle } from "e2b";
|
||||||
|
|
||||||
|
// Terminal class to manage a pseudo-terminal (PTY) in a sandbox environment
|
||||||
|
export class Terminal {
|
||||||
|
private pty: ProcessHandle | undefined; // Holds the PTY process handle
|
||||||
|
private sandbox: Sandbox; // Reference to the sandbox environment
|
||||||
|
|
||||||
|
// Constructor initializes the Terminal with a sandbox
|
||||||
|
constructor(sandbox: Sandbox) {
|
||||||
|
this.sandbox = sandbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the terminal with specified rows, columns, and data handler
|
||||||
|
async init({
|
||||||
|
rows = 20,
|
||||||
|
cols = 80,
|
||||||
|
onData,
|
||||||
|
}: {
|
||||||
|
rows?: number;
|
||||||
|
cols?: number;
|
||||||
|
onData: (responseData: string) => void;
|
||||||
|
}): Promise<void> {
|
||||||
|
// Create a new PTY process
|
||||||
|
this.pty = await this.sandbox.pty.create({
|
||||||
|
rows,
|
||||||
|
cols,
|
||||||
|
timeout: 0,
|
||||||
|
onData: (data: Uint8Array) => {
|
||||||
|
onData(new TextDecoder().decode(data)); // Convert received data to string and pass to handler
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send data to the terminal
|
||||||
|
async sendData(data: string) {
|
||||||
|
if (this.pty) {
|
||||||
|
await this.sandbox.pty.sendInput(this.pty.pid, new TextEncoder().encode(data));
|
||||||
|
} else {
|
||||||
|
console.log("Cannot send data because pty is not initialized.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize the terminal
|
||||||
|
async resize(size: { cols: number; rows: number }): Promise<void> {
|
||||||
|
if (this.pty) {
|
||||||
|
await this.sandbox.pty.resize(this.pty.pid, size);
|
||||||
|
} else {
|
||||||
|
console.log("Cannot resize terminal because pty is not initialized.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the terminal, killing the PTY process and stopping the input stream
|
||||||
|
async close(): Promise<void> {
|
||||||
|
if (this.pty) {
|
||||||
|
await this.pty.kill();
|
||||||
|
} else {
|
||||||
|
console.log("Cannot kill pty because it is not initialized.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage example:
|
||||||
|
// const terminal = new Terminal(sandbox);
|
||||||
|
// await terminal.init();
|
||||||
|
// terminal.sendData('ls -la');
|
||||||
|
// await terminal.resize({ cols: 100, rows: 30 });
|
||||||
|
// await terminal.close();
|
@ -20,7 +20,11 @@ import {
|
|||||||
saveFile,
|
saveFile,
|
||||||
} from "./fileoperations";
|
} from "./fileoperations";
|
||||||
import { LockManager } from "./utils";
|
import { LockManager } from "./utils";
|
||||||
import { Sandbox, Terminal, FilesystemManager } from "e2b";
|
|
||||||
|
import { Sandbox, Filesystem } from "e2b";
|
||||||
|
|
||||||
|
import { Terminal } from "./Terminal"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MAX_BODY_SIZE,
|
MAX_BODY_SIZE,
|
||||||
createFileRL,
|
createFileRL,
|
||||||
@ -52,12 +56,12 @@ const terminals: Record<string, Terminal> = {};
|
|||||||
const dirName = "/home/user";
|
const dirName = "/home/user";
|
||||||
|
|
||||||
const moveFile = async (
|
const moveFile = async (
|
||||||
filesystem: FilesystemManager,
|
filesystem: Filesystem,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
newFilePath: string
|
newFilePath: string
|
||||||
) => {
|
) => {
|
||||||
const fileContents = await filesystem.readBytes(filePath);
|
const fileContents = await filesystem.read(filePath);
|
||||||
await filesystem.writeBytes(newFilePath, fileContents);
|
await filesystem.write(newFilePath, fileContents);
|
||||||
await filesystem.remove(filePath);
|
await filesystem.remove(filePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -155,8 +159,9 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
await lockManager.acquireLock(data.sandboxId, async () => {
|
await lockManager.acquireLock(data.sandboxId, async () => {
|
||||||
try {
|
try {
|
||||||
if (!containers[data.sandboxId]) {
|
// Start a new container if the container doesn't exist or it timed out.
|
||||||
containers[data.sandboxId] = await Sandbox.create();
|
if (!containers[data.sandboxId] || !(await containers[data.sandboxId].isRunning())) {
|
||||||
|
containers[data.sandboxId] = await Sandbox.create({ timeoutMs: 1200_000 });
|
||||||
console.log("Created container ", data.sandboxId);
|
console.log("Created container ", data.sandboxId);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -167,19 +172,28 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
// Change the owner of the project directory to user
|
// Change the owner of the project directory to user
|
||||||
const fixPermissions = async () => {
|
const fixPermissions = async () => {
|
||||||
await containers[data.sandboxId].process.startAndWait(
|
await containers[data.sandboxId].commands.run(
|
||||||
`sudo chown -R user "${path.join(dirName, "projects", data.sandboxId)}"`
|
`sudo chown -R user "${path.posix.join(dirName, "projects", data.sandboxId)}"`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Copy all files from the project to the container
|
||||||
const sandboxFiles = await getSandboxFiles(data.sandboxId);
|
const sandboxFiles = await getSandboxFiles(data.sandboxId);
|
||||||
sandboxFiles.fileData.forEach(async (file) => {
|
const containerFiles = containers[data.sandboxId].files;
|
||||||
const filePath = path.join(dirName, file.id);
|
const promises = sandboxFiles.fileData.map(async (file) => {
|
||||||
await containers[data.sandboxId].filesystem.makeDir(
|
try {
|
||||||
path.dirname(filePath)
|
const filePath = path.posix.join(dirName, file.id);
|
||||||
);
|
const parentDirectory = path.dirname(filePath);
|
||||||
await containers[data.sandboxId].filesystem.write(filePath, file.data);
|
if (!containerFiles.exists(parentDirectory)) {
|
||||||
|
await containerFiles.makeDir(parentDirectory);
|
||||||
|
}
|
||||||
|
await containerFiles.write(filePath, file.data);
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log("Failed to create file: " + e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
fixPermissions();
|
fixPermissions();
|
||||||
|
|
||||||
socket.emit("loaded", sandboxFiles.files);
|
socket.emit("loaded", sandboxFiles.files);
|
||||||
@ -231,8 +245,8 @@ io.on("connection", async (socket) => {
|
|||||||
if (!file) return;
|
if (!file) return;
|
||||||
file.data = body;
|
file.data = body;
|
||||||
|
|
||||||
await containers[data.sandboxId].filesystem.write(
|
await containers[data.sandboxId].files.write(
|
||||||
path.join(dirName, file.id),
|
path.posix.join(dirName, file.id),
|
||||||
body
|
body
|
||||||
);
|
);
|
||||||
fixPermissions();
|
fixPermissions();
|
||||||
@ -253,9 +267,9 @@ io.on("connection", async (socket) => {
|
|||||||
const newFileId = folderId + "/" + parts.pop();
|
const newFileId = folderId + "/" + parts.pop();
|
||||||
|
|
||||||
await moveFile(
|
await moveFile(
|
||||||
containers[data.sandboxId].filesystem,
|
containers[data.sandboxId].files,
|
||||||
path.join(dirName, fileId),
|
path.posix.join(dirName, fileId),
|
||||||
path.join(dirName, newFileId)
|
path.posix.join(dirName, newFileId)
|
||||||
);
|
);
|
||||||
fixPermissions();
|
fixPermissions();
|
||||||
|
|
||||||
@ -346,8 +360,8 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
const id = `projects/${data.sandboxId}/${name}`;
|
const id = `projects/${data.sandboxId}/${name}`;
|
||||||
|
|
||||||
await containers[data.sandboxId].filesystem.write(
|
await containers[data.sandboxId].files.write(
|
||||||
path.join(dirName, id),
|
path.posix.join(dirName, id),
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
fixPermissions();
|
fixPermissions();
|
||||||
@ -383,8 +397,8 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
const id = `projects/${data.sandboxId}/${name}`;
|
const id = `projects/${data.sandboxId}/${name}`;
|
||||||
|
|
||||||
await containers[data.sandboxId].filesystem.makeDir(
|
await containers[data.sandboxId].files.makeDir(
|
||||||
path.join(dirName, id)
|
path.posix.join(dirName, id)
|
||||||
);
|
);
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
@ -412,9 +426,9 @@ io.on("connection", async (socket) => {
|
|||||||
parts.slice(0, parts.length - 1).join("/") + "/" + newName;
|
parts.slice(0, parts.length - 1).join("/") + "/" + newName;
|
||||||
|
|
||||||
await moveFile(
|
await moveFile(
|
||||||
containers[data.sandboxId].filesystem,
|
containers[data.sandboxId].files,
|
||||||
path.join(dirName, fileId),
|
path.posix.join(dirName, fileId),
|
||||||
path.join(dirName, newFileId)
|
path.posix.join(dirName, newFileId)
|
||||||
);
|
);
|
||||||
fixPermissions();
|
fixPermissions();
|
||||||
await renameFile(fileId, newFileId, file.data);
|
await renameFile(fileId, newFileId, file.data);
|
||||||
@ -435,8 +449,8 @@ io.on("connection", async (socket) => {
|
|||||||
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
const file = sandboxFiles.fileData.find((f) => f.id === fileId);
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
await containers[data.sandboxId].filesystem.remove(
|
await containers[data.sandboxId].files.remove(
|
||||||
path.join(dirName, fileId)
|
path.posix.join(dirName, fileId)
|
||||||
);
|
);
|
||||||
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
||||||
(f) => f.id !== fileId
|
(f) => f.id !== fileId
|
||||||
@ -462,8 +476,8 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
await containers[data.sandboxId].filesystem.remove(
|
await containers[data.sandboxId].files.remove(
|
||||||
path.join(dirName, file)
|
path.posix.join(dirName, file)
|
||||||
);
|
);
|
||||||
|
|
||||||
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
sandboxFiles.fileData = sandboxFiles.fileData.filter(
|
||||||
@ -491,35 +505,42 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
await lockManager.acquireLock(data.sandboxId, async () => {
|
await lockManager.acquireLock(data.sandboxId, async () => {
|
||||||
try {
|
try {
|
||||||
terminals[id] = await containers[data.sandboxId].terminal.start({
|
terminals[id] = new Terminal(containers[data.sandboxId])
|
||||||
onData: (responseData: string) => {
|
await terminals[id].init({
|
||||||
io.emit("terminalResponse", { id, data: responseData });
|
onData: (responseString: string) => {
|
||||||
|
io.emit("terminalResponse", { id, data: responseString });
|
||||||
|
|
||||||
function extractPortNumber(inputString: string) {
|
function extractPortNumber(inputString: string) {
|
||||||
// Remove ANSI escape codes
|
// Remove ANSI escape codes
|
||||||
const cleanedString = inputString.replace(/\x1B\[[0-9;]*m/g, '');
|
const cleanedString = inputString.replace(/\x1B\[[0-9;]*m/g, '');
|
||||||
|
|
||||||
// Regular expression to match port number
|
// Regular expression to match port number
|
||||||
const regex = /http:\/\/localhost:(\d+)/;
|
const regex = /http:\/\/localhost:(\d+)/;
|
||||||
// If a match is found, return the port number
|
// If a match is found, return the port number
|
||||||
const match = cleanedString.match(regex);
|
const match = cleanedString.match(regex);
|
||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
}
|
}
|
||||||
const port = parseInt(extractPortNumber(responseData) ?? "");
|
const port = parseInt(extractPortNumber(responseString) ?? "");
|
||||||
if (port) {
|
if (port) {
|
||||||
io.emit(
|
io.emit(
|
||||||
"previewURL",
|
"previewURL",
|
||||||
"https://" + containers[data.sandboxId].getHostname(port)
|
"https://" + containers[data.sandboxId].getHost(port)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
size: { cols: 80, rows: 20 },
|
cols: 80,
|
||||||
onExit: () => console.log("Terminal exited", id),
|
rows: 20,
|
||||||
|
//onExit: () => console.log("Terminal exited", id),
|
||||||
});
|
});
|
||||||
await terminals[id].sendData(
|
|
||||||
`cd "${path.join(dirName, "projects", data.sandboxId)}"\r`
|
const defaultDirectory = path.posix.join(dirName, "projects", data.sandboxId);
|
||||||
);
|
const defaultCommands = [
|
||||||
await terminals[id].sendData("export PS1='user> '\rclear\r");
|
`cd "${defaultDirectory}"`,
|
||||||
|
"export PS1='user> '",
|
||||||
|
"clear"
|
||||||
|
]
|
||||||
|
for (const command of defaultCommands) await terminals[id].sendData(command + "\r");
|
||||||
|
|
||||||
console.log("Created terminal", id);
|
console.log("Created terminal", id);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(`Error creating terminal ${id}:`, e);
|
console.error(`Error creating terminal ${id}:`, e);
|
||||||
@ -548,13 +569,13 @@ io.on("connection", async (socket) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
socket.on("terminalData", (id: string, data: string) => {
|
socket.on("terminalData", async (id: string, data: string) => {
|
||||||
try {
|
try {
|
||||||
if (!terminals[id]) {
|
if (!terminals[id]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
terminals[id].sendData(data);
|
await terminals[id].sendData(data);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error("Error writing to terminal:", e);
|
console.error("Error writing to terminal:", e);
|
||||||
io.emit("error", `Error: writing to terminal. ${e.message ?? e}`);
|
io.emit("error", `Error: writing to terminal. ${e.message ?? e}`);
|
||||||
@ -567,7 +588,7 @@ io.on("connection", async (socket) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await terminals[id].kill();
|
await terminals[id].close();
|
||||||
delete terminals[id];
|
delete terminals[id];
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
@ -603,7 +624,7 @@ io.on("connection", async (socket) => {
|
|||||||
|
|
||||||
// Generate code from cloudflare workers AI
|
// Generate code from cloudflare workers AI
|
||||||
const generateCodePromise = fetch(
|
const generateCodePromise = fetch(
|
||||||
`${process.env.AI_WORKER_URL}/api?fileName=${fileName}&code=${code}&line=${line}&instructions=${instructions}`,
|
`${process.env.AI_WORKER_URL}/api?fileName=${encodeURIComponent(fileName)}&code=${encodeURIComponent(code)}&line=${encodeURIComponent(line)}&instructions=${encodeURIComponent(instructions)}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@ -636,7 +657,7 @@ io.on("connection", async (socket) => {
|
|||||||
if (data.isOwner && connections[data.sandboxId] <= 0) {
|
if (data.isOwner && connections[data.sandboxId] <= 0) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(terminals).map(async ([key, terminal]) => {
|
Object.entries(terminals).map(async ([key, terminal]) => {
|
||||||
await terminal.kill();
|
await terminal.close();
|
||||||
delete terminals[key];
|
delete terminals[key];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -644,7 +665,7 @@ io.on("connection", async (socket) => {
|
|||||||
await lockManager.acquireLock(data.sandboxId, async () => {
|
await lockManager.acquireLock(data.sandboxId, async () => {
|
||||||
try {
|
try {
|
||||||
if (containers[data.sandboxId]) {
|
if (containers[data.sandboxId]) {
|
||||||
await containers[data.sandboxId].close();
|
await containers[data.sandboxId].kill();
|
||||||
delete containers[data.sandboxId];
|
delete containers[data.sandboxId];
|
||||||
console.log("Closed container", data.sandboxId);
|
console.log("Closed container", data.sandboxId);
|
||||||
}
|
}
|
||||||
|
32
backend/storage/package-lock.json
generated
32
backend/storage/package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "storage",
|
"name": "storage",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"p-limit": "^6.1.0",
|
||||||
"zod": "^3.23.4"
|
"zod": "^3.23.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -894,6 +895,21 @@
|
|||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/runner/node_modules/p-limit": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"yocto-queue": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitest/snapshot": {
|
"node_modules/@vitest/snapshot": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.0.tgz",
|
||||||
@ -1766,12 +1782,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
"version": "5.0.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.1.0.tgz",
|
||||||
"integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
|
"integrity": "sha512-H0jc0q1vOzlEk0TqAKXKZxdl7kX3OFUzCnNVUnq5Pc3DGo0kpeaMuPqxQn235HibwBEb0/pm9dgKTjXy66fBkg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yocto-queue": "^1.0.0"
|
"yocto-queue": "^1.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@ -2970,10 +2985,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "1.0.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz",
|
||||||
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
|
"integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.20"
|
"node": ">=12.20"
|
||||||
},
|
},
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"p-limit": "^6.1.0",
|
||||||
"zod": "^3.23.4"
|
"zod": "^3.23.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import pLimit from 'p-limit';
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
R2: R2Bucket
|
R2: R2Bucket
|
||||||
|
Templates: R2Bucket
|
||||||
KEY: string
|
KEY: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,17 +146,18 @@ export default {
|
|||||||
|
|
||||||
console.log(`Copying template: ${type}`);
|
console.log(`Copying template: ${type}`);
|
||||||
|
|
||||||
const templateDirectory = `templates/${type}`;
|
|
||||||
|
|
||||||
// List all objects under the directory
|
// List all objects under the directory
|
||||||
const { objects } = await env.R2.list({ prefix: templateDirectory });
|
const { objects } = await env.Templates.list({ prefix: type });
|
||||||
|
|
||||||
// Copy each object to the new directory
|
// Copy each object to the new directory with a 5 concurrency limit
|
||||||
for (const { key } of objects) {
|
const limit = pLimit(5);
|
||||||
const destinationKey = key.replace(templateDirectory, `projects/${sandboxId}`);
|
await Promise.all(objects.map(({ key }) =>
|
||||||
const fileBody = await env.R2.get(key).then(res => res?.body ?? "");
|
limit(async () => {
|
||||||
|
const destinationKey = key.replace(type, `projects/${sandboxId}`);
|
||||||
|
const fileBody = await env.Templates.get(key).then(res => res?.body ?? "");
|
||||||
await env.R2.put(destinationKey, fileBody);
|
await env.R2.put(destinationKey, fileBody);
|
||||||
}
|
})
|
||||||
|
));
|
||||||
|
|
||||||
return success
|
return success
|
||||||
} else {
|
} else {
|
||||||
|
@ -91,7 +91,7 @@ export default async function CodePage({ params }: { params: { id: string } }) {
|
|||||||
<>
|
<>
|
||||||
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
<div className="overflow-hidden overscroll-none w-screen flex flex-col h-screen bg-background">
|
||||||
<Room id={sandboxId}>
|
<Room id={sandboxId}>
|
||||||
<TerminalProvider>
|
<TerminalProvider>
|
||||||
<Navbar userData={userData} sandboxData={sandboxData} shared={shared} />
|
<Navbar userData={userData} sandboxData={sandboxData} shared={shared} />
|
||||||
<div className="w-screen flex grow">
|
<div className="w-screen flex grow">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
@ -99,7 +99,7 @@ export default async function CodePage({ params }: { params: { id: string } }) {
|
|||||||
sandboxData={sandboxData}
|
sandboxData={sandboxData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TerminalProvider>
|
</TerminalProvider>
|
||||||
</Room>
|
</Room>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -93,7 +93,7 @@ export default function NewProjectModal({
|
|||||||
open: boolean
|
open: boolean
|
||||||
setOpen: (open: boolean) => void
|
setOpen: (open: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
const [selected, setSelected] = useState("react")
|
const [selected, setSelected] = useState("reactjs")
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react"
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
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 { Socket } from "socket.io-client"
|
||||||
@ -59,7 +59,7 @@ export default function GenerateInput({
|
|||||||
}: {
|
}: {
|
||||||
regenerate?: boolean
|
regenerate?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
if (user.generations >= 10) {
|
if (user.generations >= 1000) {
|
||||||
toast.error("You reached the maximum # of generations.")
|
toast.error("You reached the maximum # of generations.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -84,6 +84,13 @@ export default function GenerateInput({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
const handleGenerateForm = useCallback(
|
||||||
|
(e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
handleGenerate({ regenerate: false })
|
||||||
|
},
|
||||||
|
[input, currentPrompt]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (code) {
|
if (code) {
|
||||||
@ -93,9 +100,23 @@ export default function GenerateInput({
|
|||||||
}
|
}
|
||||||
}, [code])
|
}, [code])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
//listen to when Esc key is pressed and close the modal
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener("keydown", handleKeyDown)
|
||||||
|
return () => window.removeEventListener("keydown", handleKeyDown)
|
||||||
|
}, [])
|
||||||
|
|
||||||
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">
|
<form
|
||||||
|
onSubmit={handleGenerateForm}
|
||||||
|
className="flex items-center font-sans space-x-2"
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
style={{
|
style={{
|
||||||
@ -109,8 +130,8 @@ export default function GenerateInput({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
|
type="submit"
|
||||||
disabled={loading.generate || loading.regenerate || input === ""}
|
disabled={loading.generate || loading.regenerate || input === ""}
|
||||||
onClick={() => handleGenerate({})}
|
|
||||||
>
|
>
|
||||||
{loading.generate ? (
|
{loading.generate ? (
|
||||||
<>
|
<>
|
||||||
@ -126,13 +147,14 @@ export default function GenerateInput({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="smIcon"
|
size="smIcon"
|
||||||
className="bg-transparent shrink-0 border-muted-foreground"
|
className="bg-transparent shrink-0 border-muted-foreground"
|
||||||
>
|
>
|
||||||
<X className="h-3 w-3" />
|
<X className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</form>
|
||||||
{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">
|
||||||
|
@ -1,48 +1,40 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { SetStateAction, useCallback, useEffect, useRef, useState } from "react"
|
import { SetStateAction, useCallback, useEffect, useRef, useState } from "react"
|
||||||
|
import * as monaco from "monaco-editor"
|
||||||
import { Sandbox, User, TFile, TFolder, TTab } from "@/lib/types"
|
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
|
||||||
|
import { toast } from "sonner"
|
||||||
// Authentication
|
|
||||||
import { useClerk } from "@clerk/nextjs"
|
import { useClerk } from "@clerk/nextjs"
|
||||||
|
import { AnimatePresence, motion } from "framer-motion"
|
||||||
|
|
||||||
// Liveblocks
|
|
||||||
import * as Y from "yjs"
|
import * as Y from "yjs"
|
||||||
import LiveblocksProvider from "@liveblocks/yjs"
|
import LiveblocksProvider from "@liveblocks/yjs"
|
||||||
import { MonacoBinding } from "y-monaco"
|
import { MonacoBinding } from "y-monaco"
|
||||||
import { Awareness } from "y-protocols/awareness"
|
import { Awareness } from "y-protocols/awareness"
|
||||||
import { TypedLiveblocksProvider, useRoom, useSelf } from "@/liveblocks.config"
|
import { TypedLiveblocksProvider, useRoom, useSelf } from "@/liveblocks.config"
|
||||||
|
|
||||||
// Icons
|
|
||||||
import { FileJson, Loader2, TerminalSquare } from "lucide-react"
|
|
||||||
|
|
||||||
// Contexts
|
|
||||||
import { PreviewProvider, usePreview } from '@/context/PreviewContext';
|
|
||||||
import { useSocket } from "@/context/SocketContext"
|
|
||||||
|
|
||||||
// External Components
|
|
||||||
import monaco from "monaco-editor"
|
|
||||||
import { Terminal } from "@xterm/xterm"
|
|
||||||
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
|
|
||||||
import { toast } from "sonner"
|
|
||||||
import {
|
import {
|
||||||
ResizableHandle,
|
ResizableHandle,
|
||||||
ResizablePanel,
|
ResizablePanel,
|
||||||
ResizablePanelGroup,
|
ResizablePanelGroup,
|
||||||
} from "@/components/ui/resizable"
|
} from "@/components/ui/resizable"
|
||||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
import { FileJson, Loader2, Sparkles, TerminalSquare } from "lucide-react"
|
||||||
|
|
||||||
// Editor Components
|
|
||||||
import Tab from "../ui/tab"
|
import Tab from "../ui/tab"
|
||||||
import Sidebar from "./sidebar"
|
import Sidebar from "./sidebar"
|
||||||
import GenerateInput from "./generate"
|
import GenerateInput from "./generate"
|
||||||
|
import { Sandbox, User, TFile, TFolder, TTab } from "@/lib/types"
|
||||||
import { addNew, processFileType, validateName, debounce } from "@/lib/utils"
|
import { addNew, processFileType, validateName, debounce } from "@/lib/utils"
|
||||||
import { Cursors } from "./live/cursors"
|
import { Cursors } from "./live/cursors"
|
||||||
|
import { Terminal } from "@xterm/xterm"
|
||||||
import DisableAccessModal from "./live/disableModal"
|
import DisableAccessModal from "./live/disableModal"
|
||||||
import Loading from "./loading"
|
import Loading from "./loading"
|
||||||
import PreviewWindow from "./preview"
|
import PreviewWindow from "./preview"
|
||||||
import Terminals from "./terminals"
|
import Terminals from "./terminals"
|
||||||
|
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||||
|
import { PreviewProvider, usePreview } from "@/context/PreviewContext"
|
||||||
|
import { useSocket } from "@/context/SocketContext"
|
||||||
|
import { Button } from "../ui/button"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
export default function CodeEditor({
|
export default function CodeEditor({
|
||||||
userData,
|
userData,
|
||||||
@ -51,18 +43,22 @@ export default function CodeEditor({
|
|||||||
userData: User
|
userData: User
|
||||||
sandboxData: Sandbox
|
sandboxData: Sandbox
|
||||||
}) {
|
}) {
|
||||||
|
//SocketContext functions and effects
|
||||||
|
const { socket, setUserAndSandboxId } = useSocket()
|
||||||
|
|
||||||
// Socket to the backend server
|
|
||||||
const { socket, setUserAndSandboxId } = useSocket();
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Pass the user and sandbox ID to the socket, causing the socket to be created and to connect.
|
// Ensure userData.id and sandboxData.id are available before attempting to connect
|
||||||
setUserAndSandboxId(userData.id, sandboxData.id);
|
if (userData.id && sandboxData.id) {
|
||||||
}, []);
|
// Check if the socket is not initialized or not connected
|
||||||
|
if (!socket || (socket && !socket.connected)) {
|
||||||
|
// Initialize socket connection
|
||||||
|
setUserAndSandboxId(userData.id, sandboxData.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [socket, userData.id, sandboxData.id, setUserAndSandboxId])
|
||||||
|
|
||||||
// Preview panel state
|
//Preview Button state
|
||||||
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true)
|
const [isPreviewCollapsed, setIsPreviewCollapsed] = useState(true)
|
||||||
|
|
||||||
// When the owner closes the project, isDisabled gets set for the other users.
|
|
||||||
const [disableAccess, setDisableAccess] = useState({
|
const [disableAccess, setDisableAccess] = useState({
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
message: "",
|
message: "",
|
||||||
@ -74,6 +70,8 @@ export default function CodeEditor({
|
|||||||
const [activeFileId, setActiveFileId] = useState<string>("")
|
const [activeFileId, setActiveFileId] = useState<string>("")
|
||||||
const [activeFileContent, setActiveFileContent] = useState("")
|
const [activeFileContent, setActiveFileContent] = useState("")
|
||||||
const [deletingFolderId, setDeletingFolderId] = useState("")
|
const [deletingFolderId, setDeletingFolderId] = useState("")
|
||||||
|
// Added this state to track the most recent content for each file
|
||||||
|
const [fileContents, setFileContents] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
// Editor state
|
// Editor state
|
||||||
const [editorLanguage, setEditorLanguage] = useState("plaintext")
|
const [editorLanguage, setEditorLanguage] = useState("plaintext")
|
||||||
@ -82,7 +80,6 @@ export default function CodeEditor({
|
|||||||
useState<monaco.editor.IStandaloneCodeEditor>()
|
useState<monaco.editor.IStandaloneCodeEditor>()
|
||||||
|
|
||||||
// AI Copilot state
|
// AI Copilot state
|
||||||
const [ai, setAi] = useState(false)
|
|
||||||
const [generate, setGenerate] = useState<{
|
const [generate, setGenerate] = useState<{
|
||||||
show: boolean
|
show: boolean
|
||||||
id: string
|
id: string
|
||||||
@ -95,7 +92,8 @@ export default function CodeEditor({
|
|||||||
options: monaco.editor.IModelDeltaDecoration[]
|
options: monaco.editor.IModelDeltaDecoration[]
|
||||||
instance: monaco.editor.IEditorDecorationsCollection | undefined
|
instance: monaco.editor.IEditorDecorationsCollection | undefined
|
||||||
}>({ options: [], instance: undefined })
|
}>({ options: [], instance: undefined })
|
||||||
|
const [isSelected, setIsSelected] = useState(false)
|
||||||
|
const [showSuggestion, setShowSuggestion] = useState(false)
|
||||||
// Terminal state
|
// Terminal state
|
||||||
const [terminals, setTerminals] = useState<
|
const [terminals, setTerminals] = useState<
|
||||||
{
|
{
|
||||||
@ -105,13 +103,13 @@ export default function CodeEditor({
|
|||||||
>([])
|
>([])
|
||||||
|
|
||||||
// Preview state
|
// Preview state
|
||||||
const [previewURL, setPreviewURL] = useState<string>("");
|
const [previewURL, setPreviewURL] = useState<string>("")
|
||||||
|
|
||||||
const loadPreviewURL = (url: string) => {
|
const loadPreviewURL = (url: string) => {
|
||||||
// This will cause a reload if previewURL changed.
|
// This will cause a reload if previewURL changed.
|
||||||
setPreviewURL(url);
|
setPreviewURL(url)
|
||||||
// If the URL didn't change, still reload the preview.
|
// If the URL didn't change, still reload the preview.
|
||||||
previewWindowRef.current?.refreshIframe();
|
previewWindowRef.current?.refreshIframe()
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOwner = sandboxData.userId === userData.id
|
const isOwner = sandboxData.userId === userData.id
|
||||||
@ -124,23 +122,29 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
// Liveblocks providers map to prevent reinitializing providers
|
// Liveblocks providers map to prevent reinitializing providers
|
||||||
type ProviderData = {
|
type ProviderData = {
|
||||||
provider: LiveblocksProvider<never, never, never, never>;
|
provider: LiveblocksProvider<never, never, never, never>
|
||||||
yDoc: Y.Doc;
|
yDoc: Y.Doc
|
||||||
yText: Y.Text;
|
yText: Y.Text
|
||||||
binding?: MonacoBinding;
|
binding?: MonacoBinding
|
||||||
onSync: (isSynced: boolean) => void;
|
onSync: (isSynced: boolean) => void
|
||||||
};
|
}
|
||||||
const providersMap = useRef(new Map<string, ProviderData>());
|
const providersMap = useRef(new Map<string, ProviderData>())
|
||||||
|
|
||||||
// Refs for libraries / features
|
// Refs for libraries / features
|
||||||
const editorContainerRef = useRef<HTMLDivElement>(null)
|
const editorContainerRef = useRef<HTMLDivElement>(null)
|
||||||
const monacoRef = useRef<typeof monaco | null>(null)
|
const monacoRef = useRef<typeof monaco | null>(null)
|
||||||
const generateRef = useRef<HTMLDivElement>(null)
|
const generateRef = useRef<HTMLDivElement>(null)
|
||||||
|
const suggestionRef = useRef<HTMLDivElement>(null)
|
||||||
const generateWidgetRef = useRef<HTMLDivElement>(null)
|
const generateWidgetRef = useRef<HTMLDivElement>(null)
|
||||||
const previewPanelRef = useRef<ImperativePanelHandle>(null)
|
const previewPanelRef = useRef<ImperativePanelHandle>(null)
|
||||||
const editorPanelRef = useRef<ImperativePanelHandle>(null)
|
const editorPanelRef = useRef<ImperativePanelHandle>(null)
|
||||||
const previewWindowRef = useRef<{ refreshIframe: () => void }>(null)
|
const previewWindowRef = useRef<{ refreshIframe: () => void }>(null)
|
||||||
|
|
||||||
|
const debouncedSetIsSelected = useRef(
|
||||||
|
debounce((value: boolean) => {
|
||||||
|
setIsSelected(value)
|
||||||
|
}, 800) //
|
||||||
|
).current
|
||||||
// Pre-mount editor keybindings
|
// Pre-mount editor keybindings
|
||||||
const handleEditorWillMount: BeforeMount = (monaco) => {
|
const handleEditorWillMount: BeforeMount = (monaco) => {
|
||||||
monaco.editor.addKeybindingRules([
|
monaco.editor.addKeybindingRules([
|
||||||
@ -157,6 +161,13 @@ export default function CodeEditor({
|
|||||||
monacoRef.current = monaco
|
monacoRef.current = monaco
|
||||||
|
|
||||||
editor.onDidChangeCursorPosition((e) => {
|
editor.onDidChangeCursorPosition((e) => {
|
||||||
|
setIsSelected(false)
|
||||||
|
const selection = editor.getSelection()
|
||||||
|
if (selection !== null) {
|
||||||
|
const hasSelection = !selection.isEmpty()
|
||||||
|
debouncedSetIsSelected(hasSelection)
|
||||||
|
setShowSuggestion(hasSelection)
|
||||||
|
}
|
||||||
const { column, lineNumber } = e.position
|
const { column, lineNumber } = e.position
|
||||||
if (lineNumber === cursorLine) return
|
if (lineNumber === cursorLine) return
|
||||||
setCursorLine(lineNumber)
|
setCursorLine(lineNumber)
|
||||||
@ -210,28 +221,63 @@ export default function CodeEditor({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const handleAiEdit = React.useCallback(() => {
|
||||||
|
if (!editorRef) return
|
||||||
|
const selection = editorRef.getSelection()
|
||||||
|
if (!selection) return
|
||||||
|
const pos = selection.getPosition()
|
||||||
|
const start = selection.getStartPosition()
|
||||||
|
const end = selection.getEndPosition()
|
||||||
|
let pref: monaco.editor.ContentWidgetPositionPreference
|
||||||
|
let id = ""
|
||||||
|
const isMultiline = start.lineNumber !== end.lineNumber
|
||||||
|
if (isMultiline) {
|
||||||
|
if (pos.lineNumber <= start.lineNumber) {
|
||||||
|
pref = monaco.editor.ContentWidgetPositionPreference.ABOVE
|
||||||
|
} else {
|
||||||
|
pref = monaco.editor.ContentWidgetPositionPreference.BELOW
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pref = monaco.editor.ContentWidgetPositionPreference.ABOVE
|
||||||
|
}
|
||||||
|
editorRef.changeViewZones(function (changeAccessor) {
|
||||||
|
if (!generateRef.current) return
|
||||||
|
if (pref === monaco.editor.ContentWidgetPositionPreference.ABOVE) {
|
||||||
|
id = changeAccessor.addZone({
|
||||||
|
afterLineNumber: start.lineNumber - 1,
|
||||||
|
heightInLines: 2,
|
||||||
|
domNode: generateRef.current,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setGenerate((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
show: true,
|
||||||
|
pref: [pref],
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [editorRef])
|
||||||
|
|
||||||
// Generate widget effect
|
// Generate widget effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ai) {
|
|
||||||
setGenerate((prev) => {
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
show: false,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (generate.show) {
|
if (generate.show) {
|
||||||
|
setShowSuggestion(false)
|
||||||
editorRef?.changeViewZones(function (changeAccessor) {
|
editorRef?.changeViewZones(function (changeAccessor) {
|
||||||
if (!generateRef.current) return
|
if (!generateRef.current) return
|
||||||
const id = changeAccessor.addZone({
|
if (!generate.id) {
|
||||||
afterLineNumber: cursorLine,
|
const id = changeAccessor.addZone({
|
||||||
heightInLines: 3,
|
afterLineNumber: cursorLine,
|
||||||
domNode: generateRef.current,
|
heightInLines: 3,
|
||||||
})
|
domNode: generateRef.current,
|
||||||
|
})
|
||||||
|
setGenerate((prev) => {
|
||||||
|
return { ...prev, id, line: cursorLine }
|
||||||
|
})
|
||||||
|
}
|
||||||
setGenerate((prev) => {
|
setGenerate((prev) => {
|
||||||
return { ...prev, id, line: cursorLine }
|
return { ...prev, line: cursorLine }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -293,20 +339,63 @@ export default function CodeEditor({
|
|||||||
}
|
}
|
||||||
}, [generate.show])
|
}, [generate.show])
|
||||||
|
|
||||||
|
// Suggestion widget effect
|
||||||
|
useEffect(() => {
|
||||||
|
if (!suggestionRef.current || !editorRef) return
|
||||||
|
const widgetElement = suggestionRef.current
|
||||||
|
const suggestionWidget: monaco.editor.IContentWidget = {
|
||||||
|
getDomNode: () => {
|
||||||
|
return widgetElement
|
||||||
|
},
|
||||||
|
getId: () => {
|
||||||
|
return "suggestion.widget"
|
||||||
|
},
|
||||||
|
getPosition: () => {
|
||||||
|
const selection = editorRef?.getSelection()
|
||||||
|
const column = Math.max(3, selection?.positionColumn ?? 1)
|
||||||
|
let lineNumber = selection?.positionLineNumber ?? 1
|
||||||
|
let pref = monaco.editor.ContentWidgetPositionPreference.ABOVE
|
||||||
|
if (lineNumber <= 3) {
|
||||||
|
pref = monaco.editor.ContentWidgetPositionPreference.BELOW
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
preference: [pref],
|
||||||
|
position: {
|
||||||
|
lineNumber,
|
||||||
|
column,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if (isSelected) {
|
||||||
|
editorRef?.addContentWidget(suggestionWidget)
|
||||||
|
editorRef?.applyFontInfo(suggestionRef.current)
|
||||||
|
} else {
|
||||||
|
editorRef?.removeContentWidget(suggestionWidget)
|
||||||
|
}
|
||||||
|
}, [isSelected])
|
||||||
|
|
||||||
// Decorations effect for generate widget tips
|
// Decorations effect for generate widget tips
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (decorations.options.length === 0) {
|
if (decorations.options.length === 0) {
|
||||||
decorations.instance?.clear()
|
decorations.instance?.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ai) return
|
|
||||||
|
|
||||||
const model = editorRef?.getModel()
|
const model = editorRef?.getModel()
|
||||||
const line = model?.getLineContent(cursorLine)
|
// added this because it was giving client side exception - Illegal value for lineNumber when opening an empty file
|
||||||
|
if (model) {
|
||||||
if (line === undefined || line.trim() !== "") {
|
const totalLines = model.getLineCount();
|
||||||
decorations.instance?.clear()
|
// Check if the cursorLine is a valid number, If cursorLine is out of bounds, we fall back to 1 (the first line) as a default safe value.
|
||||||
return
|
const lineNumber = cursorLine > 0 && cursorLine <= totalLines ? cursorLine : 1; // fallback to a valid line number
|
||||||
|
// If for some reason the content doesn't exist, we use an empty string as a fallback.
|
||||||
|
const line = model.getLineContent(lineNumber) ?? "";
|
||||||
|
// Check if the line is not empty or only whitespace (i.e., `.trim()` removes spaces).
|
||||||
|
// If the line has content, we clear any decorations using the instance of the `decorations` object.
|
||||||
|
// Decorations refer to editor highlights, underlines, or markers, so this clears those if conditions are met.
|
||||||
|
if (line.trim() !== "") {
|
||||||
|
decorations.instance?.clear();
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decorations.instance) {
|
if (decorations.instance) {
|
||||||
@ -325,33 +414,41 @@ export default function CodeEditor({
|
|||||||
}, [decorations.options])
|
}, [decorations.options])
|
||||||
|
|
||||||
// Save file keybinding logic effect
|
// Save file keybinding logic effect
|
||||||
|
// Function to save the file content after a debounce period
|
||||||
const debouncedSaveData = useCallback(
|
const debouncedSaveData = useCallback(
|
||||||
debounce((value: string | undefined, activeFileId: string | undefined) => {
|
debounce((activeFileId: string | undefined) => {
|
||||||
setTabs((prev) =>
|
if (activeFileId) {
|
||||||
prev.map((tab) =>
|
// Get the current content of the file
|
||||||
tab.id === activeFileId ? { ...tab, saved: true } : tab
|
const content = fileContents[activeFileId];
|
||||||
)
|
|
||||||
);
|
// Mark the file as saved in the tabs
|
||||||
console.log(`Saving file...${activeFileId}`);
|
setTabs((prev) =>
|
||||||
console.log(`Saving file...${value}`);
|
prev.map((tab) =>
|
||||||
socket?.emit("saveFile", activeFileId, value);
|
tab.id === activeFileId ? { ...tab, saved: true } : tab
|
||||||
|
)
|
||||||
|
);
|
||||||
|
console.log(`Saving file...${activeFileId}`);
|
||||||
|
console.log(`Saving file...${content}`);
|
||||||
|
socket?.emit("saveFile", activeFileId, content);
|
||||||
|
}
|
||||||
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000),
|
}, Number(process.env.FILE_SAVE_DEBOUNCE_DELAY) || 1000),
|
||||||
[socket]
|
[socket, fileContents]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Keydown event listener to trigger file save on Ctrl+S or Cmd+S
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const down = (e: KeyboardEvent) => {
|
const down = (e: KeyboardEvent) => {
|
||||||
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
|
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
debouncedSaveData(editorRef?.getValue(), activeFileId);
|
debouncedSaveData(activeFileId);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
document.addEventListener("keydown", down);
|
document.addEventListener("keydown", down)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", down);
|
document.removeEventListener("keydown", down)
|
||||||
};
|
}
|
||||||
}, [activeFileId, tabs, debouncedSaveData]);
|
}, [activeFileId, tabs, debouncedSaveData])
|
||||||
|
|
||||||
// Liveblocks live collaboration setup effect
|
// Liveblocks live collaboration setup effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -360,13 +457,13 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
if (!editorRef || !tab || !model) return
|
if (!editorRef || !tab || !model) return
|
||||||
|
|
||||||
let providerData: ProviderData;
|
let providerData: ProviderData
|
||||||
|
|
||||||
// When a file is opened for the first time, create a new provider and store in providersMap.
|
// When a file is opened for the first time, create a new provider and store in providersMap.
|
||||||
if (!providersMap.current.has(tab.id)) {
|
if (!providersMap.current.has(tab.id)) {
|
||||||
const yDoc = new Y.Doc();
|
const yDoc = new Y.Doc()
|
||||||
const yText = yDoc.getText(tab.id);
|
const yText = yDoc.getText(tab.id)
|
||||||
const yProvider = new LiveblocksProvider(room, yDoc);
|
const yProvider = new LiveblocksProvider(room, yDoc)
|
||||||
|
|
||||||
// Inserts the file content into the editor once when the tab is changed.
|
// Inserts the file content into the editor once when the tab is changed.
|
||||||
const onSync = (isSynced: boolean) => {
|
const onSync = (isSynced: boolean) => {
|
||||||
@ -387,12 +484,11 @@ export default function CodeEditor({
|
|||||||
yProvider.on("sync", onSync)
|
yProvider.on("sync", onSync)
|
||||||
|
|
||||||
// Save the provider to the map.
|
// Save the provider to the map.
|
||||||
providerData = { provider: yProvider, yDoc, yText, onSync };
|
providerData = { provider: yProvider, yDoc, yText, onSync }
|
||||||
providersMap.current.set(tab.id, providerData);
|
providersMap.current.set(tab.id, providerData)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// When a tab is opened that has been open before, reuse the existing provider.
|
// When a tab is opened that has been open before, reuse the existing provider.
|
||||||
providerData = providersMap.current.get(tab.id)!;
|
providerData = providersMap.current.get(tab.id)!
|
||||||
}
|
}
|
||||||
|
|
||||||
const binding = new MonacoBinding(
|
const binding = new MonacoBinding(
|
||||||
@ -400,21 +496,21 @@ export default function CodeEditor({
|
|||||||
model,
|
model,
|
||||||
new Set([editorRef]),
|
new Set([editorRef]),
|
||||||
providerData.provider.awareness as unknown as Awareness
|
providerData.provider.awareness as unknown as Awareness
|
||||||
);
|
)
|
||||||
|
|
||||||
providerData.binding = binding;
|
providerData.binding = binding
|
||||||
setProvider(providerData.provider);
|
setProvider(providerData.provider)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// Cleanup logic
|
// Cleanup logic
|
||||||
if (binding) {
|
if (binding) {
|
||||||
binding.destroy();
|
binding.destroy()
|
||||||
}
|
}
|
||||||
if (providerData.binding) {
|
if (providerData.binding) {
|
||||||
providerData.binding = undefined;
|
providerData.binding = undefined
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}, [room, activeFileContent]);
|
}, [room, activeFileContent])
|
||||||
|
|
||||||
// Added this effect to clean up when the component unmounts
|
// Added this effect to clean up when the component unmounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -422,14 +518,14 @@ export default function CodeEditor({
|
|||||||
// Clean up all providers when the component unmounts
|
// Clean up all providers when the component unmounts
|
||||||
providersMap.current.forEach((data) => {
|
providersMap.current.forEach((data) => {
|
||||||
if (data.binding) {
|
if (data.binding) {
|
||||||
data.binding.destroy();
|
data.binding.destroy()
|
||||||
}
|
}
|
||||||
data.provider.disconnect();
|
data.provider.disconnect()
|
||||||
data.yDoc.destroy();
|
data.yDoc.destroy()
|
||||||
});
|
})
|
||||||
providersMap.current.clear();
|
providersMap.current.clear()
|
||||||
};
|
}
|
||||||
}, []);
|
}, [])
|
||||||
|
|
||||||
// Connection/disconnection effect
|
// Connection/disconnection effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -487,49 +583,71 @@ export default function CodeEditor({
|
|||||||
socket?.off("disableAccess", onDisableAccess)
|
socket?.off("disableAccess", onDisableAccess)
|
||||||
socket?.off("previewURL", loadPreviewURL)
|
socket?.off("previewURL", loadPreviewURL)
|
||||||
}
|
}
|
||||||
}, [socket, terminals, setTerminals, setFiles, toast, setDisableAccess, isOwner, loadPreviewURL]);
|
}, [
|
||||||
|
socket,
|
||||||
|
terminals,
|
||||||
|
setTerminals,
|
||||||
|
setFiles,
|
||||||
|
toast,
|
||||||
|
setDisableAccess,
|
||||||
|
isOwner,
|
||||||
|
loadPreviewURL,
|
||||||
|
])
|
||||||
|
|
||||||
// Helper functions for tabs:
|
// Helper functions for tabs:
|
||||||
|
|
||||||
// Select file and load content
|
// Select file and load content
|
||||||
|
|
||||||
// Initialize debounced function once
|
// Initialize debounced function once
|
||||||
const fileCache = useRef(new Map());
|
const fileCache = useRef(new Map())
|
||||||
|
|
||||||
// Debounced function to get file content
|
// Debounced function to get file content
|
||||||
const debouncedGetFile =
|
const debouncedGetFile = (tabId: any, callback: any) => {
|
||||||
(tabId: any, callback: any) => {
|
socket?.emit("getFile", tabId, callback)
|
||||||
socket?.emit('getFile', tabId, callback);
|
|
||||||
} // 300ms debounce delay, adjust as needed
|
} // 300ms debounce delay, adjust as needed
|
||||||
|
|
||||||
const selectFile = (tab: TTab) => {
|
const selectFile = (tab: TTab) => {
|
||||||
|
|
||||||
if (tab.id === activeFileId) return;
|
if (tab.id === activeFileId) return;
|
||||||
|
|
||||||
setGenerate((prev) => ({ ...prev, show: false }));
|
setGenerate((prev) => ({ ...prev, show: false }));
|
||||||
|
|
||||||
|
// Check if the tab already exists in the list of open tabs
|
||||||
const exists = tabs.find((t) => t.id === tab.id);
|
const exists = tabs.find((t) => t.id === tab.id);
|
||||||
setTabs((prev) => {
|
setTabs((prev) => {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
|
// If the tab exists, make it the active tab
|
||||||
setActiveFileId(exists.id);
|
setActiveFileId(exists.id);
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
// If the tab doesn't exist, add it to the list of tabs and make it active
|
||||||
return [...prev, tab];
|
return [...prev, tab];
|
||||||
});
|
});
|
||||||
|
|
||||||
if (fileCache.current.has(tab.id)) {
|
// If the file's content is already cached, set it as the active content
|
||||||
setActiveFileContent(fileCache.current.get(tab.id));
|
if (fileContents[tab.id]) {
|
||||||
|
setActiveFileContent(fileContents[tab.id]);
|
||||||
} else {
|
} else {
|
||||||
debouncedGetFile(tab.id, (response: SetStateAction<string>) => {
|
// Otherwise, fetch the content of the file and cache it
|
||||||
fileCache.current.set(tab.id, response);
|
debouncedGetFile(tab.id, (response: string) => {
|
||||||
|
setFileContents(prev => ({ ...prev, [tab.id]: response }));
|
||||||
setActiveFileContent(response);
|
setActiveFileContent(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the editor language based on the file type
|
||||||
setEditorLanguage(processFileType(tab.name));
|
setEditorLanguage(processFileType(tab.name));
|
||||||
|
// Set the active file ID to the new tab
|
||||||
setActiveFileId(tab.id);
|
setActiveFileId(tab.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Added this effect to update fileContents when the editor content changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeFileId) {
|
||||||
|
// Cache the current active file content using the file ID as the key
|
||||||
|
setFileContents(prev => ({ ...prev, [activeFileId]: activeFileContent }));
|
||||||
|
}
|
||||||
|
}, [activeFileContent, activeFileId]);
|
||||||
|
|
||||||
// Close tab and remove from tabs
|
// Close tab and remove from tabs
|
||||||
const closeTab = (id: string) => {
|
const closeTab = (id: string) => {
|
||||||
const numTabs = tabs.length
|
const numTabs = tabs.length
|
||||||
@ -649,30 +767,74 @@ export default function CodeEditor({
|
|||||||
{/* Copilot DOM elements */}
|
{/* Copilot DOM elements */}
|
||||||
<PreviewProvider>
|
<PreviewProvider>
|
||||||
<div ref={generateRef} />
|
<div ref={generateRef} />
|
||||||
|
<div ref={suggestionRef} className="absolute">
|
||||||
|
<AnimatePresence>
|
||||||
|
{isSelected && showSuggestion && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ ease: "easeOut", duration: 0.2 }}
|
||||||
|
>
|
||||||
|
<Button size="xs" type="submit" onClick={handleAiEdit}>
|
||||||
|
<Sparkles className="h-3 w-3 mr-1" />
|
||||||
|
Edit Code
|
||||||
|
</Button>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
<div className="z-50 p-1" ref={generateWidgetRef}>
|
<div className="z-50 p-1" ref={generateWidgetRef}>
|
||||||
{generate.show && ai ? (
|
{generate.show ? (
|
||||||
<GenerateInput
|
<GenerateInput
|
||||||
user={userData}
|
user={userData}
|
||||||
socket={socket!}
|
socket={socket!}
|
||||||
width={generate.width - 90}
|
width={generate.width - 90}
|
||||||
data={{
|
data={{
|
||||||
fileName: tabs.find((t) => t.id === activeFileId)?.name ?? "",
|
fileName: tabs.find((t) => t.id === activeFileId)?.name ?? "",
|
||||||
code: editorRef?.getValue() ?? "",
|
code:
|
||||||
|
(isSelected && editorRef?.getSelection()
|
||||||
|
? editorRef
|
||||||
|
?.getModel()
|
||||||
|
?.getValueInRange(editorRef?.getSelection()!)
|
||||||
|
: editorRef?.getValue()) ?? "",
|
||||||
line: generate.line,
|
line: generate.line,
|
||||||
}}
|
}}
|
||||||
editor={{
|
editor={{
|
||||||
language: editorLanguage,
|
language: editorLanguage,
|
||||||
}}
|
}}
|
||||||
onExpand={() => {
|
onExpand={() => {
|
||||||
|
const line = generate.line
|
||||||
|
|
||||||
editorRef?.changeViewZones(function (changeAccessor) {
|
editorRef?.changeViewZones(function (changeAccessor) {
|
||||||
changeAccessor.removeZone(generate.id)
|
changeAccessor.removeZone(generate.id)
|
||||||
|
|
||||||
if (!generateRef.current) return
|
if (!generateRef.current) return
|
||||||
const id = changeAccessor.addZone({
|
let id = ""
|
||||||
afterLineNumber: cursorLine,
|
if (isSelected) {
|
||||||
heightInLines: 12,
|
const selection = editorRef?.getSelection()
|
||||||
domNode: generateRef.current,
|
if (!selection) return
|
||||||
})
|
const isAbove =
|
||||||
|
generate.pref?.[0] ===
|
||||||
|
monaco.editor.ContentWidgetPositionPreference.ABOVE
|
||||||
|
const afterLineNumber = isAbove ? line - 1 : line
|
||||||
|
id = changeAccessor.addZone({
|
||||||
|
afterLineNumber,
|
||||||
|
heightInLines: isAbove ? 11 : 12,
|
||||||
|
domNode: generateRef.current,
|
||||||
|
})
|
||||||
|
const contentWidget = generate.widget
|
||||||
|
if (contentWidget) {
|
||||||
|
editorRef?.layoutContentWidget(contentWidget)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
id = changeAccessor.addZone({
|
||||||
|
afterLineNumber: cursorLine,
|
||||||
|
heightInLines: 12,
|
||||||
|
|
||||||
|
domNode: generateRef.current,
|
||||||
|
})
|
||||||
|
}
|
||||||
setGenerate((prev) => {
|
setGenerate((prev) => {
|
||||||
return { ...prev, id }
|
return { ...prev, id }
|
||||||
})
|
})
|
||||||
@ -686,12 +848,14 @@ export default function CodeEditor({
|
|||||||
show: !prev.show,
|
show: !prev.show,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const file = editorRef?.getValue()
|
const selection = editorRef?.getSelection()
|
||||||
|
const range =
|
||||||
const lines = file?.split("\n") || []
|
isSelected && selection
|
||||||
lines.splice(line - 1, 0, code)
|
? selection
|
||||||
const updatedFile = lines.join("\n")
|
: new monaco.Range(line, 1, line, 1)
|
||||||
editorRef?.setValue(updatedFile)
|
editorRef?.executeEdits("ai-generation", [
|
||||||
|
{ range, text: code, forceMoveMarkers: true },
|
||||||
|
])
|
||||||
}}
|
}}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setGenerate((prev) => {
|
setGenerate((prev) => {
|
||||||
@ -717,9 +881,6 @@ export default function CodeEditor({
|
|||||||
setFiles={setFiles}
|
setFiles={setFiles}
|
||||||
addNew={(name, type) => addNew(name, type, setFiles, sandboxData)}
|
addNew={(name, type) => addNew(name, type, setFiles, sandboxData)}
|
||||||
deletingFolderId={deletingFolderId}
|
deletingFolderId={deletingFolderId}
|
||||||
// AI Copilot Toggle
|
|
||||||
ai={ai}
|
|
||||||
setAi={setAi}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Shadcn resizeable panels: https://ui.shadcn.com/docs/components/resizable */}
|
{/* Shadcn resizeable panels: https://ui.shadcn.com/docs/components/resizable */}
|
||||||
@ -771,15 +932,10 @@ export default function CodeEditor({
|
|||||||
beforeMount={handleEditorWillMount}
|
beforeMount={handleEditorWillMount}
|
||||||
onMount={handleEditorMount}
|
onMount={handleEditorMount}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (value === activeFileContent) {
|
// If the new content is different from the cached content, update it
|
||||||
setTabs((prev) =>
|
if (value !== fileContents[activeFileId]) {
|
||||||
prev.map((tab) =>
|
setActiveFileContent(value ?? ""); // Update the active file content
|
||||||
tab.id === activeFileId
|
// Mark the file as unsaved by setting 'saved' to false
|
||||||
? { ...tab, saved: true }
|
|
||||||
: tab
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
setTabs((prev) =>
|
setTabs((prev) =>
|
||||||
prev.map((tab) =>
|
prev.map((tab) =>
|
||||||
tab.id === activeFileId
|
tab.id === activeFileId
|
||||||
@ -787,6 +943,15 @@ export default function CodeEditor({
|
|||||||
: tab
|
: tab
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
// If the content matches the cached content, mark the file as saved
|
||||||
|
setTabs((prev) =>
|
||||||
|
prev.map((tab) =>
|
||||||
|
tab.id === activeFileId
|
||||||
|
? { ...tab, saved: true }
|
||||||
|
: tab
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
options={{
|
options={{
|
||||||
@ -831,7 +996,11 @@ export default function CodeEditor({
|
|||||||
open={() => {
|
open={() => {
|
||||||
usePreview().previewPanelRef.current?.expand()
|
usePreview().previewPanelRef.current?.expand()
|
||||||
setIsPreviewCollapsed(false)
|
setIsPreviewCollapsed(false)
|
||||||
}} collapsed={isPreviewCollapsed} src={previewURL} ref={previewWindowRef} />
|
}}
|
||||||
|
collapsed={isPreviewCollapsed}
|
||||||
|
src={previewURL}
|
||||||
|
ref={previewWindowRef}
|
||||||
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle />
|
<ResizableHandle />
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
@ -855,4 +1024,3 @@ export default function CodeEditor({
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ export default function Navbar({
|
|||||||
const [isShareOpen, setIsShareOpen] = useState(false);
|
const [isShareOpen, setIsShareOpen] = useState(false);
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
|
|
||||||
const isOwner = sandboxData.userId === userData.id;
|
const isOwner = sandboxData.userId === userData.id;;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { Play, StopCircle } from "lucide-react";
|
import { Play, StopCircle } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useTerminal } from "@/context/TerminalContext";
|
import { useTerminal } from "@/context/TerminalContext";
|
||||||
@ -16,53 +17,57 @@ export default function RunButtonModal({
|
|||||||
setIsRunning: (running: boolean) => void;
|
setIsRunning: (running: boolean) => void;
|
||||||
sandboxData: Sandbox;
|
sandboxData: Sandbox;
|
||||||
}) {
|
}) {
|
||||||
const { createNewTerminal, terminals, closeTerminal } = useTerminal();
|
const { createNewTerminal, closeTerminal, terminals } = useTerminal();
|
||||||
const { setIsPreviewCollapsed, previewPanelRef } = usePreview();
|
const { setIsPreviewCollapsed, previewPanelRef } = usePreview();
|
||||||
|
// Ref to keep track of the last created terminal's ID
|
||||||
|
const lastCreatedTerminalRef = useRef<string | null>(null);
|
||||||
|
|
||||||
const handleRun = () => {
|
// Effect to update the lastCreatedTerminalRef when a new terminal is added
|
||||||
if (isRunning) {
|
useEffect(() => {
|
||||||
console.log('Stopping sandbox...');
|
if (terminals.length > 0 && !isRunning) {
|
||||||
console.log('Closing Preview Window');
|
const latestTerminal = terminals[terminals.length - 1];
|
||||||
|
if (latestTerminal && latestTerminal.id !== lastCreatedTerminalRef.current) {
|
||||||
terminals.forEach(term => {
|
lastCreatedTerminalRef.current = latestTerminal.id;
|
||||||
if (term.terminal) {
|
|
||||||
closeTerminal(term.id);
|
|
||||||
console.log('Closing Terminal', term.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setIsPreviewCollapsed(true);
|
|
||||||
previewPanelRef.current?.collapse();
|
|
||||||
} else {
|
|
||||||
console.log('Running sandbox...');
|
|
||||||
console.log('Opening Terminal');
|
|
||||||
console.log('Opening Preview Window');
|
|
||||||
|
|
||||||
if (terminals.length < 4) {
|
|
||||||
if (sandboxData.type === "streamlit") {
|
|
||||||
createNewTerminal(
|
|
||||||
"pip install -r requirements.txt && streamlit run main.py --server.runOnSave true"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
createNewTerminal("yarn install && yarn dev");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast.error("You reached the maximum # of terminals.");
|
|
||||||
console.error("Maximum number of terminals reached.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsPreviewCollapsed(false);
|
|
||||||
previewPanelRef.current?.expand();
|
|
||||||
}
|
}
|
||||||
|
}, [terminals, isRunning]);
|
||||||
|
|
||||||
|
const handleRun = async () => {
|
||||||
|
if (isRunning && lastCreatedTerminalRef.current)
|
||||||
|
{
|
||||||
|
await closeTerminal(lastCreatedTerminalRef.current);
|
||||||
|
lastCreatedTerminalRef.current = null;
|
||||||
|
setIsPreviewCollapsed(true);
|
||||||
|
previewPanelRef.current?.collapse();
|
||||||
|
}
|
||||||
|
else if (!isRunning && terminals.length < 4)
|
||||||
|
{
|
||||||
|
const command = sandboxData.type === "streamlit"
|
||||||
|
? "pip install -r requirements.txt && streamlit run main.py --server.runOnSave true"
|
||||||
|
: "yarn install && yarn dev";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a new terminal with the appropriate command
|
||||||
|
await createNewTerminal(command);
|
||||||
|
setIsPreviewCollapsed(false);
|
||||||
|
previewPanelRef.current?.expand();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to create new terminal.");
|
||||||
|
console.error("Error creating new terminal:", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (!isRunning) {
|
||||||
|
toast.error("You've reached the maximum number of terminals.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsRunning(!isRunning);
|
setIsRunning(!isRunning);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Button variant="outline" onClick={handleRun}>
|
||||||
<Button variant="outline" onClick={handleRun}>
|
{isRunning ? <StopCircle className="w-4 h-4 mr-2" /> : <Play className="w-4 h-4 mr-2" />}
|
||||||
{isRunning ? <StopCircle className="w-4 h-4 mr-2" /> : <Play className="w-4 h-4 mr-2" />}
|
{isRunning ? 'Stop' : 'Run'}
|
||||||
{isRunning ? 'Stop' : 'Run'}
|
</Button>
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -32,8 +32,6 @@ export default function Sidebar({
|
|||||||
socket,
|
socket,
|
||||||
setFiles,
|
setFiles,
|
||||||
addNew,
|
addNew,
|
||||||
ai,
|
|
||||||
setAi,
|
|
||||||
deletingFolderId,
|
deletingFolderId,
|
||||||
}: {
|
}: {
|
||||||
sandboxData: Sandbox;
|
sandboxData: Sandbox;
|
||||||
@ -50,8 +48,6 @@ export default function Sidebar({
|
|||||||
socket: Socket;
|
socket: Socket;
|
||||||
setFiles: (files: (TFile | TFolder)[]) => void;
|
setFiles: (files: (TFile | TFolder)[]) => void;
|
||||||
addNew: (name: string, type: "file" | "folder") => void;
|
addNew: (name: string, type: "file" | "folder") => void;
|
||||||
ai: boolean;
|
|
||||||
setAi: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
deletingFolderId: string;
|
deletingFolderId: string;
|
||||||
}) {
|
}) {
|
||||||
const ref = useRef(null); // drop target
|
const ref = useRef(null); // drop target
|
||||||
@ -186,20 +182,6 @@ export default function Sidebar({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full space-y-4">
|
<div className="w-full space-y-4">
|
||||||
<div className="flex items-center justify-between w-full">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Sparkles
|
|
||||||
className={`h-4 w-4 mr-2 ${
|
|
||||||
ai ? "text-indigo-500" : "text-muted-foreground"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
Copilot{" "}
|
|
||||||
<span className="font-mono text-muted-foreground inline-block ml-1.5 text-xs leading-none border border-b-2 border-muted-foreground py-1 px-1.5 rounded-md">
|
|
||||||
⌘G
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Switch checked={ai} onCheckedChange={setAi} />
|
|
||||||
</div>
|
|
||||||
{/* <Button className="w-full">
|
{/* <Button className="w-full">
|
||||||
<MonitorPlay className="w-4 h-4 mr-2" /> Run
|
<MonitorPlay className="w-4 h-4 mr-2" /> Run
|
||||||
</Button> */}
|
</Button> */}
|
||||||
|
@ -22,6 +22,7 @@ const buttonVariants = cva(
|
|||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2",
|
default: "h-9 px-4 py-2",
|
||||||
|
xs: "h-6 px-2.5 py-1.5 rounded-sm text-[0.7rem]",
|
||||||
sm: "h-8 rounded-md px-3 text-xs",
|
sm: "h-8 rounded-md px-3 text-xs",
|
||||||
lg: "h-10 rounded-md px-8",
|
lg: "h-10 rounded-md px-8",
|
||||||
icon: "h-9 w-9",
|
icon: "h-9 w-9",
|
||||||
|
@ -42,13 +42,13 @@ export default function UserButton({ userData }: { userData: User }) {
|
|||||||
<div className="py-1.5 px-2 w-full flex flex-col items-start text-sm">
|
<div className="py-1.5 px-2 w-full flex flex-col items-start text-sm">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Sparkles className={`h-4 w-4 mr-2 text-indigo-500`} />
|
<Sparkles className={`h-4 w-4 mr-2 text-indigo-500`} />
|
||||||
AI Usage: {userData.generations}/10
|
AI Usage: {userData.generations}/1000
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-full w-full mt-2 h-2 overflow-hidden bg-secondary">
|
<div className="rounded-full w-full mt-2 h-2 overflow-hidden bg-secondary">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-indigo-500 rounded-full"
|
className="h-full bg-indigo-500 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
width: `${(userData.generations * 100) / 10}%`,
|
width: `${(userData.generations * 100) / 1000}%`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
295
frontend/lib/file-extension-to-language.json
Normal file
295
frontend/lib/file-extension-to-language.json
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
{
|
||||||
|
"_coffee": "coffeescript",
|
||||||
|
"_js": "javascript",
|
||||||
|
"adp": "tcl",
|
||||||
|
"al": "perl",
|
||||||
|
"ant": "xml",
|
||||||
|
"aw": "php",
|
||||||
|
"axml": "xml",
|
||||||
|
"bash": "shell",
|
||||||
|
"bats": "shell",
|
||||||
|
"bones": "javascript",
|
||||||
|
"boot": "clojure",
|
||||||
|
"builder": "ruby",
|
||||||
|
"bzl": "python",
|
||||||
|
"c": "c",
|
||||||
|
"c++": "cpp",
|
||||||
|
"cake": "coffeescript",
|
||||||
|
"cats": "c",
|
||||||
|
"cc": "cpp",
|
||||||
|
"ccxml": "xml",
|
||||||
|
"cfg": "ini",
|
||||||
|
"cgi": "shell",
|
||||||
|
"cjsx": "coffeescript",
|
||||||
|
"cl2": "clojure",
|
||||||
|
"clixml": "xml",
|
||||||
|
"clj": "clojure",
|
||||||
|
"cljc": "clojure",
|
||||||
|
"cljs.hl": "clojure",
|
||||||
|
"cljs": "clojure",
|
||||||
|
"cljscm": "clojure",
|
||||||
|
"cljx": "clojure",
|
||||||
|
"coffee": "coffeescript",
|
||||||
|
"command": "shell",
|
||||||
|
"cp": "cpp",
|
||||||
|
"cpp": "cpp",
|
||||||
|
"cproject": "xml",
|
||||||
|
"cql": "sql",
|
||||||
|
"csl": "xml",
|
||||||
|
"cson": "coffeescript",
|
||||||
|
"csproj": "xml",
|
||||||
|
"ct": "xml",
|
||||||
|
"ctp": "php",
|
||||||
|
"cxx": "cpp",
|
||||||
|
"ddl": "sql",
|
||||||
|
"dfm": "pascal",
|
||||||
|
"dita": "xml",
|
||||||
|
"ditamap": "xml",
|
||||||
|
"ditaval": "xml",
|
||||||
|
"dll.config": "xml",
|
||||||
|
"dotsettings": "xml",
|
||||||
|
"dpr": "pascal",
|
||||||
|
"ecl": "ecl",
|
||||||
|
"eclxml": "ecl",
|
||||||
|
"es": "javascript",
|
||||||
|
"es6": "javascript",
|
||||||
|
"ex": "elixir",
|
||||||
|
"exs": "elixir",
|
||||||
|
"fcgi": "shell",
|
||||||
|
"filters": "xml",
|
||||||
|
"frag": "javascript",
|
||||||
|
"fsproj": "xml",
|
||||||
|
"fxml": "xml",
|
||||||
|
"gemspec": "ruby",
|
||||||
|
"geojson": "json",
|
||||||
|
"glade": "xml",
|
||||||
|
"gml": "xml",
|
||||||
|
"god": "ruby",
|
||||||
|
"grxml": "xml",
|
||||||
|
"gs": "javascript",
|
||||||
|
"gyp": "python",
|
||||||
|
"h": "cpp",
|
||||||
|
"h++": "cpp",
|
||||||
|
"handlebars": "handlebars",
|
||||||
|
"hbs": "handlebars",
|
||||||
|
"hcl": "hcl",
|
||||||
|
"hh": "cpp",
|
||||||
|
"hic": "clojure",
|
||||||
|
"hpp": "cpp",
|
||||||
|
"htm": "html",
|
||||||
|
"html.hl": "html",
|
||||||
|
"html": "html",
|
||||||
|
"hxx": "cpp",
|
||||||
|
"iced": "coffeescript",
|
||||||
|
"idc": "c",
|
||||||
|
"iml": "xml",
|
||||||
|
"inc": "sql",
|
||||||
|
"ini": "ini",
|
||||||
|
"inl": "cpp",
|
||||||
|
"ipp": "cpp",
|
||||||
|
"irbrc": "ruby",
|
||||||
|
"ivy": "xml",
|
||||||
|
"j2": "python",
|
||||||
|
"jake": "javascript",
|
||||||
|
"jbuilder": "ruby",
|
||||||
|
"jelly": "xml",
|
||||||
|
"jinja": "python",
|
||||||
|
"jinja2": "python",
|
||||||
|
"js": "javascript",
|
||||||
|
"jsb": "javascript",
|
||||||
|
"jscad": "javascript",
|
||||||
|
"jsfl": "javascript",
|
||||||
|
"jsm": "javascript",
|
||||||
|
"json": "json",
|
||||||
|
"jsproj": "xml",
|
||||||
|
"jss": "javascript",
|
||||||
|
"kml": "xml",
|
||||||
|
"ksh": "shell",
|
||||||
|
"kt": "kotlin",
|
||||||
|
"ktm": "kotlin",
|
||||||
|
"kts": "kotlin",
|
||||||
|
"launch": "xml",
|
||||||
|
"lmi": "python",
|
||||||
|
"lock": "json",
|
||||||
|
"lpr": "pascal",
|
||||||
|
"lua": "lua",
|
||||||
|
"markdown": "markdown",
|
||||||
|
"md": "markdown",
|
||||||
|
"mdpolicy": "xml",
|
||||||
|
"mkd": "markdown",
|
||||||
|
"mkdn": "markdown",
|
||||||
|
"mkdown": "markdown",
|
||||||
|
"mm": "xml",
|
||||||
|
"mod": "xml",
|
||||||
|
"mspec": "ruby",
|
||||||
|
"mustache": "python",
|
||||||
|
"mxml": "xml",
|
||||||
|
"njs": "javascript",
|
||||||
|
"nproj": "xml",
|
||||||
|
"nse": "lua",
|
||||||
|
"nuspec": "xml",
|
||||||
|
"odd": "xml",
|
||||||
|
"osm": "xml",
|
||||||
|
"pac": "javascript",
|
||||||
|
"pas": "pascal",
|
||||||
|
"pd_lua": "lua",
|
||||||
|
"perl": "perl",
|
||||||
|
"ph": "perl",
|
||||||
|
"php": "php",
|
||||||
|
"php3": "php",
|
||||||
|
"php4": "php",
|
||||||
|
"php5": "php",
|
||||||
|
"phps": "php",
|
||||||
|
"phpt": "php",
|
||||||
|
"pl": "perl",
|
||||||
|
"plist": "xml",
|
||||||
|
"pluginspec": "xml",
|
||||||
|
"plx": "perl",
|
||||||
|
"pm": "perl",
|
||||||
|
"pod": "perl",
|
||||||
|
"podspec": "ruby",
|
||||||
|
"pp": "pascal",
|
||||||
|
"prc": "sql",
|
||||||
|
"prefs": "ini",
|
||||||
|
"pro": "ini",
|
||||||
|
"properties": "ini",
|
||||||
|
"props": "xml",
|
||||||
|
"ps1": "powershell",
|
||||||
|
"ps1xml": "xml",
|
||||||
|
"psc1": "xml",
|
||||||
|
"psd1": "powershell",
|
||||||
|
"psgi": "perl",
|
||||||
|
"psm1": "powershell",
|
||||||
|
"pt": "xml",
|
||||||
|
"py": "python",
|
||||||
|
"pyde": "python",
|
||||||
|
"pyp": "python",
|
||||||
|
"pyt": "python",
|
||||||
|
"pyw": "python",
|
||||||
|
"r": "r",
|
||||||
|
"rabl": "ruby",
|
||||||
|
"rake": "ruby",
|
||||||
|
"rb": "ruby",
|
||||||
|
"rbuild": "ruby",
|
||||||
|
"rbw": "ruby",
|
||||||
|
"rbx": "ruby",
|
||||||
|
"rbxs": "lua",
|
||||||
|
"rd": "r",
|
||||||
|
"rdf": "xml",
|
||||||
|
"reek": "yaml",
|
||||||
|
"rest.txt": "restructuredtext",
|
||||||
|
"rest": "restructuredtext",
|
||||||
|
"ron": "markdown",
|
||||||
|
"rpy": "python",
|
||||||
|
"rq": "sparql",
|
||||||
|
"rs.in": "rust",
|
||||||
|
"rs": "rust",
|
||||||
|
"rss": "xml",
|
||||||
|
"rst.txt": "restructuredtext",
|
||||||
|
"rst": "restructuredtext",
|
||||||
|
"rsx": "r",
|
||||||
|
"ru": "ruby",
|
||||||
|
"ruby": "ruby",
|
||||||
|
"rviz": "yaml",
|
||||||
|
"sbt": "scala",
|
||||||
|
"sc": "scala",
|
||||||
|
"scala": "scala",
|
||||||
|
"scm": "scheme",
|
||||||
|
"scxml": "xml",
|
||||||
|
"sh.in": "shell",
|
||||||
|
"sh": "shell",
|
||||||
|
"sjs": "javascript",
|
||||||
|
"sld": "scheme",
|
||||||
|
"sls": "scheme",
|
||||||
|
"sparql": "sparql",
|
||||||
|
"sps": "scheme",
|
||||||
|
"sql": "sql",
|
||||||
|
"srdf": "xml",
|
||||||
|
"ss": "scheme",
|
||||||
|
"ssjs": "javascript",
|
||||||
|
"st": "html",
|
||||||
|
"storyboard": "xml",
|
||||||
|
"sttheme": "xml",
|
||||||
|
"sublime_metrics": "javascript",
|
||||||
|
"sublime_session": "javascript",
|
||||||
|
"sublime-build": "javascript",
|
||||||
|
"sublime-commands": "javascript",
|
||||||
|
"sublime-completions": "javascript",
|
||||||
|
"sublime-keymap": "javascript",
|
||||||
|
"sublime-macro": "javascript",
|
||||||
|
"sublime-menu": "javascript",
|
||||||
|
"sublime-mousemap": "javascript",
|
||||||
|
"sublime-project": "javascript",
|
||||||
|
"sublime-settings": "javascript",
|
||||||
|
"sublime-snippet": "xml",
|
||||||
|
"sublime-syntax": "yaml",
|
||||||
|
"sublime-theme": "javascript",
|
||||||
|
"sublime-workspace": "javascript",
|
||||||
|
"sv": "systemverilog",
|
||||||
|
"svh": "systemverilog",
|
||||||
|
"syntax": "yaml",
|
||||||
|
"t": "perl",
|
||||||
|
"tab": "sql",
|
||||||
|
"tac": "python",
|
||||||
|
"targets": "xml",
|
||||||
|
"tcc": "cpp",
|
||||||
|
"tcl": "tcl",
|
||||||
|
"tf": "hcl",
|
||||||
|
"thor": "ruby",
|
||||||
|
"tm": "tcl",
|
||||||
|
"tmcommand": "xml",
|
||||||
|
"tml": "xml",
|
||||||
|
"tmlanguage": "xml",
|
||||||
|
"tmpreferences": "xml",
|
||||||
|
"tmsnippet": "xml",
|
||||||
|
"tmtheme": "xml",
|
||||||
|
"tmux": "shell",
|
||||||
|
"tool": "shell",
|
||||||
|
"topojson": "json",
|
||||||
|
"tpp": "cpp",
|
||||||
|
"ts": "typescript",
|
||||||
|
"tsx": "typescript",
|
||||||
|
"udf": "sql",
|
||||||
|
"ui": "xml",
|
||||||
|
"urdf": "xml",
|
||||||
|
"ux": "xml",
|
||||||
|
"v": "verilog",
|
||||||
|
"vbproj": "xml",
|
||||||
|
"vcxproj": "xml",
|
||||||
|
"veo": "verilog",
|
||||||
|
"vh": "systemverilog",
|
||||||
|
"viw": "sql",
|
||||||
|
"vssettings": "xml",
|
||||||
|
"vxml": "xml",
|
||||||
|
"w": "c",
|
||||||
|
"watchr": "ruby",
|
||||||
|
"wlua": "lua",
|
||||||
|
"wsdl": "xml",
|
||||||
|
"wsf": "xml",
|
||||||
|
"wsgi": "python",
|
||||||
|
"wxi": "xml",
|
||||||
|
"wxl": "xml",
|
||||||
|
"wxs": "xml",
|
||||||
|
"x3d": "xml",
|
||||||
|
"xacro": "xml",
|
||||||
|
"xaml": "xml",
|
||||||
|
"xht": "html",
|
||||||
|
"xhtml": "html",
|
||||||
|
"xib": "xml",
|
||||||
|
"xlf": "xml",
|
||||||
|
"xliff": "xml",
|
||||||
|
"xmi": "xml",
|
||||||
|
"xml.dist": "xml",
|
||||||
|
"xml": "xml",
|
||||||
|
"xproj": "xml",
|
||||||
|
"xpy": "python",
|
||||||
|
"xsd": "xml",
|
||||||
|
"xsjs": "javascript",
|
||||||
|
"xsjslib": "javascript",
|
||||||
|
"xul": "xml",
|
||||||
|
"yaml-tmlanguage": "yaml",
|
||||||
|
"yaml": "yaml",
|
||||||
|
"yml": "yaml",
|
||||||
|
"zcml": "xml",
|
||||||
|
"zsh": "shell"
|
||||||
|
}
|
@ -2,18 +2,19 @@ import { type ClassValue, clsx } from "clsx"
|
|||||||
// import { toast } from "sonner"
|
// import { toast } from "sonner"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import { Sandbox, TFile, TFolder } from "./types"
|
import { Sandbox, TFile, TFolder } from "./types"
|
||||||
|
import fileExtToLang from "./file-extension-to-language.json"
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processFileType(file: string) {
|
export function processFileType(file: string) {
|
||||||
const ending = file.split(".").pop()
|
const extension = file.split(".").pop()
|
||||||
|
const fileExtToLangMap = fileExtToLang as Record<string, string>
|
||||||
|
if (extension && fileExtToLangMap[extension]) {
|
||||||
|
return fileExtToLangMap[extension]
|
||||||
|
}
|
||||||
|
|
||||||
if (ending === "ts" || ending === "tsx") return "typescript"
|
|
||||||
if (ending === "js" || ending === "jsx") return "javascript"
|
|
||||||
|
|
||||||
if (ending) return ending
|
|
||||||
return "plaintext"
|
return "plaintext"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,12 +63,15 @@ export function addNew(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function debounce<T extends (...args: any[]) => void>(func: T, wait: number): T {
|
export function debounce<T extends (...args: any[]) => void>(
|
||||||
let timeout: NodeJS.Timeout | null = null;
|
func: T,
|
||||||
|
wait: number
|
||||||
|
): T {
|
||||||
|
let timeout: NodeJS.Timeout | null = null
|
||||||
return function (...args: Parameters<T>) {
|
return function (...args: Parameters<T>) {
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout)
|
||||||
}
|
}
|
||||||
timeout = setTimeout(() => func(...args), wait);
|
timeout = setTimeout(() => func(...args), wait)
|
||||||
} as T;
|
} as T
|
||||||
}
|
}
|
Reference in New Issue
Block a user