import { html, useState, useEffect, useCallback } from '../lib/preact.js'; import { API_PROJECTS, API_SPAWN, fetchWithTimeout } from '../utils/api.js'; export function SpawnModal({ isOpen, onClose, onSpawn, currentProject }) { const [projects, setProjects] = useState([]); const [selectedProject, setSelectedProject] = useState(''); const [agentType, setAgentType] = useState('claude'); const [loading, setLoading] = useState(false); const [loadingProjects, setLoadingProjects] = useState(false); const [closing, setClosing] = useState(false); const [error, setError] = useState(null); const needsProjectPicker = !currentProject; // Lock body scroll when modal is open useEffect(() => { if (!isOpen) return; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; }, [isOpen]); // Reset state on open useEffect(() => { if (isOpen) { setAgentType('claude'); setError(null); setLoading(false); setClosing(false); } }, [isOpen]); // Fetch projects when needed useEffect(() => { if (isOpen && needsProjectPicker) { setLoadingProjects(true); fetchWithTimeout(API_PROJECTS) .then(r => r.json()) .then(data => { setProjects(data.projects || []); setSelectedProject(''); }) .catch(err => setError(err.message)) .finally(() => setLoadingProjects(false)); } }, [isOpen, needsProjectPicker]); // Animated close handler const handleClose = useCallback(() => { if (loading) return; setClosing(true); setTimeout(() => { setClosing(false); onClose(); }, 200); }, [loading, onClose]); // Handle escape key useEffect(() => { if (!isOpen) return; const handleKeyDown = (e) => { if (e.key === 'Escape') handleClose(); }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen, handleClose]); const handleSpawn = async () => { const project = currentProject || selectedProject; if (!project) { setError('Please select a project'); return; } setLoading(true); setError(null); try { const response = await fetchWithTimeout(API_SPAWN, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${window.AMC_AUTH_TOKEN}`, }, body: JSON.stringify({ project, agent_type: agentType }), }); const data = await response.json(); if (data.ok) { onSpawn({ success: true, project, agentType }); handleClose(); } else { setError(data.error || 'Spawn failed'); onSpawn({ error: data.error }); } } catch (err) { const msg = err.name === 'AbortError' ? 'Request timed out' : err.message; setError(msg); onSpawn({ error: msg }); } finally { setLoading(false); } }; if (!isOpen) return null; const canSpawn = !loading && (currentProject || selectedProject); return html`
e.target === e.currentTarget && handleClose()} >
e.stopPropagation()} >

Spawn Agent

${needsProjectPicker && html`
${loadingProjects ? html`
... Loading projects
` : html` `}
`} ${currentProject && html`
${currentProject}
`}
${error && html`
${error}
`}
`; }