import React, { useEffect, useMemo, useState } from 'react'; import { api } from '@renderer/api'; import { Button } from '@renderer/components/ui/button'; import { Combobox } from '@renderer/components/ui/combobox'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@renderer/components/ui/dialog'; import { Input } from '@renderer/components/ui/input'; import { Label } from '@renderer/components/ui/label'; import { MentionableTextarea } from '@renderer/components/ui/MentionableTextarea'; import { useDraftPersistence } from '@renderer/hooks/useDraftPersistence'; import { cn } from '@renderer/lib/utils'; import { Check, CheckCircle2, Loader2 } from 'lucide-react'; import type { MentionSuggestion } from '@renderer/types/mention'; import type { Project, TeamLaunchRequest, TeamProvisioningPrepareResult } from '@shared/types'; interface LaunchTeamDialogProps { open: boolean; teamName: string; defaultProjectPath?: string; provisioningError: string | null; onClose: () => void; onLaunch: (request: TeamLaunchRequest) => Promise; } function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function renderHighlightedText(text: string, query: string): React.JSX.Element { if (!query.trim()) { return {text}; } const pattern = new RegExp(`(${escapeRegExp(query)})`, 'ig'); const parts = text.split(pattern); return ( {parts.map((part, index) => { const isMatch = part.toLowerCase() === query.toLowerCase(); if (!isMatch) { return {part}; } return ( {part} ); })} ); } export const LaunchTeamDialog = ({ open, teamName, defaultProjectPath, provisioningError, onClose, onLaunch, }: LaunchTeamDialogProps): React.JSX.Element => { const [cwdMode, setCwdMode] = useState<'project' | 'custom'>('project'); const [selectedProjectPath, setSelectedProjectPath] = useState(''); const [customCwd, setCustomCwd] = useState(''); const promptDraft = useDraftPersistence({ key: `launchTeam:${teamName}:prompt` }); const [projects, setProjects] = useState([]); const [projectsLoading, setProjectsLoading] = useState(false); const [projectsError, setProjectsError] = useState(null); const [localError, setLocalError] = useState(null); const [prepareState, setPrepareState] = useState<'idle' | 'loading' | 'ready' | 'failed'>('idle'); const [prepareMessage, setPrepareMessage] = useState(null); const [prepareWarnings, setPrepareWarnings] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const resetFormState = (): void => { setLocalError(null); setIsSubmitting(false); setPrepareState('idle'); setPrepareMessage(null); setPrepareWarnings([]); setCwdMode('project'); setSelectedProjectPath(''); setCustomCwd(''); }; // Warm up CLI on open useEffect(() => { if (!open) { return; } if (typeof api.teams.prepareProvisioning !== 'function') { setPrepareState('failed'); setPrepareWarnings([]); setPrepareMessage( 'Current preload version does not support team:prepareProvisioning. Restart the dev app.' ); return; } let cancelled = false; setPrepareState('loading'); setPrepareMessage('Warming up CLI environment...'); setPrepareWarnings([]); void (async () => { try { const prepResult: TeamProvisioningPrepareResult = await api.teams.prepareProvisioning(); if (cancelled) { return; } setPrepareState(prepResult.ready ? 'ready' : 'failed'); setPrepareMessage(prepResult.message); setPrepareWarnings(prepResult.warnings ?? []); } catch (error) { if (cancelled) { return; } setPrepareState('failed'); setPrepareWarnings([]); setPrepareMessage( error instanceof Error ? error.message : 'Failed to warm up Claude CLI environment' ); } })(); return () => { cancelled = true; }; }, [open]); // Fetch projects on open useEffect(() => { if (!open) { return; } setProjectsLoading(true); setProjectsError(null); let cancelled = false; void (async () => { try { const nextProjects = await api.getProjects(); if (cancelled) { return; } setProjects(nextProjects); } catch (error) { if (cancelled) { return; } setProjectsError(error instanceof Error ? error.message : 'Failed to load projects'); setProjects([]); } finally { if (!cancelled) { setProjectsLoading(false); } } })(); return () => { cancelled = true; }; }, [open]); // Pre-select defaultProjectPath when projects loaded useEffect(() => { if (cwdMode !== 'project') { return; } if (selectedProjectPath || projects.length === 0) { return; } if (defaultProjectPath) { const match = projects.find((p) => p.path === defaultProjectPath); if (match) { setSelectedProjectPath(match.path); return; } } setSelectedProjectPath(projects[0].path); }, [cwdMode, projects, selectedProjectPath, defaultProjectPath]); const effectiveCwd = cwdMode === 'project' ? selectedProjectPath.trim() : customCwd.trim(); const mentionSuggestions = useMemo( () => projects.map((p) => ({ id: p.path, name: p.name, subtitle: p.path, })), [projects] ); const activeError = localError ?? provisioningError; const handleSubmit = (): void => { if (!effectiveCwd) { setLocalError('Select working directory (cwd)'); return; } setLocalError(null); setIsSubmitting(true); void (async () => { try { await onLaunch({ teamName, cwd: effectiveCwd, prompt: promptDraft.value.trim() || undefined, }); resetFormState(); onClose(); } catch { // error is shown via provisioningError prop } finally { setIsSubmitting(false); } })(); }; return ( { if (!nextOpen) { resetFormState(); onClose(); } }} > Launch Team Start team {teamName} via local Claude CLI. {prepareState === 'failed' ? (

{prepareMessage ?? 'Failed to prepare environment'}

{prepareWarnings.length > 0 ? (
{prepareWarnings.map((warning) => (

{warning}

))}
) : null}
) : null}
{cwdMode === 'project' ? (
({ value: project.path, label: project.name, description: project.path, }))} value={selectedProjectPath} onValueChange={setSelectedProjectPath} placeholder={projectsLoading ? 'Loading projects...' : 'Select a project...'} searchPlaceholder="Search project by name or path" emptyMessage="Nothing found" disabled={projectsLoading || projects.length === 0} renderOption={(option, isSelected, query) => ( <>

{renderHighlightedText(option.label, query)}

{renderHighlightedText(option.description ?? '', query)}

)} /> {!selectedProjectPath ? (

Select a project from the list

) : null} {projectsError ? (

{projectsError}

) : null} {!projectsLoading && projects.length === 0 ? (

No projects found, switch to custom path.

) : null}
) : (
setCustomCwd(event.target.value)} placeholder="/absolute/path/to/project" />
)}
Draft saved ) : null } />
{activeError ? (

{activeError}

) : null} {prepareState === 'idle' || prepareState === 'loading' ? (
{prepareMessage ?? (prepareState === 'idle' ? 'Warming up CLI environment...' : 'Preparing environment...')}
) : null} {prepareState === 'ready' ? (
CLI environment ready
) : null}
); };