import React from 'react';
const ErrorDisplay = ({ error, onRetry, onDismiss, showDetails = false }) => {
if (!error) return null;
// Parse error details
const getErrorInfo = (error) => {
const info = {
title: 'Something went wrong',
message: error.message || 'An unexpected error occurred',
type: 'error',
suggestions: [],
details: {}
};
// Parse enhanced error details
if (error.details) {
info.details = error.details;
if (error.details.type) {
info.type = error.details.type;
}
}
// Provide user-friendly messages based on error patterns
const message = error.message?.toLowerCase() || '';
if (message.includes('cannot connect') || message.includes('fetch')) {
info.title = 'Connection Error';
info.message = 'Cannot connect to the MCP server';
info.suggestions = [
'Check if the MCP server is running on localhost:8000',
'Verify your network connection',
'Try refreshing the page'
];
} else if (message.includes('question is required') || message.includes('message') && message.includes('required')) {
info.title = 'Invalid Input';
info.message = 'Please enter a message or question';
info.suggestions = [
'Make sure your input field is not empty',
'Check that you\'ve typed something in the message box'
];
} else if (message.includes('timeout')) {
info.title = 'Request Timeout';
info.message = 'The request took too long to complete';
info.suggestions = [
'Try again with a shorter message',
'Check your internet connection',
'The server might be overloaded - wait a moment and retry'
];
} else if (message.includes('rate limit') || message.includes('quota')) {
info.title = 'Rate Limit Exceeded';
info.message = 'Too many requests - please slow down';
info.suggestions = [
'Wait a few moments before trying again',
'Reduce the frequency of your requests'
];
} else if (message.includes('unauthorized') || message.includes('api key')) {
info.title = 'Authentication Error';
info.message = 'API authentication failed';
info.suggestions = [
'Check your API key configuration',
'Verify your credentials are correct',
'Contact your administrator if the problem persists'
];
} else if (message.includes('model') && message.includes('not found')) {
info.title = 'Model Not Available';
info.message = 'The requested AI model is not available';
info.suggestions = [
'Try using "mistral:latest" as the model',
'Check available models in the dropdown',
'Verify the model name is spelled correctly'
];
} else if (message.includes('database') || message.includes('sql')) {
info.title = 'Database Error';
info.message = 'Database operation failed';
info.suggestions = [
'Check your database connection',
'Verify the database is running',
'Check your query syntax if applicable'
];
} else if (error.status) {
// HTTP status specific messages
if (error.status === 400) {
info.title = 'Invalid Request';
info.message = error.message || 'The request was malformed';
} else if (error.status === 401) {
info.title = 'Unauthorized';
info.message = 'Authentication required';
} else if (error.status === 403) {
info.title = 'Forbidden';
info.message = 'You don\'t have permission to perform this action';
} else if (error.status === 404) {
info.title = 'Not Found';
info.message = 'The requested resource was not found';
} else if (error.status === 500) {
info.title = 'Server Error';
info.message = 'Internal server error occurred';
info.suggestions = [
'Try again in a few moments',
'Contact support if the problem persists'
];
} else if (error.status === 503) {
info.title = 'Service Unavailable';
info.message = 'The service is temporarily unavailable';
info.suggestions = [
'The server may be starting up - wait a moment',
'Check if required services (Ollama, PostgreSQL) are running'
];
}
}
return info;
};
const errorInfo = getErrorInfo(error);
const getIconForType = (type) => {
switch (type) {
case 'VALIDATION_ERROR':
return '⚠️';
case 'LLM_ERROR':
return '🤖';
case 'DATABASE_ERROR':
return '🗄️';
case 'SERVICE_ERROR':
return '⚙️';
default:
return '❌';
}
};
const getColorForType = (type) => {
switch (type) {
case 'VALIDATION_ERROR':
return { bg: '#fff3cd', border: '#ffeaa7', text: '#856404' };
case 'LLM_ERROR':
return { bg: '#e7f3ff', border: '#bee5eb', text: '#0c5460' };
case 'DATABASE_ERROR':
return { bg: '#fde2d1', border: '#f5c6cb', text: '#721c24' };
case 'SERVICE_ERROR':
return { bg: '#e2e3e5', border: '#ced4da', text: '#495057' };
default:
return { bg: '#f8d7da', border: '#f5c6cb', text: '#721c24' };
}
};
const colors = getColorForType(errorInfo.type);
return (
<div style={{
margin: '10px 0',
padding: '16px',
backgroundColor: colors.bg,
border: `2px solid ${colors.border}`,
borderRadius: '8px',
color: colors.text
}}>
{/* Header */}
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '12px'
}}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<span style={{ fontSize: '1.2rem', marginRight: '8px' }}>
{getIconForType(errorInfo.type)}
</span>
<strong style={{ fontSize: '1rem' }}>{errorInfo.title}</strong>
</div>
{onDismiss && (
<button
onClick={onDismiss}
style={{
background: 'none',
border: 'none',
fontSize: '1.2rem',
cursor: 'pointer',
color: colors.text,
opacity: 0.7
}}
>
✕
</button>
)}
</div>
{/* Message */}
<div style={{ marginBottom: '12px', lineHeight: '1.4' }}>
{errorInfo.message}
</div>
{/* Suggestions */}
{errorInfo.suggestions.length > 0 && (
<div style={{ marginBottom: '12px' }}>
<strong style={{ fontSize: '0.9rem' }}>Try this:</strong>
<ul style={{ marginTop: '6px', marginBottom: '0', paddingLeft: '20px' }}>
{errorInfo.suggestions.map((suggestion, index) => (
<li key={index} style={{ fontSize: '0.85rem', marginBottom: '4px' }}>
{suggestion}
</li>
))}
</ul>
</div>
)}
{/* Action Buttons */}
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
{onRetry && (
<button
onClick={onRetry}
style={{
padding: '6px 12px',
backgroundColor: colors.text,
color: colors.bg,
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.9rem',
fontWeight: 'bold'
}}
>
🔄 Try Again
</button>
)}
{showDetails && Object.keys(errorInfo.details).length > 0 && (
<details style={{ fontSize: '0.8em', marginTop: '8px' }}>
<summary style={{ cursor: 'pointer', fontWeight: 'bold' }}>
Show Details
</summary>
<pre style={{
marginTop: '6px',
padding: '8px',
backgroundColor: 'rgba(0,0,0,0.1)',
borderRadius: '4px',
fontSize: '0.75rem',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word'
}}>
{JSON.stringify({
error: error.message,
status: error.status,
details: errorInfo.details,
endpoint: error.endpoint
}, null, 2)}
</pre>
</details>
)}
</div>
</div>
);
};
// Error boundary component
export class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error boundary caught an error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<ErrorDisplay
error={this.state.error}
onRetry={() => {
this.setState({ hasError: false, error: null });
if (this.props.onRetry) {
this.props.onRetry();
}
}}
showDetails={true}
/>
);
}
return this.props.children;
}
}
export default ErrorDisplay;