diff --git a/app/components/workbench/Workbench.client.tsx b/app/components/workbench/Workbench.client.tsx
index b8142a66..839e9a80 100644
--- a/app/components/workbench/Workbench.client.tsx
+++ b/app/components/workbench/Workbench.client.tsx
@@ -122,15 +122,26 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
{selectedView === 'code' && (
- {
- workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
- }}
- >
-
- Toggle Terminal
-
+ <>
+ {
+ workbenchStore.downloadZip();
+ }}
+ >
+
+ Download Code
+
+ {
+ workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
+ }}
+ >
+
+ Toggle Terminal
+
+ >
)}
1) {
+ let currentFolder = zip;
+ for (let i = 0; i < pathSegments.length - 1; i++) {
+ currentFolder = currentFolder.folder(pathSegments[i])!;
+ }
+ currentFolder.file(pathSegments[pathSegments.length - 1], dirent.content);
+ } else {
+ // If there's only one segment, it's a file in the root
+ zip.file(relativePath, dirent.content);
+ }
+ }
+ }
+
+ const content = await zip.generateAsync({ type: 'blob' });
+ saveAs(content, 'project.zip');
+ }
}
export const workbenchStore = new WorkbenchStore();
diff --git a/package.json b/package.json
index 6cc49c7e..7f3faead 100644
--- a/package.json
+++ b/package.json
@@ -59,10 +59,12 @@
"ai": "^3.4.9",
"date-fns": "^3.6.0",
"diff": "^5.2.0",
+ "file-saver": "^2.0.5",
"framer-motion": "^11.2.12",
"isbot": "^4.1.0",
"istextorbinary": "^9.5.0",
"jose": "^5.6.3",
+ "jszip": "^3.10.1",
"nanostores": "^0.10.3",
"ollama-ai-provider": "^0.15.2",
"react": "^18.2.0",
@@ -84,6 +86,7 @@
"@cloudflare/workers-types": "^4.20240620.0",
"@remix-run/dev": "^2.10.0",
"@types/diff": "^5.2.1",
+ "@types/file-saver": "^2.0.7",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"fast-glob": "^3.3.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 75f0b99c..69a4b0c2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -119,6 +119,9 @@ importers:
diff:
specifier: ^5.2.0
version: 5.2.0
+ file-saver:
+ specifier: ^2.0.5
+ version: 2.0.5
framer-motion:
specifier: ^11.2.12
version: 11.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -131,6 +134,9 @@ importers:
jose:
specifier: ^5.6.3
version: 5.6.3
+ jszip:
+ specifier: ^3.10.1
+ version: 3.10.1
nanostores:
specifier: ^0.10.3
version: 0.10.3
@@ -189,6 +195,9 @@ importers:
'@types/diff':
specifier: ^5.2.1
version: 5.2.1
+ '@types/file-saver':
+ specifier: ^2.0.7
+ version: 2.0.7
'@types/react':
specifier: ^18.2.20
version: 18.3.3
@@ -1772,6 +1781,9 @@ packages:
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+ '@types/file-saver@2.0.7':
+ resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
+
'@types/hast@2.3.10':
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
@@ -2900,6 +2912,9 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
+ file-saver@2.0.5:
+ resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
+
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -3181,6 +3196,9 @@ packages:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
+ immediate@3.0.6:
+ resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
+
immutable@4.3.7:
resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==}
@@ -3396,6 +3414,9 @@ packages:
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+ jszip@3.10.1:
+ resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
+
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -3410,6 +3431,9 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
+ lie@3.3.0:
+ resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
+
lilconfig@3.1.2:
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
engines: {node: '>=14'}
@@ -6912,6 +6936,8 @@ snapshots:
'@types/estree@1.0.6': {}
+ '@types/file-saver@2.0.7': {}
+
'@types/hast@2.3.10':
dependencies:
'@types/unist': 2.0.10
@@ -8396,6 +8422,8 @@ snapshots:
dependencies:
flat-cache: 4.0.1
+ file-saver@2.0.5: {}
+
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -8747,6 +8775,8 @@ snapshots:
ignore@5.3.1: {}
+ immediate@3.0.6: {}
+
immutable@4.3.7:
optional: true
@@ -8915,6 +8945,13 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
+ jszip@3.10.1:
+ dependencies:
+ lie: 3.3.0
+ pako: 1.0.11
+ readable-stream: 2.3.8
+ setimmediate: 1.0.5
+
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -8928,6 +8965,10 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
+ lie@3.3.0:
+ dependencies:
+ immediate: 3.0.6
+
lilconfig@3.1.2: {}
loader-utils@3.3.1: {}