diff --git a/app/components/header/DownloadButton.tsx b/app/components/header/DownloadButton.tsx
new file mode 100644
index 00000000..a2bc727a
--- /dev/null
+++ b/app/components/header/DownloadButton.tsx
@@ -0,0 +1,57 @@
+import ReactModal from 'react-modal';
+import { toast } from 'react-toastify';
+import { workbenchStore } from '~/lib/stores/workbench';
+import { downloadRepository } from '~/lib/replay/Deploy';
+
+ReactModal.setAppElement('#root');
+
+// Component for downloading an app's contents to disk.
+
+export function DownloadButton() {
+ const handleDownload = async () => {
+ const repositoryId = workbenchStore.repositoryId.get();
+ if (!repositoryId) {
+ toast.error('No repository ID found');
+ return;
+ }
+
+ try {
+ const repositoryContents = await downloadRepository(repositoryId);
+
+ // Convert base64 to blob
+ const byteCharacters = atob(repositoryContents);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+ const byteArray = new Uint8Array(byteNumbers);
+ const blob = new Blob([byteArray], { type: 'application/zip' });
+
+ // Create download link and trigger save dialog
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `repository-${repositoryId}.zip`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(url);
+
+ toast.success('Repository downloaded successfully');
+ } catch (error) {
+ console.error('Error downloading repository:', error);
+ toast.error('Failed to download repository');
+ }
+ };
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx
index 33e15962..12201392 100644
--- a/app/components/header/Header.tsx
+++ b/app/components/header/Header.tsx
@@ -8,6 +8,7 @@ import { Feedback } from './Feedback';
import { Suspense } from 'react';
import { ClientAuth } from '~/components/auth/ClientAuth';
import { DeployChatButton } from './DeployChatButton';
+import { DownloadButton } from './DownloadButton';
export function Header() {
const chatStarted = useStore(chatStore.started);
@@ -43,9 +44,14 @@ export function Header() {
)}
{chatStarted && (
-
- {() => }
-
+ <>
+
+ {() => }
+
+
+ {() => }
+
+ >
)}
diff --git a/app/lib/replay/Deploy.ts b/app/lib/replay/Deploy.ts
index c6fac4f2..ecc0e17f 100644
--- a/app/lib/replay/Deploy.ts
+++ b/app/lib/replay/Deploy.ts
@@ -65,3 +65,14 @@ export async function deployRepository(repositoryId: string, settings: DeploySet
return result;
}
+
+export async function downloadRepository(repositoryId: string): Promise {
+ const { repositoryContents } = (await sendCommandDedicatedClient({
+ method: 'Nut.getRepository',
+ params: {
+ repositoryId,
+ },
+ })) as { repositoryContents: string };
+
+ return repositoryContents;
+}
diff --git a/app/lib/replay/DevelopmentServer.ts b/app/lib/replay/DevelopmentServer.ts
index e074997e..65ccc63c 100644
--- a/app/lib/replay/DevelopmentServer.ts
+++ b/app/lib/replay/DevelopmentServer.ts
@@ -15,6 +15,6 @@ export async function updateDevelopmentServer(repositoryId: string | undefined)
console.log('UpdateDevelopmentServer', new Date().toISOString(), repositoryURL);
workbenchStore.showWorkbench.set(repositoryURL !== undefined);
- workbenchStore.repositoryId.set(repositoryURL);
+ workbenchStore.repositoryId.set(repositoryId);
workbenchStore.previewURL.set(repositoryURL);
}
diff --git a/app/utils/folderImport.ts b/app/utils/folderImport.ts
deleted file mode 100644
index 111280f5..00000000
--- a/app/utils/folderImport.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { Message } from '~/lib/persistence/message';
-import { generateId } from './fileUtils';
-import JSZip from 'jszip';
-
-interface FileArtifact {
- content: string;
- path: string;
-}
-
-export async function getFileRepositoryContents(files: File[]): Promise {
- const artifacts: FileArtifact[] = await Promise.all(
- files.map(async (file) => {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
-
- reader.onload = () => {
- const content = reader.result as string;
- const relativePath = file.webkitRelativePath.split('/').slice(1).join('/');
- resolve({
- content,
- path: relativePath,
- });
- };
- reader.onerror = reject;
- reader.readAsText(file);
- });
- }),
- );
-
- const zip = new JSZip();
-
- for (const { path, content } of artifacts) {
- zip.file(path, content);
- }
-
- return await zip.generateAsync({ type: 'base64' });
-}
-
-// TODO: Common up with createMessagesForRepository.
-export function createChatFromFolder(folderName: string, repositoryId: string): Message[] {
- const filesContent = `I've imported the contents of the "${folderName}" folder.`;
-
- const userMessage: Message = {
- role: 'user',
- id: generateId(),
- content: `Import the "${folderName}" folder`,
- type: 'text',
- };
-
- const filesMessage: Message = {
- role: 'assistant',
- content: filesContent,
- id: generateId(),
- repositoryId,
- type: 'text',
- };
-
- const messages = [userMessage, filesMessage];
-
- return messages;
-}