import { useState, useEffect } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { GitCommit, Sparkles } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { getElectronAPI } from '@/lib/electron'; import { toast } from 'sonner'; import { useAppStore } from '@/store/app-store'; interface WorktreeInfo { path: string; branch: string; isMain: boolean; hasChanges?: boolean; changedFilesCount?: number; } interface CommitWorktreeDialogProps { open: boolean; onOpenChange: (open: boolean) => void; worktree: WorktreeInfo | null; onCommitted: () => void; } export function CommitWorktreeDialog({ open, onOpenChange, worktree, onCommitted, }: CommitWorktreeDialogProps) { const [message, setMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); const [isGenerating, setIsGenerating] = useState(false); const [error, setError] = useState(null); const enableAiCommitMessages = useAppStore((state) => state.enableAiCommitMessages); const handleCommit = async () => { if (!worktree || !message.trim()) return; setIsLoading(true); setError(null); try { const api = getElectronAPI(); if (!api?.worktree?.commit) { setError('Worktree API not available'); return; } const result = await api.worktree.commit(worktree.path, message); if (result.success && result.result) { if (result.result.committed) { toast.success('Changes committed', { description: `Commit ${result.result.commitHash} on ${result.result.branch}`, }); onCommitted(); onOpenChange(false); setMessage(''); } else { toast.info('No changes to commit', { description: result.result.message, }); } } else { setError(result.error || 'Failed to commit changes'); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to commit'); } finally { setIsLoading(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { // Prevent commit while loading or while AI is generating a message if (e.key === 'Enter' && e.metaKey && !isLoading && !isGenerating && message.trim()) { handleCommit(); } }; // Generate AI commit message when dialog opens (if enabled) useEffect(() => { if (open && worktree) { // Reset state setMessage(''); setError(null); // Only generate AI commit message if enabled if (!enableAiCommitMessages) { return; } setIsGenerating(true); let cancelled = false; const generateMessage = async () => { try { const api = getElectronAPI(); if (!api?.worktree?.generateCommitMessage) { if (!cancelled) { setIsGenerating(false); } return; } const result = await api.worktree.generateCommitMessage(worktree.path); if (cancelled) return; if (result.success && result.message) { setMessage(result.message); } else { // Don't show error toast, just log it and leave message empty console.warn('Failed to generate commit message:', result.error); setMessage(''); } } catch (err) { if (cancelled) return; // Don't show error toast for generation failures console.warn('Error generating commit message:', err); setMessage(''); } finally { if (!cancelled) { setIsGenerating(false); } } }; generateMessage(); return () => { cancelled = true; }; } }, [open, worktree, enableAiCommitMessages]); if (!worktree) return null; return ( Commit Changes Commit changes in the{' '} {worktree.branch} worktree. {worktree.changedFilesCount && ( ({worktree.changedFilesCount} file {worktree.changedFilesCount > 1 ? 's' : ''} changed) )}