import { useState, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import BranchSelector from '@/components/tasks/BranchSelector'; import { ExecutorProfileSelector } from '@/components/settings'; import { useAttemptCreation } from '@/hooks/useAttemptCreation'; import { useNavigateWithSearch, useTask, useAttempt, useBranches, useTaskAttempts, } from '@/hooks'; import { useProject } from '@/contexts/ProjectContext'; import { useUserSystem } from '@/components/ConfigProvider'; import { paths } from '@/lib/paths'; import NiceModal, { useModal } from '@ebay/nice-modal-react'; import { defineModal } from '@/lib/modals'; import type { ExecutorProfileId, BaseCodingAgent } from 'shared/types'; import { useKeySubmitTask, Scope } from '@/keyboard'; export interface CreateAttemptDialogProps { taskId: string; } const CreateAttemptDialogImpl = NiceModal.create( ({ taskId }) => { const modal = useModal(); const navigate = useNavigateWithSearch(); const { projectId } = useProject(); const { t } = useTranslation('tasks'); const { profiles, config } = useUserSystem(); const { createAttempt, isCreating, error } = useAttemptCreation({ taskId, onSuccess: (attempt) => { if (projectId) { navigate(paths.attempt(projectId, taskId, attempt.id)); } }, }); const [userSelectedProfile, setUserSelectedProfile] = useState(null); const [userSelectedBranch, setUserSelectedBranch] = useState( null ); const { data: branches = [], isLoading: isLoadingBranches } = useBranches( projectId, { enabled: modal.visible && !!projectId } ); const { data: attempts = [], isLoading: isLoadingAttempts } = useTaskAttempts(taskId, { enabled: modal.visible, refetchInterval: 5000, }); const { data: task, isLoading: isLoadingTask } = useTask(taskId, { enabled: modal.visible, }); const parentAttemptId = task?.parent_task_attempt ?? undefined; const { data: parentAttempt, isLoading: isLoadingParent } = useAttempt( parentAttemptId, { enabled: modal.visible && !!parentAttemptId } ); const latestAttempt = useMemo(() => { if (attempts.length === 0) return null; return attempts.reduce((latest, attempt) => new Date(attempt.created_at) > new Date(latest.created_at) ? attempt : latest ); }, [attempts]); useEffect(() => { if (!modal.visible) { setUserSelectedProfile(null); setUserSelectedBranch(null); } }, [modal.visible]); const defaultProfile: ExecutorProfileId | null = useMemo(() => { if (latestAttempt?.executor) { const lastExec = latestAttempt.executor as BaseCodingAgent; // If the last attempt used the same executor as the user's current preference, // we assume they want to use their preferred variant as well. // Otherwise, we default to the "default" variant (null) since we don't know // what variant they used last time (TaskAttempt doesn't store it). const variant = config?.executor_profile?.executor === lastExec ? config.executor_profile.variant : null; return { executor: lastExec, variant, }; } return config?.executor_profile ?? null; }, [latestAttempt?.executor, config?.executor_profile]); const currentBranchName: string | null = useMemo(() => { return branches.find((b) => b.is_current)?.name ?? null; }, [branches]); const defaultBranch: string | null = useMemo(() => { return ( parentAttempt?.branch ?? currentBranchName ?? latestAttempt?.target_branch ?? null ); }, [ parentAttempt?.branch, currentBranchName, latestAttempt?.target_branch, ]); const effectiveProfile = userSelectedProfile ?? defaultProfile; const effectiveBranch = userSelectedBranch ?? defaultBranch; const isLoadingInitial = isLoadingBranches || isLoadingAttempts || isLoadingTask || isLoadingParent; const canCreate = Boolean( effectiveProfile && effectiveBranch && !isCreating && !isLoadingInitial ); const handleCreate = async () => { if (!effectiveProfile || !effectiveBranch) return; try { await createAttempt({ profile: effectiveProfile, baseBranch: effectiveBranch, }); modal.hide(); } catch (err) { console.error('Failed to create attempt:', err); } }; const handleOpenChange = (open: boolean) => { if (!open) modal.hide(); }; useKeySubmitTask(handleCreate, { enabled: modal.visible && canCreate, scope: Scope.DIALOG, preventDefault: true, }); return ( {t('createAttemptDialog.title')} {t('createAttemptDialog.description')}
{profiles && (
)}
{error && (
{t('createAttemptDialog.error')}
)}
); } ); export const CreateAttemptDialog = defineModal( CreateAttemptDialogImpl );