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",