AI Analysis and Content Strategy fixes. Enhanced Strategy Routes refactoring.
This commit is contained in:
@@ -35,49 +35,113 @@ export const CopilotKitHealthProvider: React.FC<CopilotKitHealthProviderProps> =
|
||||
children,
|
||||
initialHealthStatus = true,
|
||||
}) => {
|
||||
// Persist health status across page reloads to prevent manual form from disappearing
|
||||
const getInitialHealthStatus = (): boolean => {
|
||||
if (typeof window === 'undefined') return initialHealthStatus;
|
||||
try {
|
||||
const saved = localStorage.getItem('copilotkit_health_status');
|
||||
if (saved !== null) {
|
||||
return saved === 'true';
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[CopilotKitHealthContext] Failed to read persisted health status:', e);
|
||||
}
|
||||
return initialHealthStatus;
|
||||
};
|
||||
|
||||
const [state, setState] = useState<CopilotKitHealthState>({
|
||||
isHealthy: initialHealthStatus,
|
||||
isHealthy: getInitialHealthStatus(),
|
||||
isChecking: false,
|
||||
lastChecked: null,
|
||||
errorMessage: null,
|
||||
retryCount: 0,
|
||||
isAvailable: initialHealthStatus,
|
||||
isAvailable: getInitialHealthStatus(),
|
||||
});
|
||||
|
||||
const markHealthy = useCallback(() => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isHealthy: true,
|
||||
isAvailable: true,
|
||||
errorMessage: null,
|
||||
retryCount: 0,
|
||||
lastChecked: new Date(),
|
||||
}));
|
||||
setState((prev) => {
|
||||
const newState = {
|
||||
...prev,
|
||||
isHealthy: true,
|
||||
isAvailable: true,
|
||||
errorMessage: null,
|
||||
retryCount: 0,
|
||||
lastChecked: new Date(),
|
||||
};
|
||||
// Persist health status to localStorage
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('copilotkit_health_status', 'true');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[CopilotKitHealthContext] Failed to persist health status:', e);
|
||||
}
|
||||
return newState;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const markUnhealthy = useCallback((errorMessage?: string) => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isHealthy: false,
|
||||
isAvailable: false,
|
||||
errorMessage: errorMessage || 'CopilotKit is unavailable',
|
||||
lastChecked: new Date(),
|
||||
retryCount: prev.retryCount + 1,
|
||||
}));
|
||||
setState((prev) => {
|
||||
const newState = {
|
||||
...prev,
|
||||
isHealthy: false,
|
||||
isAvailable: false,
|
||||
errorMessage: errorMessage || 'CopilotKit is unavailable',
|
||||
lastChecked: new Date(),
|
||||
retryCount: prev.retryCount + 1,
|
||||
};
|
||||
// Persist health status to localStorage
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('copilotkit_health_status', 'false');
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[CopilotKitHealthContext] Failed to persist health status:', e);
|
||||
}
|
||||
return newState;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Listen for CopilotKit error events from App.tsx
|
||||
React.useEffect(() => {
|
||||
const handleCopilotKitError = (event: Event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
const { errorMessage, isFatal } = customEvent.detail || {};
|
||||
const { errorMessage, isFatal, error } = customEvent.detail || {};
|
||||
|
||||
// Always mark as unhealthy for fatal errors (CORS, SSL, 403, etc.)
|
||||
if (isFatal) {
|
||||
console.warn('[CopilotKitHealthContext] Fatal CopilotKit error detected:', errorMessage);
|
||||
markUnhealthy(errorMessage || 'CopilotKit fatal error');
|
||||
} else {
|
||||
// For transient errors, just log but don't mark as unhealthy immediately
|
||||
// Let the health check determine if it's truly down
|
||||
console.warn('CopilotKit transient error:', errorMessage);
|
||||
// Check error details for CORS/network errors even if not marked as fatal
|
||||
// Safely check error strings to avoid "Cannot read properties of undefined" errors
|
||||
const errorMsg = error?.message || '';
|
||||
const errorMsgStr = typeof errorMsg === 'string' ? errorMsg.toLowerCase() : String(errorMsg || '').toLowerCase();
|
||||
const messageStr = typeof errorMessage === 'string' ? errorMessage.toLowerCase() : String(errorMessage || '').toLowerCase();
|
||||
const errorStr = errorMsgStr || messageStr || '';
|
||||
|
||||
if (errorStr && (
|
||||
errorStr.includes('cors') ||
|
||||
errorStr.includes('failed to fetch') ||
|
||||
errorStr.includes('networkerror') ||
|
||||
errorStr.includes('network') ||
|
||||
errorStr.includes('cannot read properties')
|
||||
)) {
|
||||
console.warn('[CopilotKitHealthContext] CORS/network error detected:', errorMessage);
|
||||
markUnhealthy(errorMessage || 'CopilotKit network error');
|
||||
} else if (error?.response?.status === 504 || error?.response?.status === 502 || error?.response?.status === 500) {
|
||||
// Gateway timeout, bad gateway, or server error - mark as unavailable
|
||||
console.warn('[CopilotKitHealthContext] Gateway/server error detected:', errorMessage);
|
||||
markUnhealthy(errorMessage || 'CopilotKit gateway error');
|
||||
} else if (error?.error?.statusCode === 500 || error?.error?.code === 'UNKNOWN') {
|
||||
// Internal CopilotKit errors - mark as unavailable
|
||||
console.warn('[CopilotKitHealthContext] CopilotKit internal error detected:', errorMessage);
|
||||
markUnhealthy(errorMessage || 'CopilotKit internal error');
|
||||
} else {
|
||||
// For other transient errors, just log but don't mark as unhealthy immediately
|
||||
// Let the health check determine if it's truly down
|
||||
console.warn('[CopilotKitHealthContext] Transient CopilotKit error:', errorMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -112,41 +176,62 @@ export const CopilotKitHealthProvider: React.FC<CopilotKitHealthProviderProps> =
|
||||
|
||||
// Try to check CopilotKit status endpoint
|
||||
// This is a lightweight check that doesn't require full CopilotKit initialization
|
||||
const response = await fetch('https://api.cloud.copilotkit.ai/ciu', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-copilotcloud-public-api-key': apiKey.trim(),
|
||||
},
|
||||
// Use a short timeout to avoid blocking
|
||||
signal: AbortSignal.timeout(3000),
|
||||
});
|
||||
// Use AbortController for timeout (more compatible than AbortSignal.timeout)
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.cloud.copilotkit.ai/ciu', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-copilotcloud-public-api-key': apiKey.trim(),
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (response.ok) {
|
||||
markHealthy();
|
||||
} else {
|
||||
// Provide more specific error messages based on status code
|
||||
if (response.status === 401) {
|
||||
markUnhealthy('CopilotKit API key is invalid or unauthorized');
|
||||
if (response.ok) {
|
||||
markHealthy();
|
||||
} else {
|
||||
markUnhealthy(`CopilotKit status check failed: ${response.status}`);
|
||||
// Provide more specific error messages based on status code
|
||||
if (response.status === 401) {
|
||||
markUnhealthy('CopilotKit API key is invalid or unauthorized');
|
||||
} else if (response.status === 429) {
|
||||
markUnhealthy('CopilotKit rate limit exceeded');
|
||||
} else if (response.status >= 500) {
|
||||
markUnhealthy(`CopilotKit server error: ${response.status}`);
|
||||
} else {
|
||||
markUnhealthy(`CopilotKit status check failed: ${response.status}`);
|
||||
}
|
||||
}
|
||||
} catch (fetchError: any) {
|
||||
clearTimeout(timeoutId);
|
||||
throw fetchError;
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Handle various error types
|
||||
let errorMsg = 'CopilotKit health check failed';
|
||||
let isCorsError = false;
|
||||
|
||||
if (error.name === 'AbortError' || error.name === 'TimeoutError') {
|
||||
errorMsg = 'CopilotKit health check timed out';
|
||||
} else if (error.message?.includes('CORS')) {
|
||||
} else if (error.message?.includes('CORS') || error.message?.includes('cors')) {
|
||||
errorMsg = 'CopilotKit CORS error - service may be unavailable';
|
||||
isCorsError = true;
|
||||
} else if (error.message?.includes('Failed to fetch') || error.message?.includes('NetworkError')) {
|
||||
// Failed to fetch often indicates CORS or network issues
|
||||
errorMsg = 'CopilotKit network error - service may be down or blocked';
|
||||
isCorsError = true; // Treat as potentially unavailable
|
||||
} else if (error.message?.includes('certificate') || error.message?.includes('SSL')) {
|
||||
errorMsg = 'CopilotKit SSL certificate error';
|
||||
} else if (error.message?.includes('network') || error.message?.includes('Failed to fetch')) {
|
||||
} else if (error.message?.includes('network') || error.message?.includes('Network')) {
|
||||
errorMsg = 'CopilotKit network error - service may be down';
|
||||
} else {
|
||||
errorMsg = error.message || 'Unknown error checking CopilotKit health';
|
||||
}
|
||||
|
||||
console.warn('[CopilotKitHealthContext] Health check failed:', errorMsg, error);
|
||||
markUnhealthy(errorMsg);
|
||||
} finally {
|
||||
setState((prev) => ({ ...prev, isChecking: false }));
|
||||
|
||||
Reference in New Issue
Block a user