diff --git a/package-lock.json b/package-lock.json
index 1481a75..ac922d7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@ai-sdk/google": "^1.2.10",
"@ai-sdk/openai": "^1.3.7",
"@biomejs/biome": "^1.9.4",
+ "@codesandbox/sandpack-client": "^2.19.8",
"@monaco-editor/react": "^4.7.0-rc.0",
"@openrouter/ai-sdk-provider": "^0.4.5",
"@radix-ui/react-accordion": "^1.2.4",
@@ -834,6 +835,54 @@
"node": ">=14.21.3"
}
},
+ "node_modules/@codesandbox/nodebox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz",
+ "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==",
+ "license": "SEE LICENSE IN ./LICENSE",
+ "dependencies": {
+ "outvariant": "^1.4.0",
+ "strict-event-emitter": "^0.4.3"
+ }
+ },
+ "node_modules/@codesandbox/sandpack-client": {
+ "version": "2.19.8",
+ "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz",
+ "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@codesandbox/nodebox": "0.1.8",
+ "buffer": "^6.0.3",
+ "dequal": "^2.0.2",
+ "mime-db": "^1.52.0",
+ "outvariant": "1.4.0",
+ "static-browser-server": "1.0.3"
+ }
+ },
+ "node_modules/@codesandbox/sandpack-client/node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/@drizzle-team/brocli": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
@@ -3581,6 +3630,12 @@
"@octokit/openapi-types": "^12.11.0"
}
},
+ "node_modules/@open-draft/deferred-promise": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
+ "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==",
+ "license": "MIT"
+ },
"node_modules/@openrouter/ai-sdk-provider": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-0.4.5.tgz",
@@ -16186,6 +16241,12 @@
"node": ">=8"
}
},
+ "node_modules/outvariant": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz",
+ "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==",
+ "license": "MIT"
+ },
"node_modules/own-keys": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
@@ -18425,6 +18486,18 @@
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
"license": "MIT"
},
+ "node_modules/static-browser-server": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz",
+ "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@open-draft/deferred-promise": "^2.1.0",
+ "dotenv": "^16.0.3",
+ "mime-db": "^1.52.0",
+ "outvariant": "^1.3.0"
+ }
+ },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -18451,6 +18524,12 @@
"node": ">=10.0.0"
}
},
+ "node_modules/strict-event-emitter": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz",
+ "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==",
+ "license": "MIT"
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
diff --git a/package.json b/package.json
index fae5c9b..ddde848 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
},
"scripts": {
"start": "electron-forge start",
- "package": "electron-forge package",
+ "package": "rm -rf out && rm -rf scaffold/node_modules && electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"ts": "yarn tsc -p tsconfig.app.json --noEmit",
@@ -62,6 +62,7 @@
"@ai-sdk/google": "^1.2.10",
"@ai-sdk/openai": "^1.3.7",
"@biomejs/biome": "^1.9.4",
+ "@codesandbox/sandpack-client": "^2.19.8",
"@monaco-editor/react": "^4.7.0-rc.0",
"@openrouter/ai-sdk-provider": "^0.4.5",
"@radix-ui/react-accordion": "^1.2.4",
@@ -111,4 +112,4 @@
"update-electron-app": "^3.1.1",
"uuid": "^11.1.0"
}
-}
+}
\ No newline at end of file
diff --git a/scaffold/src/index.css b/scaffold/src/globals.css
similarity index 100%
rename from scaffold/src/index.css
rename to scaffold/src/globals.css
diff --git a/scaffold/src/main.tsx b/scaffold/src/main.tsx
index 719464e..f8b6a38 100644
--- a/scaffold/src/main.tsx
+++ b/scaffold/src/main.tsx
@@ -1,5 +1,5 @@
-import { createRoot } from 'react-dom/client'
-import App from './App.tsx'
-import './index.css'
+import { createRoot } from "react-dom/client";
+import App from "./App.tsx";
+import "./globals.css";
createRoot(document.getElementById("root")!).render();
diff --git a/scaffold/tailwind.config.ts b/scaffold/tailwind.config.ts
index 8706086..b13b7f3 100644
--- a/scaffold/tailwind.config.ts
+++ b/scaffold/tailwind.config.ts
@@ -1,96 +1,96 @@
import type { Config } from "tailwindcss";
export default {
- darkMode: ["class"],
- content: [
- "./pages/**/*.{ts,tsx}",
- "./components/**/*.{ts,tsx}",
- "./app/**/*.{ts,tsx}",
- "./src/**/*.{ts,tsx}",
- ],
- prefix: "",
- theme: {
- container: {
- center: true,
- padding: '2rem',
- screens: {
- '2xl': '1400px'
- }
- },
- extend: {
- colors: {
- border: 'hsl(var(--border))',
- input: 'hsl(var(--input))',
- ring: 'hsl(var(--ring))',
- background: 'hsl(var(--background))',
- foreground: 'hsl(var(--foreground))',
- primary: {
- DEFAULT: 'hsl(var(--primary))',
- foreground: 'hsl(var(--primary-foreground))'
- },
- secondary: {
- DEFAULT: 'hsl(var(--secondary))',
- foreground: 'hsl(var(--secondary-foreground))'
- },
- destructive: {
- DEFAULT: 'hsl(var(--destructive))',
- foreground: 'hsl(var(--destructive-foreground))'
- },
- muted: {
- DEFAULT: 'hsl(var(--muted))',
- foreground: 'hsl(var(--muted-foreground))'
- },
- accent: {
- DEFAULT: 'hsl(var(--accent))',
- foreground: 'hsl(var(--accent-foreground))'
- },
- popover: {
- DEFAULT: 'hsl(var(--popover))',
- foreground: 'hsl(var(--popover-foreground))'
- },
- card: {
- DEFAULT: 'hsl(var(--card))',
- foreground: 'hsl(var(--card-foreground))'
- },
- sidebar: {
- DEFAULT: 'hsl(var(--sidebar-background))',
- foreground: 'hsl(var(--sidebar-foreground))',
- primary: 'hsl(var(--sidebar-primary))',
- 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
- accent: 'hsl(var(--sidebar-accent))',
- 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
- border: 'hsl(var(--sidebar-border))',
- ring: 'hsl(var(--sidebar-ring))'
- }
- },
- borderRadius: {
- lg: 'var(--radius)',
- md: 'calc(var(--radius) - 2px)',
- sm: 'calc(var(--radius) - 4px)'
- },
- keyframes: {
- 'accordion-down': {
- from: {
- height: '0'
- },
- to: {
- height: 'var(--radix-accordion-content-height)'
- }
- },
- 'accordion-up': {
- from: {
- height: 'var(--radix-accordion-content-height)'
- },
- to: {
- height: '0'
- }
- }
- },
- animation: {
- 'accordion-down': 'accordion-down 0.2s ease-out',
- 'accordion-up': 'accordion-up 0.2s ease-out'
- }
- }
- },
- plugins: [require("tailwindcss-animate")],
+ darkMode: ["class"],
+ content: [
+ "./pages/**/*.{ts,tsx}",
+ "./components/**/*.{ts,tsx}",
+ "./app/**/*.{ts,tsx}",
+ "./src/**/*.{ts,tsx}",
+ ],
+ prefix: "",
+ theme: {
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px",
+ },
+ },
+ extend: {
+ colors: {
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ sidebar: {
+ DEFAULT: "hsl(var(--sidebar-background))",
+ foreground: "hsl(var(--sidebar-foreground))",
+ primary: "hsl(var(--sidebar-primary))",
+ "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+ accent: "hsl(var(--sidebar-accent))",
+ "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+ border: "hsl(var(--sidebar-border))",
+ ring: "hsl(var(--sidebar-ring))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ keyframes: {
+ "accordion-down": {
+ from: {
+ height: "0",
+ },
+ to: {
+ height: "var(--radix-accordion-content-height)",
+ },
+ },
+ "accordion-up": {
+ from: {
+ height: "var(--radix-accordion-content-height)",
+ },
+ to: {
+ height: "0",
+ },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
+ },
+ },
+ },
+ plugins: [require("tailwindcss-animate")],
} satisfies Config;
diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx
index a945e66..0f92160 100644
--- a/src/components/chat/ChatInput.tsx
+++ b/src/components/chat/ChatInput.tsx
@@ -30,7 +30,6 @@ export function ChatInput({ chatId, onSubmit }: ChatInputProps) {
if (textarea) {
textarea.style.height = "0px";
const scrollHeight = textarea.scrollHeight;
- console.log("scrollHeight", scrollHeight);
textarea.style.height = `${scrollHeight + 4}px`;
}
};
diff --git a/src/components/chat/ChatMessage.tsx b/src/components/chat/ChatMessage.tsx
index 6621b16..0512ee6 100644
--- a/src/components/chat/ChatMessage.tsx
+++ b/src/components/chat/ChatMessage.tsx
@@ -8,73 +8,67 @@ interface ChatMessageProps {
message: Message;
}
-const ChatMessage = memo(
- ({ message }: ChatMessageProps) => {
- return (
+const ChatMessage = ({ message }: ChatMessageProps) => {
+ const { isStreaming } = useStreamChat();
+ return (
+
-
- {message.role === "assistant" && !message.content ? (
-
-
-
-
-
- ) : (
-
-
-
- )}
-
+ {message.role === "assistant" && !message.content && isStreaming ? (
+
+
+
+
+
+ ) : (
+
+
+
+ )}
- );
- },
- (prevProps, nextProps) => {
- return prevProps.message.content === nextProps.message.content;
- }
-);
-
-ChatMessage.displayName = "ChatMessage";
+
+ );
+};
export default ChatMessage;
diff --git a/src/components/chat/DyadAddDependency.tsx b/src/components/chat/DyadAddDependency.tsx
index b37d24a..08a41bd 100644
--- a/src/components/chat/DyadAddDependency.tsx
+++ b/src/components/chat/DyadAddDependency.tsx
@@ -28,7 +28,6 @@ export const DyadAddDependency: React.FC = ({
}) => {
// Extract package attribute from the node if available
const packages = node?.properties?.packages?.split(" ") || "";
- console.log("packages", packages);
const [isInstalling, setIsInstalling] = useState(false);
const [error, setError] = useState(null);
const selectedChatId = useAtomValue(selectedChatIdAtom);
diff --git a/src/components/preview_panel/PreviewIframe.tsx b/src/components/preview_panel/PreviewIframe.tsx
index 2c75125..812e203 100644
--- a/src/components/preview_panel/PreviewIframe.tsx
+++ b/src/components/preview_panel/PreviewIframe.tsx
@@ -24,6 +24,15 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
+import { useSettings } from "@/hooks/useSettings";
+import {
+ loadSandpackClient,
+ type SandboxSetup,
+ type ClientOptions,
+ SandpackClient,
+} from "@codesandbox/sandpack-client";
+import { showError } from "@/lib/toast";
+import { SandboxConfig } from "@/ipc/ipc_types";
interface ErrorBannerProps {
error: string | null;
@@ -107,7 +116,6 @@ export const PreviewIframe = ({
// Effect to parse routes from the router file
useEffect(() => {
if (routerContent) {
- console.log("routerContent", routerContent);
try {
const routes: Array<{ path: string; label: string }> = [];
@@ -148,6 +156,7 @@ export const PreviewIframe = ({
const [navigationHistory, setNavigationHistory] = useState([]);
const [currentHistoryPosition, setCurrentHistoryPosition] = useState(0);
const iframeRef = useRef(null);
+ const { settings } = useSettings();
// Add message listener for iframe errors and navigation events
useEffect(() => {
@@ -392,7 +401,9 @@ export const PreviewIframe = ({
}}
/>
- {!appUrl ? (
+ {settings?.runtimeMode === "web-sandbox" ? (
+
+ ) : !appUrl ? (
@@ -412,3 +423,120 @@ export const PreviewIframe = ({
);
};
+
+const parseTailwindConfig = (config: string) => {
+ const themeRegex = /theme\s*:\s*(\{[\s\S]*?\})(?=\s*,\s*plugins)/;
+ const match = config.match(themeRegex);
+ if (!match) return "{};";
+ return `{theme: ${match[1]}};`;
+};
+
+const SandpackIframe = ({ reloadKey }: { reloadKey: number }) => {
+ const selectedAppId = useAtomValue(selectedAppIdAtom);
+ const { app } = useLoadApp(selectedAppId);
+ const keyRef = useRef(null);
+ const isFirstRender = useRef(true);
+ const sandpackClientRef = useRef(null);
+
+ async function loadSandpack() {
+ if (keyRef.current === reloadKey) return;
+ keyRef.current = reloadKey;
+
+ if (!iframeRef.current || !app || !selectedAppId) return;
+ const sandboxConfig = await IpcClient.getInstance().getAppSandboxConfig(
+ selectedAppId
+ );
+
+ const sandpackConfig: SandboxSetup = mapSandpackConfig(sandboxConfig);
+
+ const options: ClientOptions = {
+ // bundlerURL: "https://sandpack.dyad.sh/",
+ showOpenInCodeSandbox: false,
+ showLoadingScreen: true,
+ showErrorScreen: true,
+ externalResources: [
+ // "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4",
+ "https://cdn.tailwindcss.com",
+ ],
+ };
+
+ let client: SandpackClient | undefined;
+ try {
+ client = await loadSandpackClient(
+ iframeRef.current,
+ sandpackConfig,
+ options
+ );
+ sandpackClientRef.current = client;
+ return client;
+ } catch (error) {
+ showError(error);
+ }
+ }
+
+ useEffect(() => {
+ async function updateSandpack() {
+ if (sandpackClientRef.current && selectedAppId) {
+ const sandboxConfig = await IpcClient.getInstance().getAppSandboxConfig(
+ selectedAppId
+ );
+ sandpackClientRef.current.updateSandbox(
+ mapSandpackConfig(sandboxConfig)
+ );
+ }
+ }
+ updateSandpack();
+ }, [app]);
+
+ useEffect(() => {
+ if (isFirstRender.current) {
+ isFirstRender.current = false;
+ return;
+ }
+
+ if (!iframeRef.current || !app || !selectedAppId) return () => {};
+
+ const clientPromise = loadSandpack();
+ return () => {
+ clientPromise.then((client) => {
+ client?.destroy();
+ sandpackClientRef.current = null;
+ });
+ };
+ }, [reloadKey]);
+ const iframeRef = useRef(null);
+
+ return (
+
+ );
+};
+
+const mapSandpackConfig = (sandboxConfig: SandboxConfig): SandboxSetup => {
+ return {
+ files: Object.fromEntries(
+ Object.entries(sandboxConfig.files).map(([key, value]) => [
+ key,
+ {
+ code: value.replace(
+ "import './globals.css'",
+ `
+const injectedStyle = document.createElement("style");
+injectedStyle.textContent = \`${sandboxConfig.files["src/globals.css"]}\`;
+injectedStyle.type = "text/tailwindcss";
+document.head.appendChild(injectedStyle);
+
+window.tailwind.config = ${parseTailwindConfig(
+ sandboxConfig.files["tailwind.config.ts"]
+ )}
+`
+ ),
+ },
+ ])
+ ),
+ dependencies: sandboxConfig.dependencies,
+ entry: sandboxConfig.entry,
+ };
+};
diff --git a/src/hooks/useLoadVersions.ts b/src/hooks/useLoadVersions.ts
index 7d30548..3fae2b8 100644
--- a/src/hooks/useLoadVersions.ts
+++ b/src/hooks/useLoadVersions.ts
@@ -1,4 +1,4 @@
-import { useState, useEffect } from "react";
+import { useState, useEffect, useCallback } from "react";
import { useAtom } from "jotai";
import { versionsListAtom } from "@/atoms/appAtoms";
import { IpcClient } from "@/ipc/ipc_client";
@@ -34,7 +34,7 @@ export function useLoadVersions(appId: number | null) {
loadVersions();
}, [appId, setVersions]);
- const refreshVersions = async () => {
+ const refreshVersions = useCallback(async () => {
if (appId === null) {
return;
}
@@ -48,7 +48,7 @@ export function useLoadVersions(appId: number | null) {
console.error("Error refreshing versions:", error);
setError(error instanceof Error ? error : new Error(String(error)));
}
- };
+ }, [appId, setVersions, setError]);
return { versions, loading, error, refreshVersions };
}
diff --git a/src/ipc/handlers/app_handlers.ts b/src/ipc/handlers/app_handlers.ts
index f19edeb..e855393 100644
--- a/src/ipc/handlers/app_handlers.ts
+++ b/src/ipc/handlers/app_handlers.ts
@@ -2,7 +2,12 @@ import { ipcMain } from "electron";
import { db, getDatabasePath } from "../../db";
import { apps, chats } from "../../db/schema";
import { desc, eq } from "drizzle-orm";
-import type { App, CreateAppParams, Version } from "../ipc_types";
+import type {
+ App,
+ CreateAppParams,
+ SandboxConfig,
+ Version,
+} from "../ipc_types";
import fs from "node:fs";
import path from "node:path";
import { getDyadAppPath, getUserDataPath } from "../../paths/paths";
@@ -25,6 +30,7 @@ import {
} from "../utils/process_manager";
import { ALLOWED_ENV_VARS } from "../../constants/models";
import { getEnvVar } from "../utils/read_env";
+import { readSettings } from "../../main/settings";
async function executeApp({
appPath,
@@ -34,7 +40,26 @@ async function executeApp({
appPath: string;
appId: number;
event: Electron.IpcMainInvokeEvent;
-}): Promise<{ processId: number }> {
+}): Promise {
+ const settings = readSettings();
+ if (settings.runtimeMode === "web-sandbox") {
+ return;
+ }
+ if (settings.runtimeMode === "local-node") {
+ await executeAppLocalNode({ appPath, appId, event });
+ return;
+ }
+ throw new Error("Invalid runtime mode");
+}
+async function executeAppLocalNode({
+ appPath,
+ appId,
+ event,
+}: {
+ appPath: string;
+ appId: number;
+ event: Electron.IpcMainInvokeEvent;
+}): Promise {
const process = spawn("npm install && npm run dev", [], {
cwd: appPath,
shell: true,
@@ -99,11 +124,43 @@ async function executeApp({
// Note: We don't throw here as the error is asynchronous. The caller got a success response already.
// Consider adding ipcRenderer event emission to notify UI of the error.
});
-
- return { processId: currentProcessId };
}
export function registerAppHandlers() {
+ ipcMain.handle(
+ "get-app-sandbox-config",
+ async (_, { appId }: { appId: number }): Promise => {
+ const app = await db.query.apps.findFirst({
+ where: eq(apps.id, appId),
+ });
+ if (!app) {
+ throw new Error("App not found");
+ }
+ const appPath = getDyadAppPath(app.path);
+ const files = getFilesRecursively(appPath, appPath);
+
+ const filesMap = await Promise.all(
+ files.map(async (file) => {
+ const content = await fs.promises.readFile(
+ path.join(appPath, file),
+ "utf-8"
+ );
+ return { [file]: content };
+ })
+ );
+
+ // Get dependencies from package.json
+ const packageJsonPath = path.join(appPath, "package.json");
+ const packageJson = await fs.promises.readFile(packageJsonPath, "utf-8");
+ const dependencies = JSON.parse(packageJson).dependencies;
+
+ return {
+ files: filesMap.reduce((acc, file) => ({ ...acc, ...file }), {}),
+ dependencies,
+ entry: "src/main.tsx",
+ };
+ }
+ );
ipcMain.handle("create-app", async (_, params: CreateAppParams) => {
const appPath = params.name;
const fullAppPath = getDyadAppPath(appPath);
@@ -272,7 +329,6 @@ export function registerAppHandlers() {
console.debug(`Starting app ${appId} in path ${app.path}`);
const appPath = getDyadAppPath(app.path);
- console.log("appPath-CWD", appPath);
try {
const currentProcessId = await executeApp({ appPath, appId, event });
@@ -379,9 +435,9 @@ export function registerAppHandlers() {
const appPath = getDyadAppPath(app.path);
console.debug(`Starting app ${appId} in path ${app.path}`);
- const currentProcessId = await executeApp({ appPath, appId, event });
+ await executeApp({ appPath, appId, event });
- return { success: true, processId: currentProcessId };
+ return { success: true };
} catch (error) {
console.error(`Error restarting app ${appId}:`, error);
throw error;
diff --git a/src/ipc/ipc_client.ts b/src/ipc/ipc_client.ts
index 5b1ddb9..48221ea 100644
--- a/src/ipc/ipc_client.ts
+++ b/src/ipc/ipc_client.ts
@@ -14,6 +14,7 @@ import type {
CreateAppParams,
CreateAppResult,
ListAppsResponse,
+ SandboxConfig,
Version,
} from "./ipc_types";
import { showError } from "@/lib/toast";
@@ -127,6 +128,18 @@ export class IpcClient {
}
}
+ public async getAppSandboxConfig(appId: number): Promise {
+ try {
+ const data = await this.ipcRenderer.invoke("get-app-sandbox-config", {
+ appId,
+ });
+ return data;
+ } catch (error) {
+ showError(error);
+ throw error;
+ }
+ }
+
public async getApp(appId: number): Promise {
try {
const data = await this.ipcRenderer.invoke("get-app", appId);
diff --git a/src/ipc/ipc_types.ts b/src/ipc/ipc_types.ts
index f18ca0d..050f1cd 100644
--- a/src/ipc/ipc_types.ts
+++ b/src/ipc/ipc_types.ts
@@ -57,3 +57,9 @@ export interface Version {
message: string;
timestamp: number;
}
+
+export interface SandboxConfig {
+ files: Record;
+ dependencies: Record;
+ entry: string;
+}
diff --git a/src/preload.ts b/src/preload.ts
index 284b18c..1670ed2 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -15,6 +15,7 @@ const validInvokeChannels = [
"get-chats",
"list-apps",
"get-app",
+ "get-app-sandbox-config",
"edit-app-file",
"read-app-file",
"run-app",