Commit
·
21dbd42
1
Parent(s):
4f7a06f
added download code button
Browse files- app/components/workbench/Workbench.client.tsx +20 -9
- app/lib/stores/workbench.ts +32 -0
- package.json +3 -0
- pnpm-lock.yaml +41 -0
app/components/workbench/Workbench.client.tsx
CHANGED
|
@@ -122,15 +122,26 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
|
|
| 122 |
<Slider selected={selectedView} options={sliderOptions} setSelected={setSelectedView} />
|
| 123 |
<div className="ml-auto" />
|
| 124 |
{selectedView === 'code' && (
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
)}
|
| 135 |
<IconButton
|
| 136 |
icon="i-ph:x-circle"
|
|
|
|
| 122 |
<Slider selected={selectedView} options={sliderOptions} setSelected={setSelectedView} />
|
| 123 |
<div className="ml-auto" />
|
| 124 |
{selectedView === 'code' && (
|
| 125 |
+
<>
|
| 126 |
+
<PanelHeaderButton
|
| 127 |
+
className="mr-1 text-sm"
|
| 128 |
+
onClick={() => {
|
| 129 |
+
workbenchStore.downloadZip();
|
| 130 |
+
}}
|
| 131 |
+
>
|
| 132 |
+
<div className="i-ph:code" />
|
| 133 |
+
Download Code
|
| 134 |
+
</PanelHeaderButton>
|
| 135 |
+
<PanelHeaderButton
|
| 136 |
+
className="mr-1 text-sm"
|
| 137 |
+
onClick={() => {
|
| 138 |
+
workbenchStore.toggleTerminal(!workbenchStore.showTerminal.get());
|
| 139 |
+
}}
|
| 140 |
+
>
|
| 141 |
+
<div className="i-ph:terminal" />
|
| 142 |
+
Toggle Terminal
|
| 143 |
+
</PanelHeaderButton>
|
| 144 |
+
</>
|
| 145 |
)}
|
| 146 |
<IconButton
|
| 147 |
icon="i-ph:x-circle"
|
app/lib/stores/workbench.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { EditorStore } from './editor';
|
|
| 9 |
import { FilesStore, type FileMap } from './files';
|
| 10 |
import { PreviewsStore } from './previews';
|
| 11 |
import { TerminalStore } from './terminal';
|
|
|
|
|
|
|
| 12 |
|
| 13 |
export interface ArtifactState {
|
| 14 |
id: string;
|
|
@@ -271,6 +273,36 @@ export class WorkbenchStore {
|
|
| 271 |
const artifacts = this.artifacts.get();
|
| 272 |
return artifacts[id];
|
| 273 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
}
|
| 275 |
|
| 276 |
export const workbenchStore = new WorkbenchStore();
|
|
|
|
| 9 |
import { FilesStore, type FileMap } from './files';
|
| 10 |
import { PreviewsStore } from './previews';
|
| 11 |
import { TerminalStore } from './terminal';
|
| 12 |
+
import JSZip from 'jszip';
|
| 13 |
+
import { saveAs } from 'file-saver';
|
| 14 |
|
| 15 |
export interface ArtifactState {
|
| 16 |
id: string;
|
|
|
|
| 273 |
const artifacts = this.artifacts.get();
|
| 274 |
return artifacts[id];
|
| 275 |
}
|
| 276 |
+
|
| 277 |
+
async downloadZip() {
|
| 278 |
+
const zip = new JSZip();
|
| 279 |
+
const files = this.files.get();
|
| 280 |
+
|
| 281 |
+
for (const [filePath, dirent] of Object.entries(files)) {
|
| 282 |
+
if (dirent?.type === 'file' && !dirent.isBinary) {
|
| 283 |
+
// Remove '/home/project/' from the beginning of the path
|
| 284 |
+
const relativePath = filePath.replace(/^\/home\/project\//, '');
|
| 285 |
+
|
| 286 |
+
// Split the path into segments
|
| 287 |
+
const pathSegments = relativePath.split('/');
|
| 288 |
+
|
| 289 |
+
// If there's more than one segment, we need to create folders
|
| 290 |
+
if (pathSegments.length > 1) {
|
| 291 |
+
let currentFolder = zip;
|
| 292 |
+
for (let i = 0; i < pathSegments.length - 1; i++) {
|
| 293 |
+
currentFolder = currentFolder.folder(pathSegments[i])!;
|
| 294 |
+
}
|
| 295 |
+
currentFolder.file(pathSegments[pathSegments.length - 1], dirent.content);
|
| 296 |
+
} else {
|
| 297 |
+
// If there's only one segment, it's a file in the root
|
| 298 |
+
zip.file(relativePath, dirent.content);
|
| 299 |
+
}
|
| 300 |
+
}
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
const content = await zip.generateAsync({ type: 'blob' });
|
| 304 |
+
saveAs(content, 'project.zip');
|
| 305 |
+
}
|
| 306 |
}
|
| 307 |
|
| 308 |
export const workbenchStore = new WorkbenchStore();
|
package.json
CHANGED
|
@@ -59,10 +59,12 @@
|
|
| 59 |
"ai": "^3.4.9",
|
| 60 |
"date-fns": "^3.6.0",
|
| 61 |
"diff": "^5.2.0",
|
|
|
|
| 62 |
"framer-motion": "^11.2.12",
|
| 63 |
"isbot": "^4.1.0",
|
| 64 |
"istextorbinary": "^9.5.0",
|
| 65 |
"jose": "^5.6.3",
|
|
|
|
| 66 |
"nanostores": "^0.10.3",
|
| 67 |
"ollama-ai-provider": "^0.15.2",
|
| 68 |
"react": "^18.2.0",
|
|
@@ -84,6 +86,7 @@
|
|
| 84 |
"@cloudflare/workers-types": "^4.20240620.0",
|
| 85 |
"@remix-run/dev": "^2.10.0",
|
| 86 |
"@types/diff": "^5.2.1",
|
|
|
|
| 87 |
"@types/react": "^18.2.20",
|
| 88 |
"@types/react-dom": "^18.2.7",
|
| 89 |
"fast-glob": "^3.3.2",
|
|
|
|
| 59 |
"ai": "^3.4.9",
|
| 60 |
"date-fns": "^3.6.0",
|
| 61 |
"diff": "^5.2.0",
|
| 62 |
+
"file-saver": "^2.0.5",
|
| 63 |
"framer-motion": "^11.2.12",
|
| 64 |
"isbot": "^4.1.0",
|
| 65 |
"istextorbinary": "^9.5.0",
|
| 66 |
"jose": "^5.6.3",
|
| 67 |
+
"jszip": "^3.10.1",
|
| 68 |
"nanostores": "^0.10.3",
|
| 69 |
"ollama-ai-provider": "^0.15.2",
|
| 70 |
"react": "^18.2.0",
|
|
|
|
| 86 |
"@cloudflare/workers-types": "^4.20240620.0",
|
| 87 |
"@remix-run/dev": "^2.10.0",
|
| 88 |
"@types/diff": "^5.2.1",
|
| 89 |
+
"@types/file-saver": "^2.0.7",
|
| 90 |
"@types/react": "^18.2.20",
|
| 91 |
"@types/react-dom": "^18.2.7",
|
| 92 |
"fast-glob": "^3.3.2",
|
pnpm-lock.yaml
CHANGED
|
@@ -119,6 +119,9 @@ importers:
|
|
| 119 |
diff:
|
| 120 |
specifier: ^5.2.0
|
| 121 |
version: 5.2.0
|
|
|
|
|
|
|
|
|
|
| 122 |
framer-motion:
|
| 123 |
specifier: ^11.2.12
|
| 124 |
version: 11.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
@@ -131,6 +134,9 @@ importers:
|
|
| 131 |
jose:
|
| 132 |
specifier: ^5.6.3
|
| 133 |
version: 5.6.3
|
|
|
|
|
|
|
|
|
|
| 134 |
nanostores:
|
| 135 |
specifier: ^0.10.3
|
| 136 |
version: 0.10.3
|
|
@@ -189,6 +195,9 @@ importers:
|
|
| 189 |
'@types/diff':
|
| 190 |
specifier: ^5.2.1
|
| 191 |
version: 5.2.1
|
|
|
|
|
|
|
|
|
|
| 192 |
'@types/react':
|
| 193 |
specifier: ^18.2.20
|
| 194 |
version: 18.3.3
|
|
@@ -1772,6 +1781,9 @@ packages:
|
|
| 1772 |
'@types/estree@1.0.6':
|
| 1773 |
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
| 1774 |
|
|
|
|
|
|
|
|
|
|
| 1775 |
'@types/hast@2.3.10':
|
| 1776 |
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
|
| 1777 |
|
|
@@ -2900,6 +2912,9 @@ packages:
|
|
| 2900 |
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
| 2901 |
engines: {node: '>=16.0.0'}
|
| 2902 |
|
|
|
|
|
|
|
|
|
|
| 2903 |
fill-range@7.1.1:
|
| 2904 |
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
| 2905 |
engines: {node: '>=8'}
|
|
@@ -3181,6 +3196,9 @@ packages:
|
|
| 3181 |
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
|
| 3182 |
engines: {node: '>= 4'}
|
| 3183 |
|
|
|
|
|
|
|
|
|
|
| 3184 |
immutable@4.3.7:
|
| 3185 |
resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==}
|
| 3186 |
|
|
@@ -3396,6 +3414,9 @@ packages:
|
|
| 3396 |
jsonfile@6.1.0:
|
| 3397 |
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
| 3398 |
|
|
|
|
|
|
|
|
|
|
| 3399 |
keyv@4.5.4:
|
| 3400 |
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
| 3401 |
|
|
@@ -3410,6 +3431,9 @@ packages:
|
|
| 3410 |
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
| 3411 |
engines: {node: '>= 0.8.0'}
|
| 3412 |
|
|
|
|
|
|
|
|
|
|
| 3413 |
lilconfig@3.1.2:
|
| 3414 |
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
|
| 3415 |
engines: {node: '>=14'}
|
|
@@ -6912,6 +6936,8 @@ snapshots:
|
|
| 6912 |
|
| 6913 |
'@types/estree@1.0.6': {}
|
| 6914 |
|
|
|
|
|
|
|
| 6915 |
'@types/hast@2.3.10':
|
| 6916 |
dependencies:
|
| 6917 |
'@types/unist': 2.0.10
|
|
@@ -8396,6 +8422,8 @@ snapshots:
|
|
| 8396 |
dependencies:
|
| 8397 |
flat-cache: 4.0.1
|
| 8398 |
|
|
|
|
|
|
|
| 8399 |
fill-range@7.1.1:
|
| 8400 |
dependencies:
|
| 8401 |
to-regex-range: 5.0.1
|
|
@@ -8747,6 +8775,8 @@ snapshots:
|
|
| 8747 |
|
| 8748 |
ignore@5.3.1: {}
|
| 8749 |
|
|
|
|
|
|
|
| 8750 |
immutable@4.3.7:
|
| 8751 |
optional: true
|
| 8752 |
|
|
@@ -8915,6 +8945,13 @@ snapshots:
|
|
| 8915 |
optionalDependencies:
|
| 8916 |
graceful-fs: 4.2.11
|
| 8917 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8918 |
keyv@4.5.4:
|
| 8919 |
dependencies:
|
| 8920 |
json-buffer: 3.0.1
|
|
@@ -8928,6 +8965,10 @@ snapshots:
|
|
| 8928 |
prelude-ls: 1.2.1
|
| 8929 |
type-check: 0.4.0
|
| 8930 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8931 |
lilconfig@3.1.2: {}
|
| 8932 |
|
| 8933 |
loader-utils@3.3.1: {}
|
|
|
|
| 119 |
diff:
|
| 120 |
specifier: ^5.2.0
|
| 121 |
version: 5.2.0
|
| 122 |
+
file-saver:
|
| 123 |
+
specifier: ^2.0.5
|
| 124 |
+
version: 2.0.5
|
| 125 |
framer-motion:
|
| 126 |
specifier: ^11.2.12
|
| 127 |
version: 11.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
|
|
| 134 |
jose:
|
| 135 |
specifier: ^5.6.3
|
| 136 |
version: 5.6.3
|
| 137 |
+
jszip:
|
| 138 |
+
specifier: ^3.10.1
|
| 139 |
+
version: 3.10.1
|
| 140 |
nanostores:
|
| 141 |
specifier: ^0.10.3
|
| 142 |
version: 0.10.3
|
|
|
|
| 195 |
'@types/diff':
|
| 196 |
specifier: ^5.2.1
|
| 197 |
version: 5.2.1
|
| 198 |
+
'@types/file-saver':
|
| 199 |
+
specifier: ^2.0.7
|
| 200 |
+
version: 2.0.7
|
| 201 |
'@types/react':
|
| 202 |
specifier: ^18.2.20
|
| 203 |
version: 18.3.3
|
|
|
|
| 1781 |
'@types/estree@1.0.6':
|
| 1782 |
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
| 1783 |
|
| 1784 |
+
'@types/file-saver@2.0.7':
|
| 1785 |
+
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
|
| 1786 |
+
|
| 1787 |
'@types/hast@2.3.10':
|
| 1788 |
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
|
| 1789 |
|
|
|
|
| 2912 |
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
| 2913 |
engines: {node: '>=16.0.0'}
|
| 2914 |
|
| 2915 |
+
file-saver@2.0.5:
|
| 2916 |
+
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
|
| 2917 |
+
|
| 2918 |
fill-range@7.1.1:
|
| 2919 |
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
| 2920 |
engines: {node: '>=8'}
|
|
|
|
| 3196 |
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
|
| 3197 |
engines: {node: '>= 4'}
|
| 3198 |
|
| 3199 |
+
immediate@3.0.6:
|
| 3200 |
+
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
| 3201 |
+
|
| 3202 |
immutable@4.3.7:
|
| 3203 |
resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==}
|
| 3204 |
|
|
|
|
| 3414 |
jsonfile@6.1.0:
|
| 3415 |
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
| 3416 |
|
| 3417 |
+
jszip@3.10.1:
|
| 3418 |
+
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
|
| 3419 |
+
|
| 3420 |
keyv@4.5.4:
|
| 3421 |
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
| 3422 |
|
|
|
|
| 3431 |
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
| 3432 |
engines: {node: '>= 0.8.0'}
|
| 3433 |
|
| 3434 |
+
lie@3.3.0:
|
| 3435 |
+
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
|
| 3436 |
+
|
| 3437 |
lilconfig@3.1.2:
|
| 3438 |
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
|
| 3439 |
engines: {node: '>=14'}
|
|
|
|
| 6936 |
|
| 6937 |
'@types/estree@1.0.6': {}
|
| 6938 |
|
| 6939 |
+
'@types/file-saver@2.0.7': {}
|
| 6940 |
+
|
| 6941 |
'@types/hast@2.3.10':
|
| 6942 |
dependencies:
|
| 6943 |
'@types/unist': 2.0.10
|
|
|
|
| 8422 |
dependencies:
|
| 8423 |
flat-cache: 4.0.1
|
| 8424 |
|
| 8425 |
+
file-saver@2.0.5: {}
|
| 8426 |
+
|
| 8427 |
fill-range@7.1.1:
|
| 8428 |
dependencies:
|
| 8429 |
to-regex-range: 5.0.1
|
|
|
|
| 8775 |
|
| 8776 |
ignore@5.3.1: {}
|
| 8777 |
|
| 8778 |
+
immediate@3.0.6: {}
|
| 8779 |
+
|
| 8780 |
immutable@4.3.7:
|
| 8781 |
optional: true
|
| 8782 |
|
|
|
|
| 8945 |
optionalDependencies:
|
| 8946 |
graceful-fs: 4.2.11
|
| 8947 |
|
| 8948 |
+
jszip@3.10.1:
|
| 8949 |
+
dependencies:
|
| 8950 |
+
lie: 3.3.0
|
| 8951 |
+
pako: 1.0.11
|
| 8952 |
+
readable-stream: 2.3.8
|
| 8953 |
+
setimmediate: 1.0.5
|
| 8954 |
+
|
| 8955 |
keyv@4.5.4:
|
| 8956 |
dependencies:
|
| 8957 |
json-buffer: 3.0.1
|
|
|
|
| 8965 |
prelude-ls: 1.2.1
|
| 8966 |
type-check: 0.4.0
|
| 8967 |
|
| 8968 |
+
lie@3.3.0:
|
| 8969 |
+
dependencies:
|
| 8970 |
+
immediate: 3.0.6
|
| 8971 |
+
|
| 8972 |
lilconfig@3.1.2: {}
|
| 8973 |
|
| 8974 |
loader-utils@3.3.1: {}
|