import { useState, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { useAppStore, type ThemeMode } from '@/store/app-store'; import { getElectronAPI } from '@/lib/electron'; import { initializeProject } from '@/lib/project-init'; import { FolderOpen, Plus, Folder, Clock, Sparkles, MessageSquare, ChevronDown, Loader2, } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { toast } from 'sonner'; import { WorkspacePickerModal } from '@/components/dialogs/workspace-picker-modal'; import { NewProjectModal } from '@/components/dialogs/new-project-modal'; import { getHttpApiClient } from '@/lib/http-api-client'; import type { StarterTemplate } from '@/lib/templates'; import { useNavigate } from '@tanstack/react-router'; export function WelcomeView() { const { projects, trashedProjects, currentProject, upsertAndSetCurrentProject, addProject, setCurrentProject, theme: globalTheme, } = useAppStore(); const navigate = useNavigate(); const [showNewProjectModal, setShowNewProjectModal] = useState(false); const [isCreating, setIsCreating] = useState(false); const [isOpening, setIsOpening] = useState(false); const [showInitDialog, setShowInitDialog] = useState(false); const [isAnalyzing, setIsAnalyzing] = useState(false); const [initStatus, setInitStatus] = useState<{ isNewProject: boolean; createdFiles: string[]; projectName: string; projectPath: string; } | null>(null); const [showWorkspacePicker, setShowWorkspacePicker] = useState(false); /** * Kick off project analysis agent to analyze the codebase */ const analyzeProject = useCallback(async (projectPath: string) => { const api = getElectronAPI(); if (!api.autoMode?.analyzeProject) { console.log('[Welcome] Auto mode API not available, skipping analysis'); return; } setIsAnalyzing(true); try { console.log('[Welcome] Starting project analysis for:', projectPath); const result = await api.autoMode.analyzeProject(projectPath); if (result.success) { toast.success('Project analyzed', { description: 'AI agent has analyzed your project structure', }); } else { console.error('[Welcome] Project analysis failed:', result.error); } } catch (error) { console.error('[Welcome] Failed to analyze project:', error); } finally { setIsAnalyzing(false); } }, []); /** * Initialize project and optionally kick off project analysis agent */ const initializeAndOpenProject = useCallback( async (path: string, name: string) => { setIsOpening(true); try { // Initialize the .automaker directory structure const initResult = await initializeProject(path); if (!initResult.success) { toast.error('Failed to initialize project', { description: initResult.error || 'Unknown error occurred', }); return; } // Upsert project and set as current (handles both create and update cases) // Theme preservation is handled by the store action const trashedProject = trashedProjects.find((p) => p.path === path); const effectiveTheme = (trashedProject?.theme as ThemeMode | undefined) || (currentProject?.theme as ThemeMode | undefined) || globalTheme; upsertAndSetCurrentProject(path, name, effectiveTheme); // Show initialization dialog if files were created if (initResult.createdFiles && initResult.createdFiles.length > 0) { setInitStatus({ isNewProject: initResult.isNewProject, createdFiles: initResult.createdFiles, projectName: name, projectPath: path, }); setShowInitDialog(true); // Kick off agent to analyze the project and update app_spec.txt console.log('[Welcome] Project initialized, created files:', initResult.createdFiles); console.log('[Welcome] Kicking off project analysis agent...'); // Start analysis in background (don't await, let it run async) analyzeProject(path); } else { toast.success('Project opened', { description: `Opened ${name}`, }); } // Navigate to the board view navigate({ to: '/board' }); } catch (error) { console.error('[Welcome] Failed to open project:', error); toast.error('Failed to open project', { description: error instanceof Error ? error.message : 'Unknown error', }); } finally { setIsOpening(false); } }, [ trashedProjects, currentProject, globalTheme, upsertAndSetCurrentProject, analyzeProject, navigate, ] ); const handleOpenProject = useCallback(async () => { try { // Check if workspace is configured const httpClient = getHttpApiClient(); const configResult = await httpClient.workspace.getConfig(); if (configResult.success && configResult.configured) { // Show workspace picker modal setShowWorkspacePicker(true); } else { // Fall back to current behavior (native dialog or manual input) const api = getElectronAPI(); const result = await api.openDirectory(); if (!result.canceled && result.filePaths[0]) { const path = result.filePaths[0]; // Extract folder name from path (works on both Windows and Mac/Linux) const name = path.split(/[/\\]/).filter(Boolean).pop() || 'Untitled Project'; await initializeAndOpenProject(path, name); } } } catch (error) { console.error('[Welcome] Failed to check workspace config:', error); // Fall back to current behavior on error const api = getElectronAPI(); const result = await api.openDirectory(); if (!result.canceled && result.filePaths[0]) { const path = result.filePaths[0]; const name = path.split(/[/\\]/).filter(Boolean).pop() || 'Untitled Project'; await initializeAndOpenProject(path, name); } } }, [initializeAndOpenProject]); /** * Handle selecting a project from workspace picker */ const handleWorkspaceSelect = useCallback( async (path: string, name: string) => { setShowWorkspacePicker(false); await initializeAndOpenProject(path, name); }, [initializeAndOpenProject] ); /** * Handle clicking on a recent project */ const handleRecentProjectClick = useCallback( async (project: { id: string; name: string; path: string }) => { await initializeAndOpenProject(project.path, project.name); }, [initializeAndOpenProject] ); const handleNewProject = () => { setShowNewProjectModal(true); }; const handleInteractiveMode = () => { navigate({ to: '/interview' }); }; /** * Create a blank project with just .automaker directory structure */ const handleCreateBlankProject = async (projectName: string, parentDir: string) => { setIsCreating(true); try { const api = getElectronAPI(); const projectPath = `${parentDir}/${projectName}`; // Validate that parent directory exists const parentExists = await api.exists(parentDir); if (!parentExists) { toast.error('Parent directory does not exist', { description: `Cannot create project in non-existent directory: ${parentDir}`, }); return; } // Verify parent is actually a directory const parentStat = await api.stat(parentDir); if (parentStat && !parentStat.stats?.isDirectory) { toast.error('Parent path is not a directory', { description: `${parentDir} is not a directory`, }); return; } // Create project directory const mkdirResult = await api.mkdir(projectPath); if (!mkdirResult.success) { toast.error('Failed to create project directory', { description: mkdirResult.error || 'Unknown error occurred', }); return; } // Initialize .automaker directory with all necessary files const initResult = await initializeProject(projectPath); if (!initResult.success) { toast.error('Failed to initialize project', { description: initResult.error || 'Unknown error occurred', }); return; } // Update the app_spec.txt with the project name // Note: Must follow XML format as defined in apps/server/src/lib/app-spec-format.ts await api.writeFile( `${projectPath}/.automaker/app_spec.txt`, ` ${projectName} Describe your project here. This file will be analyzed by an AI agent to understand your project structure and tech stack. ` ); const project = { id: `project-${Date.now()}`, name: projectName, path: projectPath, lastOpened: new Date().toISOString(), }; addProject(project); setCurrentProject(project); setShowNewProjectModal(false); toast.success('Project created', { description: `Created ${projectName} with .automaker directory`, }); // Set init status to show the dialog setInitStatus({ isNewProject: true, createdFiles: initResult.createdFiles || [], projectName: projectName, projectPath: projectPath, }); setShowInitDialog(true); } catch (error) { console.error('Failed to create project:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); } finally { setIsCreating(false); } }; /** * Create a project from a GitHub starter template */ const handleCreateFromTemplate = async ( template: StarterTemplate, projectName: string, parentDir: string ) => { setIsCreating(true); try { const httpClient = getHttpApiClient(); const api = getElectronAPI(); // Clone the template repository const cloneResult = await httpClient.templates.clone( template.repoUrl, projectName, parentDir ); if (!cloneResult.success || !cloneResult.projectPath) { toast.error('Failed to clone template', { description: cloneResult.error || 'Unknown error occurred', }); return; } const projectPath = cloneResult.projectPath; // Initialize .automaker directory with all necessary files const initResult = await initializeProject(projectPath); if (!initResult.success) { toast.error('Failed to initialize project', { description: initResult.error || 'Unknown error occurred', }); return; } // Update the app_spec.txt with template-specific info // Note: Must follow XML format as defined in apps/server/src/lib/app-spec-format.ts await api.writeFile( `${projectPath}/.automaker/app_spec.txt`, ` ${projectName} This project was created from the "${template.name}" starter template. ${template.description} ${template.techStack.map((tech) => `${tech}`).join('\n ')} ${template.features.map((feature) => `${feature}`).join('\n ')} ` ); const project = { id: `project-${Date.now()}`, name: projectName, path: projectPath, lastOpened: new Date().toISOString(), }; addProject(project); setCurrentProject(project); setShowNewProjectModal(false); toast.success('Project created from template', { description: `Created ${projectName} from ${template.name}`, }); // Set init status to show the dialog setInitStatus({ isNewProject: true, createdFiles: initResult.createdFiles || [], projectName: projectName, projectPath: projectPath, }); setShowInitDialog(true); // Kick off project analysis analyzeProject(projectPath); } catch (error) { console.error('Failed to create project from template:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); } finally { setIsCreating(false); } }; /** * Create a project from a custom GitHub URL */ const handleCreateFromCustomUrl = async ( repoUrl: string, projectName: string, parentDir: string ) => { setIsCreating(true); try { const httpClient = getHttpApiClient(); const api = getElectronAPI(); // Clone the repository const cloneResult = await httpClient.templates.clone(repoUrl, projectName, parentDir); if (!cloneResult.success || !cloneResult.projectPath) { toast.error('Failed to clone repository', { description: cloneResult.error || 'Unknown error occurred', }); return; } const projectPath = cloneResult.projectPath; // Initialize .automaker directory with all necessary files const initResult = await initializeProject(projectPath); if (!initResult.success) { toast.error('Failed to initialize project', { description: initResult.error || 'Unknown error occurred', }); return; } // Update the app_spec.txt with basic info // Note: Must follow XML format as defined in apps/server/src/lib/app-spec-format.ts await api.writeFile( `${projectPath}/.automaker/app_spec.txt`, ` ${projectName} This project was cloned from ${repoUrl}. The AI agent will analyze the project structure. ` ); const project = { id: `project-${Date.now()}`, name: projectName, path: projectPath, lastOpened: new Date().toISOString(), }; addProject(project); setCurrentProject(project); setShowNewProjectModal(false); toast.success('Project created from repository', { description: `Created ${projectName} from ${repoUrl}`, }); // Set init status to show the dialog setInitStatus({ isNewProject: true, createdFiles: initResult.createdFiles || [], projectName: projectName, projectPath: projectPath, }); setShowInitDialog(true); // Kick off project analysis analyzeProject(projectPath); } catch (error) { console.error('Failed to create project from custom URL:', error); toast.error('Failed to create project', { description: error instanceof Error ? error.message : 'Unknown error', }); } finally { setIsCreating(false); } }; const recentProjects = [...projects] .sort((a, b) => { const dateA = a.lastOpened ? new Date(a.lastOpened).getTime() : 0; const dateB = b.lastOpened ? new Date(b.lastOpened).getTime() : 0; return dateB - dateA; }) .slice(0, 5); return (
{/* Header Section */}
Automaker Logo

Welcome to Automaker

Your autonomous AI development studio

{/* Content Area */}
{/* Quick Actions */}
{/* New Project Card */}

New Project

Create a new project from scratch with AI-powered development

Quick Setup Interactive Mode
{/* Open Project Card */}

Open Project

Open an existing project folder to continue working

{/* Recent Projects */} {recentProjects.length > 0 && (

Recent Projects

{recentProjects.map((project, index) => (
handleRecentProjectClick(project)} data-testid={`recent-project-${project.id}`} style={{ animationDelay: `${index * 50}ms` }} >

{project.name}

{project.path}

{project.lastOpened && (

{new Date(project.lastOpened).toLocaleDateString()}

)}
))}
)} {/* Empty State for No Projects */} {recentProjects.length === 0 && (

No projects yet

Get started by creating a new project or opening an existing one

)}
{/* New Project Modal */} {/* Project Initialization Dialog */}
{initStatus?.isNewProject ? 'Project Initialized' : 'Project Updated'}
{initStatus?.isNewProject ? `Created .automaker directory structure for ${initStatus?.projectName}` : `Updated missing files in .automaker for ${initStatus?.projectName}`}

Created files:

    {initStatus?.createdFiles.map((file) => (
  • {file}
  • ))}
{initStatus?.isNewProject && (
{isAnalyzing ? (

AI agent is analyzing your project structure...

) : (

Tip: Edit the{' '} app_spec.txt {' '} file to describe your project. The AI agent will use this to understand your project structure.

)}
)}
{/* Workspace Picker Modal */} {/* Loading overlay when opening project */} {isOpening && (

Initializing project...

)}
); }