import { useEffect, useRef, useState, useCallback } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { HotkeyButton } from '@/components/ui/hotkey-button'; import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; import { Loader2, Lightbulb, Download, StopCircle, ChevronDown, ChevronRight, RefreshCw, Shield, Zap, } from 'lucide-react'; import { getElectronAPI, FeatureSuggestion, SuggestionsEvent, SuggestionType, } from '@/lib/electron'; import { useAppStore, Feature } from '@/store/app-store'; import { toast } from 'sonner'; interface FeatureSuggestionsDialogProps { open: boolean; onClose: () => void; projectPath: string; // Props to persist state across dialog open/close suggestions: FeatureSuggestion[]; setSuggestions: (suggestions: FeatureSuggestion[]) => void; isGenerating: boolean; setIsGenerating: (generating: boolean) => void; } // Configuration for each suggestion type const suggestionTypeConfig: Record< SuggestionType, { label: string; icon: React.ComponentType<{ className?: string }>; description: string; color: string; } > = { features: { label: 'Feature Suggestions', icon: Lightbulb, description: 'Discover missing features and improvements', color: 'text-yellow-500', }, refactoring: { label: 'Refactoring Suggestions', icon: RefreshCw, description: 'Find code smells and refactoring opportunities', color: 'text-blue-500', }, security: { label: 'Security Suggestions', icon: Shield, description: 'Identify security vulnerabilities and issues', color: 'text-red-500', }, performance: { label: 'Performance Suggestions', icon: Zap, description: 'Discover performance bottlenecks and optimizations', color: 'text-green-500', }, }; export function FeatureSuggestionsDialog({ open, onClose, projectPath, suggestions, setSuggestions, isGenerating, setIsGenerating, }: FeatureSuggestionsDialogProps) { const [progress, setProgress] = useState([]); const [selectedIds, setSelectedIds] = useState>(new Set()); const [expandedIds, setExpandedIds] = useState>(new Set()); const [isImporting, setIsImporting] = useState(false); const [currentSuggestionType, setCurrentSuggestionType] = useState(null); const scrollRef = useRef(null); const autoScrollRef = useRef(true); const { features, setFeatures } = useAppStore(); // Initialize selectedIds when suggestions change useEffect(() => { if (suggestions.length > 0 && selectedIds.size === 0) { setSelectedIds(new Set(suggestions.map((s) => s.id))); } }, [suggestions, selectedIds.size]); // Auto-scroll progress when new content arrives useEffect(() => { if (autoScrollRef.current && scrollRef.current && isGenerating) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [progress, isGenerating]); // Listen for suggestion events when dialog is open useEffect(() => { if (!open) return; const api = getElectronAPI(); if (!api?.suggestions) return; const unsubscribe = api.suggestions.onEvent((event: SuggestionsEvent) => { if (event.type === 'suggestions_progress') { setProgress((prev) => [...prev, event.content || '']); } else if (event.type === 'suggestions_tool') { const toolName = event.tool || 'Unknown Tool'; setProgress((prev) => [...prev, `Using tool: ${toolName}\n`]); } else if (event.type === 'suggestions_complete') { setIsGenerating(false); if (event.suggestions && event.suggestions.length > 0) { setSuggestions(event.suggestions); // Select all by default setSelectedIds(new Set(event.suggestions.map((s) => s.id))); const typeLabel = currentSuggestionType ? suggestionTypeConfig[currentSuggestionType].label.toLowerCase() : 'suggestions'; toast.success(`Generated ${event.suggestions.length} ${typeLabel}!`); } else { toast.info('No suggestions generated. Try again.'); } } else if (event.type === 'suggestions_error') { setIsGenerating(false); toast.error(`Error: ${event.error}`); } }); return () => { unsubscribe(); }; }, [open, setSuggestions, setIsGenerating, currentSuggestionType]); // Start generating suggestions for a specific type const handleGenerate = useCallback( async (suggestionType: SuggestionType) => { const api = getElectronAPI(); if (!api?.suggestions) { toast.error('Suggestions API not available'); return; } setIsGenerating(true); setProgress([]); setSuggestions([]); setSelectedIds(new Set()); setCurrentSuggestionType(suggestionType); try { const result = await api.suggestions.generate(projectPath, suggestionType); if (!result.success) { toast.error(result.error || 'Failed to start generation'); setIsGenerating(false); } } catch (error) { console.error('Failed to generate suggestions:', error); toast.error('Failed to start generation'); setIsGenerating(false); } }, [projectPath, setIsGenerating, setSuggestions] ); // Stop generating const handleStop = useCallback(async () => { const api = getElectronAPI(); if (!api?.suggestions) return; try { await api.suggestions.stop(); setIsGenerating(false); toast.info('Generation stopped'); } catch (error) { console.error('Failed to stop generation:', error); } }, [setIsGenerating]); // Toggle suggestion selection const toggleSelection = useCallback((id: string) => { setSelectedIds((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }, []); // Toggle expand/collapse for a suggestion const toggleExpanded = useCallback((id: string) => { setExpandedIds((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); } else { next.add(id); } return next; }); }, []); // Select/deselect all const toggleSelectAll = useCallback(() => { if (selectedIds.size === suggestions.length) { setSelectedIds(new Set()); } else { setSelectedIds(new Set(suggestions.map((s) => s.id))); } }, [selectedIds.size, suggestions]); // Import selected suggestions as features const handleImport = useCallback(async () => { if (selectedIds.size === 0) { toast.warning('No suggestions selected'); return; } setIsImporting(true); try { const api = getElectronAPI(); const selectedSuggestions = suggestions.filter((s) => selectedIds.has(s.id)); // Create new features from selected suggestions const newFeatures: Feature[] = selectedSuggestions.map((s) => ({ id: `feature-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, category: s.category, description: s.description, status: 'backlog' as const, skipTests: true, // As specified, testing mode true priority: s.priority, // Preserve priority from suggestion })); // Create each new feature using the features API if (api.features) { for (const feature of newFeatures) { await api.features.create(projectPath, feature); } } // Merge with existing features for store update const updatedFeatures = [...features, ...newFeatures]; // Update store setFeatures(updatedFeatures); toast.success(`Imported ${newFeatures.length} features to backlog!`); // Clear suggestions after importing setSuggestions([]); setSelectedIds(new Set()); setProgress([]); setCurrentSuggestionType(null); onClose(); } catch (error) { console.error('Failed to import features:', error); toast.error('Failed to import features'); } finally { setIsImporting(false); } }, [selectedIds, suggestions, features, setFeatures, setSuggestions, projectPath, onClose]); // Handle scroll to detect if user scrolled up const handleScroll = () => { if (!scrollRef.current) return; const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; const isAtBottom = scrollHeight - scrollTop - clientHeight < 50; autoScrollRef.current = isAtBottom; }; // Go back to type selection const handleBackToSelection = useCallback(() => { setSuggestions([]); setSelectedIds(new Set()); setProgress([]); setCurrentSuggestionType(null); }, [setSuggestions]); const hasStarted = progress.length > 0 || suggestions.length > 0; const hasSuggestions = suggestions.length > 0; const currentConfig = currentSuggestionType ? suggestionTypeConfig[currentSuggestionType] : null; return ( {currentConfig ? ( <> {currentConfig.label} ) : ( <> AI Suggestions )} {currentConfig ? currentConfig.description : 'Analyze your project to discover improvements. Choose a suggestion type below.'} {!hasStarted ? ( // Initial state - show suggestion type buttons

Our AI will analyze your project and generate actionable suggestions. Choose what type of analysis you want to perform:

{( Object.entries(suggestionTypeConfig) as [ SuggestionType, (typeof suggestionTypeConfig)[SuggestionType], ][] ).map(([type, config]) => { const Icon = config.icon; return ( ); })}
) : isGenerating ? ( // Generating state - show progress
Analyzing project...
{progress.join('')}
) : hasSuggestions ? ( // Results state - show suggestions list
{suggestions.length} suggestions generated
{selectedIds.size} selected
{suggestions.map((suggestion) => { const isSelected = selectedIds.has(suggestion.id); const isExpanded = expandedIds.has(suggestion.id); return (
toggleSelection(suggestion.id)} className="mt-1" />
#{suggestion.priority} {suggestion.category}
{isExpanded && suggestion.reasoning && (

{suggestion.reasoning}

)}
); })}
) : ( // No results state

No suggestions were generated. Try running the analysis again.

{currentSuggestionType && ( )}
)} {hasSuggestions && (
{currentSuggestionType && ( )}
{isImporting ? ( ) : ( )} Import {selectedIds.size} Feature {selectedIds.size !== 1 ? 's' : ''}
)} {!hasSuggestions && !isGenerating && hasStarted && ( )}
); }