diff --git a/src/__tests__/renderer/hooks/useInlineWizard.test.ts b/src/__tests__/renderer/hooks/useInlineWizard.test.ts index b8c4976a..87dcf55a 100644 --- a/src/__tests__/renderer/hooks/useInlineWizard.test.ts +++ b/src/__tests__/renderer/hooks/useInlineWizard.test.ts @@ -477,6 +477,91 @@ describe('useInlineWizard', () => { }); }); + describe('clearError', () => { + it('should clear the current error', async () => { + const { result } = renderHook(() => useInlineWizard()); + + // Set an error manually + act(() => { + result.current.setError('Something went wrong'); + }); + + expect(result.current.error).toBe('Something went wrong'); + + // Clear the error + act(() => { + result.current.clearError(); + }); + + expect(result.current.error).toBe(null); + }); + + it('should not affect other state when clearing error', async () => { + const { result } = renderHook(() => useInlineWizard()); + + // Start wizard and set some state + await act(async () => { + await result.current.startWizard('test', undefined, '/test/project'); + }); + + act(() => { + result.current.setConfidence(50); + result.current.setError('Test error'); + }); + + expect(result.current.isWizardActive).toBe(true); + expect(result.current.confidence).toBe(50); + expect(result.current.error).toBe('Test error'); + + // Clear error + act(() => { + result.current.clearError(); + }); + + // Other state should be preserved + expect(result.current.isWizardActive).toBe(true); + expect(result.current.confidence).toBe(50); + expect(result.current.error).toBe(null); + }); + }); + + describe('retryLastMessage', () => { + it('should not retry when there is no error', async () => { + const { result } = renderHook(() => useInlineWizard()); + const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + await act(async () => { + await result.current.retryLastMessage(); + }); + + expect(consoleSpy).toHaveBeenCalledWith( + '[useInlineWizard] Cannot retry: no last message or no error' + ); + + consoleSpy.mockRestore(); + }); + + it('should not retry when there is no last message', async () => { + const { result } = renderHook(() => useInlineWizard()); + const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + // Set an error but don't send a message + act(() => { + result.current.setError('Some error'); + }); + + await act(async () => { + await result.current.retryLastMessage(); + }); + + expect(consoleSpy).toHaveBeenCalledWith( + '[useInlineWizard] Cannot retry: no last message or no error' + ); + + consoleSpy.mockRestore(); + }); + }); + describe('generateDocuments', () => { it('should return error when agent type is missing', async () => { const { result } = renderHook(() => useInlineWizard()); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 0dc7d59f..52ffd3e9 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -4241,7 +4241,11 @@ You are taking over this conversation. Based on the context above, provide a bri // Inline wizard hook for /wizard command // This manages the state for the inline wizard that creates/iterates on Auto Run documents - const { startWizard: startInlineWizard } = useInlineWizard(); + const { + startWizard: startInlineWizard, + clearError: clearInlineWizardError, + retryLastMessage: retryInlineWizardMessage, + } = useInlineWizard(); // Handler for the built-in /history command // Requests a synopsis from the current agent session and saves to history @@ -10044,6 +10048,9 @@ You are taking over this conversation. Based on the context above, provide a bri // Refresh the Auto Run panel to show newly generated documents handleAutoRunRefresh(); }} + // Inline wizard error handling callbacks + onWizardRetry={retryInlineWizardMessage} + onWizardClearError={clearInlineWizardError} /> )} diff --git a/src/renderer/components/InlineWizard/WizardConversationView.tsx b/src/renderer/components/InlineWizard/WizardConversationView.tsx index c6b4cb7e..bdbdf67d 100644 --- a/src/renderer/components/InlineWizard/WizardConversationView.tsx +++ b/src/renderer/components/InlineWizard/WizardConversationView.tsx @@ -45,6 +45,12 @@ export interface WizardConversationViewProps { ready?: boolean; /** Callback when user clicks the "Let's Go" button to start document generation */ onLetsGo?: () => void; + /** Error message to display (if any) */ + error?: string | null; + /** Callback when user clicks the retry button */ + onRetry?: () => void; + /** Callback to clear the error */ + onClearError?: () => void; } /** @@ -207,6 +213,174 @@ function StreamingResponse({ ); } +/** + * Get a user-friendly error message from a raw error string. + * Maps technical errors to helpful messages. + */ +function getUserFriendlyErrorMessage(error: string): { title: string; description: string } { + const lowerError = error.toLowerCase(); + + // Network/timeout errors + if (lowerError.includes('timeout') || lowerError.includes('timed out')) { + return { + title: 'Response Timeout', + description: 'The AI agent took too long to respond. This can happen with complex requests or network issues.', + }; + } + + // Agent not available errors + if (lowerError.includes('not available') || lowerError.includes('not found')) { + return { + title: 'Agent Not Available', + description: 'The AI agent could not be started. Please check that it is properly installed and configured.', + }; + } + + // Session errors + if (lowerError.includes('session') && (lowerError.includes('not active') || lowerError.includes('no active'))) { + return { + title: 'Session Error', + description: 'The wizard session is no longer active. Please restart the wizard.', + }; + } + + // Failed to spawn errors + if (lowerError.includes('failed to spawn')) { + return { + title: 'Failed to Start Agent', + description: 'Could not start the AI agent. Please check your configuration and try again.', + }; + } + + // Exit code errors + if (lowerError.includes('exited with code')) { + return { + title: 'Agent Error', + description: 'The AI agent encountered an error and stopped unexpectedly.', + }; + } + + // Parse errors + if (lowerError.includes('parse') || lowerError.includes('failed to parse')) { + return { + title: 'Response Error', + description: 'Could not understand the response from the AI. Please try rephrasing your message.', + }; + } + + // Default generic error + return { + title: 'Something Went Wrong', + description: error || 'An unexpected error occurred. Please try again.', + }; +} + +/** + * ErrorDisplay - Shows error messages with a retry button + */ +function ErrorDisplay({ + theme, + error, + onRetry, + onDismiss, +}: { + theme: Theme; + error: string; + onRetry?: () => void; + onDismiss?: () => void; +}): JSX.Element { + const { title, description } = getUserFriendlyErrorMessage(error); + + return ( +
+ {description} +
+ + {/* Action buttons */} +
+ {error}
+
+