AI Analysis and Content Strategy fixes. Enhanced Strategy Routes refactoring.

This commit is contained in:
ajaysi
2026-01-10 19:32:50 +05:30
parent 0b63ae7fc1
commit 8193cdba67
298 changed files with 45678 additions and 10952 deletions

View File

@@ -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 }));

View File

@@ -96,9 +96,40 @@ export const SubscriptionProvider: React.FC<SubscriptionProviderProps> = ({ chil
return;
}
// Wait a moment to ensure auth token getter is installed
// This prevents 401 errors during app initialization
await new Promise(resolve => setTimeout(resolve, 200));
// Wait for authentication to be ready
// The apiClient interceptor needs authTokenGetter to be set by TokenInstaller
// Wait up to 2 seconds for token getter to be installed (TokenInstaller runs in App.tsx)
let authReady = false;
let attempts = 0;
const maxAttempts = 20; // 20 * 100ms = 2 seconds max wait
while (attempts < maxAttempts && !authReady) {
// Wait for TokenInstaller to set the authTokenGetter in api/client.ts
await new Promise(resolve => setTimeout(resolve, 100));
// Check if user_id exists (indicates user is authenticated)
const storedUserId = localStorage.getItem('user_id');
if (storedUserId && storedUserId !== 'anonymous') {
// After a few attempts, assume token getter should be ready
// The apiClient interceptor will add the token if authTokenGetter is set
if (attempts >= 5) { // After 500ms, proceed with the request
authReady = true;
break;
}
} else {
// No user_id means user is not authenticated, exit early
console.log('SubscriptionContext: No user_id found, user not authenticated');
setLoading(false);
return;
}
attempts++;
}
if (!authReady) {
console.warn('SubscriptionContext: Auth token getter may not be ready, but proceeding with request. apiClient will handle 401 gracefully.');
// Continue anyway - apiClient interceptor will handle missing token gracefully
}
console.log('SubscriptionContext: Checking subscription for user:', userId);
const response = await apiClient.get(`/api/subscription/status/${userId}`);
@@ -304,12 +335,25 @@ export const SubscriptionProvider: React.FC<SubscriptionProviderProps> = ({ chil
// Check if it's a subscription-related error
const status = error.response?.status;
console.log('SubscriptionContext: globalSubscriptionErrorHandler called', {
status,
hasResponse: !!error.response,
dataKeys: error.response?.data ? Object.keys(error.response.data) : null,
data: error.response?.data
});
if (status === 429 || status === 402) {
const now = Date.now();
// Check if this is a usage limit error (status 429) vs subscription expired (402)
let errorData = error.response?.data || {};
console.log('SubscriptionContext: Processing subscription error', {
originalErrorData: errorData,
isArray: Array.isArray(errorData),
hasDetail: errorData.detail !== undefined
});
// If errorData is an array, extract the first element (common FastAPI response format)
if (Array.isArray(errorData)) {
errorData = errorData[0] || {};
@@ -317,10 +361,18 @@ export const SubscriptionProvider: React.FC<SubscriptionProviderProps> = ({ chil
// CRITICAL: FastAPI wraps HTTPException detail in a 'detail' field
// If errorData has a 'detail' field, extract it (this is the actual error data)
// BUT: JSONResponse returns data directly, not wrapped in 'detail'
if (errorData.detail && typeof errorData.detail === 'object') {
errorData = errorData.detail;
}
console.log('SubscriptionContext: Processed errorData', {
errorData,
hasUsageInfo: !!errorData.usage_info,
provider: errorData.provider,
message: errorData.message
});
// Check for usage_info in various possible locations (now that we've unwrapped FastAPI detail)
const usageInfo = errorData.usage_info ||
(errorData.current_calls !== undefined ? errorData : null) ||