From 838927f316960eb55fbed6d4c69eb2157e3f15c2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 15 Aug 2025 12:38:43 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Fix=20webhook=20error=20handling=20?= =?UTF-8?q?with=20reliable=20error=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API now properly detects N8N errors in 200 responses (OpenAI quota, etc.) - Returns 400 status with simple "Agent unavailable" message for webhook failures - Frontend shows errors in results container instead of disappearing toast notifications - Users now see clear, persistent error messages when agents fail - Keeps success toasts working, only fixes error display 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- agents/api_views.py | 33 +++++++++++++++++++++++++-------- static/js/agents-core.js | 34 ++++++++++++++++++++++++++++++++-- static/js/workflows-core.js | 11 ++++++++--- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/agents/api_views.py b/agents/api_views.py index b3541b6..45c32a2 100644 --- a/agents/api_views.py +++ b/agents/api_views.py @@ -102,18 +102,35 @@ def execute_agent(request): # Store webhook response execution.webhook_response = response.json() if response.headers.get('content-type', '').startswith('application/json') else {'raw': response.text} - if response.status_code == 200: + # Check if response contains N8N error indicators + has_error = False + if response.status_code == 200 and execution.webhook_response: + # Check for N8N error patterns + if isinstance(execution.webhook_response, dict): + if 'errorMessage' in execution.webhook_response or 'error' in execution.webhook_response: + has_error = True + + if response.status_code == 200 and not has_error: execution.status = 'completed' execution.output_data = execution.webhook_response + execution.completed_at = timezone.now() + execution.save() + + serializer = AgentExecutionSerializer(execution) + return Response(serializer.data, status=status.HTTP_201_CREATED) else: execution.status = 'failed' - execution.error_message = f"Webhook returned {response.status_code}: {response.text[:500]}" - - execution.completed_at = timezone.now() - execution.save() - - serializer = AgentExecutionSerializer(execution) - return Response(serializer.data, status=status.HTTP_201_CREATED) + if has_error: + error_msg = execution.webhook_response.get('errorMessage', 'Webhook execution failed') + execution.error_message = f"N8N Error: {error_msg[:500]}" + else: + execution.error_message = f"Webhook returned {response.status_code}: {response.text[:500]}" + execution.completed_at = timezone.now() + execution.save() + + return Response({ + 'error': 'Agent is temporarily unavailable. Please try again later.' + }, status=status.HTTP_400_BAD_REQUEST) except requests.RequestException as e: execution.status = 'failed' diff --git a/static/js/agents-core.js b/static/js/agents-core.js index 225b4ba..7089048 100644 --- a/static/js/agents-core.js +++ b/static/js/agents-core.js @@ -57,7 +57,7 @@ class AgentsCore extends WorkflowsCore { } catch (error) { console.error('Form submission error:', error); this.constructor.hideProcessing(); - this.constructor.showToast('❌ Connection error. Please try again.', 'error'); + this.showErrorMessage('❌ Agent is temporarily unavailable. Please try again later.'); this.resetSubmitButton(); } } @@ -84,7 +84,7 @@ class AgentsCore extends WorkflowsCore { } catch (error) { console.error('Agent execution error:', error); this.constructor.hideProcessing(); - this.constructor.showToast(`❌ ${error.message}`, 'error'); + this.showErrorMessage('❌ Agent is temporarily unavailable. Please try again later.'); this.resetSubmitButton(); } } @@ -545,6 +545,36 @@ class AgentsCore extends WorkflowsCore { } } + /** + * Show error message in results container + */ + showErrorMessage(message) { + const resultsContainer = document.getElementById('resultsContainer'); + const resultsContent = document.getElementById('resultsContent'); + + if (resultsContainer && resultsContent) { + resultsContent.innerHTML = ` +
+ ${message} +
+ `; + + // Show results container + resultsContainer.style.display = 'block'; + resultsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + } + /** * Reset submit button to original state */ diff --git a/static/js/workflows-core.js b/static/js/workflows-core.js index 5c12ece..5c6de2a 100644 --- a/static/js/workflows-core.js +++ b/static/js/workflows-core.js @@ -32,8 +32,9 @@ class WorkflowsCore { position: fixed; top: 20px; right: 20px; - z-index: 10000; + z-index: 99999; max-width: 400px; + pointer-events: none; `; // Safely append to body @@ -61,6 +62,9 @@ class WorkflowsCore { display: flex; align-items: center; animation: slideIn 0.3s ease-out; + pointer-events: auto; + position: relative; + z-index: 100000; `; // Add icon and message @@ -94,12 +98,13 @@ class WorkflowsCore { toastContainer.appendChild(toast); - // Auto-remove after 5 seconds + // Auto-remove after longer time for errors + const timeout = type === 'error' ? 8000 : 5000; // 8 seconds for errors, 5 for others setTimeout(() => { if (toast.parentElement) { toast.remove(); } - }, 5000); + }, timeout); } /**