import { useState } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { IpcClient } from "@/ipc/ipc_client"; import { useMutation } from "@tanstack/react-query"; import { showError, showSuccess } from "@/lib/toast"; import { Folder, X, Loader2, Info } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Label } from "@radix-ui/react-label"; import { useNavigate } from "@tanstack/react-router"; import { useStreamChat } from "@/hooks/useStreamChat"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { useSetAtom } from "jotai"; import { useLoadApps } from "@/hooks/useLoadApps"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "./ui/accordion"; interface ImportAppDialogProps { isOpen: boolean; onClose: () => void; } export function ImportAppDialog({ isOpen, onClose }: ImportAppDialogProps) { const [selectedPath, setSelectedPath] = useState(null); const [hasAiRules, setHasAiRules] = useState(null); const [customAppName, setCustomAppName] = useState(""); const [nameExists, setNameExists] = useState(false); const [isCheckingName, setIsCheckingName] = useState(false); const [installCommand, setInstallCommand] = useState("pnpm install"); const [startCommand, setStartCommand] = useState("pnpm dev"); const navigate = useNavigate(); const { streamMessage } = useStreamChat({ hasChatId: false }); const { refreshApps } = useLoadApps(); const setSelectedAppId = useSetAtom(selectedAppIdAtom); const checkAppName = async (name: string): Promise => { setIsCheckingName(true); try { const result = await IpcClient.getInstance().checkAppName({ appName: name, }); setNameExists(result.exists); } catch (error: unknown) { showError("Failed to check app name: " + (error as any).toString()); } finally { setIsCheckingName(false); } }; const selectFolderMutation = useMutation({ mutationFn: async () => { const result = await IpcClient.getInstance().selectAppFolder(); if (!result.path || !result.name) { throw new Error("No folder selected"); } const aiRulesCheck = await IpcClient.getInstance().checkAiRules({ path: result.path, }); setHasAiRules(aiRulesCheck.exists); setSelectedPath(result.path); // Use the folder name from the IPC response setCustomAppName(result.name); // Check if the app name already exists await checkAppName(result.name); return result; }, onError: (error: Error) => { showError(error.message); }, }); const importAppMutation = useMutation({ mutationFn: async () => { if (!selectedPath) throw new Error("No folder selected"); return IpcClient.getInstance().importApp({ path: selectedPath, appName: customAppName, installCommand: installCommand || undefined, startCommand: startCommand || undefined, }); }, onSuccess: async (result) => { showSuccess( !hasAiRules ? "App imported successfully. Dyad will automatically generate an AI_RULES.md now." : "App imported successfully", ); onClose(); navigate({ to: "/chat", search: { id: result.chatId } }); if (!hasAiRules) { streamMessage({ prompt: "Generate an AI_RULES.md file for this app. Describe the tech stack in 5-10 bullet points and describe clear rules about what libraries to use for what.", chatId: result.chatId, }); } setSelectedAppId(result.appId); await refreshApps(); }, onError: (error: Error) => { showError(error.message); }, }); const handleSelectFolder = () => { selectFolderMutation.mutate(); }; const handleImport = () => { importAppMutation.mutate(); }; const handleClear = () => { setSelectedPath(null); setHasAiRules(null); setCustomAppName(""); setNameExists(false); setInstallCommand("pnpm install"); setStartCommand("pnpm dev"); }; const handleAppNameChange = async ( e: React.ChangeEvent, ) => { const newName = e.target.value; setCustomAppName(newName); if (newName.trim()) { await checkAppName(newName); } }; const hasInstallCommand = installCommand.trim().length > 0; const hasStartCommand = startCommand.trim().length > 0; const commandsValid = hasInstallCommand === hasStartCommand; return ( Import App Select an existing app folder to import into Dyad. App import is an experimental feature. If you encounter any issues, please report them using the Help button.
{!selectedPath ? ( ) : (

Selected folder:

{selectedPath}

{nameExists && (

An app with this name already exists. Please choose a different name:

)}
{isCheckingName && (
)}
Advanced options
setInstallCommand(e.target.value)} placeholder="pnpm install" disabled={importAppMutation.isPending} />
setStartCommand(e.target.value)} placeholder="pnpm dev" disabled={importAppMutation.isPending} />
{!commandsValid && (

Both commands are required when customizing.

)}
{hasAiRules === false && (

AI_RULES.md lets Dyad know which tech stack to use for editing the app

No AI_RULES.md found. Dyad will automatically generate one after importing.
)} {importAppMutation.isPending && (
Importing app...
)}
)}
); }