diff --git a/frontend/src/components/debug/ApiStatusGroupMonitor.tsx b/frontend/src/components/debug/ApiStatusGroupMonitor.tsx
new file mode 100644
index 00000000..bd59e826
--- /dev/null
+++ b/frontend/src/components/debug/ApiStatusGroupMonitor.tsx
@@ -0,0 +1,270 @@
+import { useState, useEffect, useCallback } from 'react';
+import { useResourceDebug } from '../../hooks/useResourceDebug';
+
+export interface ApiEndpoint {
+ name: string;
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
+ endpoint: string;
+ testData?: any;
+}
+
+interface ApiStatus {
+ endpoint: string;
+ status: 'pending' | 'success' | 'error';
+ responseTime?: number;
+ error?: string;
+ lastChecked?: Date;
+}
+
+interface ApiStatusGroupMonitorProps {
+ groupName: string;
+ endpoints: ApiEndpoint[];
+ baseUrl?: string;
+ onStatusChange?: (status: 'pending' | 'success' | 'error') => void;
+}
+
+export function ApiStatusGroupMonitor({
+ groupName,
+ endpoints,
+ baseUrl,
+ onStatusChange
+}: ApiStatusGroupMonitorProps) {
+ const debugEnabled = useResourceDebug();
+ const [overallStatus, setOverallStatus] = useState<'pending' | 'success' | 'error'>('pending');
+
+ const checkAllApis = useCallback(async () => {
+ if (!debugEnabled) {
+ setOverallStatus('pending');
+ if (onStatusChange) {
+ onStatusChange('pending');
+ }
+ return;
+ }
+
+ // Set pending while checking
+ setOverallStatus('pending');
+ if (onStatusChange) {
+ onStatusChange('pending');
+ }
+
+ try {
+ const API_BASE_URL = baseUrl || (import.meta.env.VITE_BACKEND_URL || 'https://api.igny8.com/api');
+ const statuses: ApiStatus[] = [];
+
+ // Check all endpoints in parallel with timeout
+ const checkPromises = endpoints.map(async (endpoint) => {
+ try {
+ const startTime = Date.now();
+
+ // Get auth token
+ const authStorage = localStorage.getItem('auth-storage');
+ const token = authStorage ? JSON.parse(authStorage)?.state?.token : null;
+
+ const headers: HeadersInit = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ // Create abort controller for timeout
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
+
+ const options: RequestInit = {
+ method: endpoint.method,
+ headers,
+ credentials: 'include',
+ signal: controller.signal,
+ };
+
+ // Only add body for methods that support it
+ if (['POST', 'PUT', 'PATCH'].includes(endpoint.method) && endpoint.testData) {
+ options.body = JSON.stringify(endpoint.testData);
+ }
+
+ try {
+ const response = await fetch(`${API_BASE_URL}${endpoint.endpoint}`, options);
+ clearTimeout(timeoutId);
+
+ return {
+ endpoint: endpoint.endpoint,
+ status: response.ok ? 'success' : 'error',
+ responseTime: Date.now() - startTime,
+ error: response.ok ? undefined : `HTTP ${response.status}`,
+ lastChecked: new Date(),
+ } as ApiStatus;
+ } catch (fetchError: any) {
+ clearTimeout(timeoutId);
+ // Handle timeout and other errors gracefully
+ return {
+ endpoint: endpoint.endpoint,
+ status: 'error' as const,
+ error: fetchError.name === 'AbortError' ? 'Timeout' : (fetchError.message || 'Network error'),
+ lastChecked: new Date(),
+ } as ApiStatus;
+ }
+ } catch (error: any) {
+ // Fallback error handling
+ return {
+ endpoint: endpoint.endpoint,
+ status: 'error' as const,
+ error: error.message || 'Check failed',
+ lastChecked: new Date(),
+ } as ApiStatus;
+ }
+ });
+
+ const results = await Promise.allSettled(checkPromises);
+ results.forEach((result) => {
+ if (result.status === 'fulfilled') {
+ statuses.push(result.value);
+ } else {
+ statuses.push({
+ endpoint: 'unknown',
+ status: 'error' as const,
+ error: result.reason?.message || 'Check failed',
+ lastChecked: new Date(),
+ });
+ }
+ });
+
+ // Determine overall status
+ const hasError = statuses.some(s => s.status === 'error');
+ const allSuccess = statuses.length > 0 && statuses.every(s => s.status === 'success');
+
+ let newStatus: 'pending' | 'success' | 'error';
+ if (hasError) {
+ newStatus = 'error';
+ } else if (allSuccess) {
+ newStatus = 'success';
+ } else {
+ newStatus = 'pending';
+ }
+
+ setOverallStatus(newStatus);
+ if (onStatusChange) {
+ onStatusChange(newStatus);
+ }
+ } catch (error) {
+ // If something goes wrong, set to error but don't block
+ console.error('API status check error:', error);
+ setOverallStatus('error');
+ if (onStatusChange) {
+ onStatusChange('error');
+ }
+ }
+ }, [debugEnabled, endpoints, baseUrl, onStatusChange]);
+
+ useEffect(() => {
+ if (!debugEnabled) {
+ setOverallStatus('pending');
+ if (onStatusChange) {
+ onStatusChange('pending');
+ }
+ return;
+ }
+
+ // Initial check with delay to avoid blocking page load
+ const initialTimeout = setTimeout(() => {
+ checkAllApis();
+ }, 1000);
+
+ // Set up interval to check every 1 minute (60 seconds)
+ const interval = setInterval(() => {
+ checkAllApis();
+ }, 60000);
+
+ return () => {
+ clearTimeout(initialTimeout);
+ clearInterval(interval);
+ };
+ }, [debugEnabled, checkAllApis, onStatusChange]);
+
+ // Don't render anything - this component just tracks status
+ return null;
+}
+
+// Sidebar widget component
+interface ApiStatusSidebarWidgetProps {
+ authStatus: 'pending' | 'success' | 'error';
+ systemStatus: 'pending' | 'success' | 'error';
+ billingStatus: 'pending' | 'success' | 'error';
+ isExpanded?: boolean;
+ isHovered?: boolean;
+ isMobileOpen?: boolean;
+}
+
+export function ApiStatusSidebarWidget({
+ authStatus,
+ systemStatus,
+ billingStatus,
+ isExpanded = true,
+ isHovered = false,
+ isMobileOpen = false
+}: ApiStatusSidebarWidgetProps) {
+ const debugEnabled = useResourceDebug();
+
+ if (!debugEnabled) {
+ return null;
+ }
+
+ const showLabels = isExpanded || isHovered || isMobileOpen;
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'success':
+ return 'bg-green-500 dark:bg-green-400';
+ case 'error':
+ return 'bg-red-500 dark:bg-red-400';
+ case 'pending':
+ return 'bg-yellow-500 dark:bg-yellow-400';
+ default:
+ return 'bg-gray-400 dark:bg-gray-500';
+ }
+ };
+
+ const getStatusTitle = (status: string, group: string) => {
+ switch (status) {
+ case 'success':
+ return `${group} APIs: All healthy`;
+ case 'error':
+ return `${group} APIs: Some errors detected`;
+ case 'pending':
+ return `${group} APIs: Checking...`;
+ default:
+ return `${group} APIs: Unknown status`;
+ }
+ };
+
+ return (
+
+ {showLabels && (
+
API Status
+ )}
+
+ {showLabels && (
+
+ A
+ S
+ B
+
+ )}
+
+ );
+}
+
diff --git a/frontend/src/components/debug/ApiStatusMonitor.tsx b/frontend/src/components/debug/ApiStatusMonitor.tsx
new file mode 100644
index 00000000..f7fdfe2b
--- /dev/null
+++ b/frontend/src/components/debug/ApiStatusMonitor.tsx
@@ -0,0 +1,189 @@
+import { useState, useEffect } from 'react';
+import { useResourceDebug } from '../../hooks/useResourceDebug';
+import { Card } from '../ui/card';
+import Badge from '../ui/badge/Badge';
+
+export interface ApiEndpoint {
+ name: string;
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
+ endpoint: string;
+ testData?: any;
+}
+
+interface ApiStatusMonitorProps {
+ title: string;
+ endpoints: ApiEndpoint[];
+ baseUrl?: string;
+}
+
+interface ApiStatus {
+ endpoint: string;
+ status: 'pending' | 'success' | 'error';
+ responseTime?: number;
+ error?: string;
+ lastChecked?: Date;
+}
+
+export default function ApiStatusMonitor({ title, endpoints, baseUrl }: ApiStatusMonitorProps) {
+ const debugEnabled = useResourceDebug();
+ const [statuses, setStatuses] = useState>({});
+ const [isChecking, setIsChecking] = useState(false);
+
+ // Only make requests if debug is enabled
+ useEffect(() => {
+ if (!debugEnabled) {
+ // Clear statuses when debug is disabled
+ setStatuses({});
+ return;
+ }
+
+ // Initial check
+ checkAllApis();
+
+ // Set up interval to check every 30 seconds
+ const interval = setInterval(() => {
+ checkAllApis();
+ }, 30000);
+
+ return () => clearInterval(interval);
+ }, [debugEnabled, endpoints]);
+
+ const checkAllApis = async () => {
+ if (!debugEnabled) return;
+
+ setIsChecking(true);
+ const API_BASE_URL = baseUrl || (import.meta.env.VITE_BACKEND_URL || 'https://api.igny8.com/api');
+
+ const newStatuses: Record = {};
+
+ for (const endpoint of endpoints) {
+ const key = `${endpoint.method}:${endpoint.endpoint}`;
+
+ try {
+ const startTime = Date.now();
+
+ // Get auth token
+ const authStorage = localStorage.getItem('auth-storage');
+ const token = authStorage ? JSON.parse(authStorage)?.state?.token : null;
+
+ const headers: HeadersInit = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
+ }
+
+ const options: RequestInit = {
+ method: endpoint.method,
+ headers,
+ credentials: 'include',
+ };
+
+ // Only add body for methods that support it
+ if (['POST', 'PUT', 'PATCH'].includes(endpoint.method) && endpoint.testData) {
+ options.body = JSON.stringify(endpoint.testData);
+ }
+
+ const response = await fetch(`${API_BASE_URL}${endpoint.endpoint}`, options);
+ const responseTime = Date.now() - startTime;
+
+ newStatuses[key] = {
+ endpoint: endpoint.endpoint,
+ status: response.ok ? 'success' : 'error',
+ responseTime,
+ error: response.ok ? undefined : `HTTP ${response.status}`,
+ lastChecked: new Date(),
+ };
+ } catch (error: any) {
+ newStatuses[key] = {
+ endpoint: endpoint.endpoint,
+ status: 'error',
+ error: error.message || 'Network error',
+ lastChecked: new Date(),
+ };
+ }
+ }
+
+ setStatuses(newStatuses);
+ setIsChecking(false);
+ };
+
+ // Don't render anything if debug is disabled
+ if (!debugEnabled) {
+ return null;
+ }
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'success':
+ return 'success';
+ case 'error':
+ return 'error';
+ case 'pending':
+ return 'warning';
+ default:
+ return 'primary';
+ }
+ };
+
+ return (
+
+
+
+ {title} API Status Monitor
+
+
+ {isChecking ? 'Checking...' : 'Active'}
+
+
+
+
+ {endpoints.map((endpoint) => {
+ const key = `${endpoint.method}:${endpoint.endpoint}`;
+ const status = statuses[key] || { status: 'pending' as const };
+
+ return (
+
+
+
+
+ {endpoint.method}
+
+
+ {endpoint.endpoint}
+
+
+ {status.responseTime && (
+
+ {status.responseTime}ms
+
+ )}
+ {status.error && (
+
+ {status.error}
+
+ )}
+
+
+
+ {status.status === 'pending' ? '⏳' : status.status === 'success' ? '✓' : '✗'}
+
+
+
+ );
+ })}
+
+
+ {Object.keys(statuses).length > 0 && (
+
+ Last checked: {statuses[Object.keys(statuses)[0]]?.lastChecked?.toLocaleTimeString()}
+
+ )}
+
+ );
+}
+
diff --git a/frontend/src/layout/AppSidebar.tsx b/frontend/src/layout/AppSidebar.tsx
index 709d06d4..14cb8ffb 100644
--- a/frontend/src/layout/AppSidebar.tsx
+++ b/frontend/src/layout/AppSidebar.tsx
@@ -20,6 +20,7 @@ import { useSidebar } from "../context/SidebarContext";
import SidebarWidget from "./SidebarWidget";
import { APP_VERSION } from "../config/version";
import { useAuthStore } from "../store/authStore";
+import { ApiStatusSidebarWidget, ApiStatusGroupMonitor } from "../components/debug/ApiStatusGroupMonitor";
type NavItem = {
name: string;
@@ -52,6 +53,11 @@ const AppSidebar: React.FC = () => {
{}
);
const subMenuRefs = useRef>({});
+
+ // API Status states for groups
+ const [authStatus, setAuthStatus] = useState<'pending' | 'success' | 'error'>('pending');
+ const [systemStatus, setSystemStatus] = useState<'pending' | 'success' | 'error'>('pending');
+ const [billingStatus, setBillingStatus] = useState<'pending' | 'success' | 'error'>('pending');
const isActive = useCallback(
(path: string) => location.pathname === path,
@@ -494,8 +500,52 @@ const AppSidebar: React.FC = () => {
)}
+ {/* API Status Monitors - Hidden components that track status */}
+
+
+
+
)}
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
);
}
diff --git a/frontend/src/pages/Billing/Usage.tsx b/frontend/src/pages/Billing/Usage.tsx
index 64213568..2132d934 100644
--- a/frontend/src/pages/Billing/Usage.tsx
+++ b/frontend/src/pages/Billing/Usage.tsx
@@ -4,6 +4,7 @@ import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchCreditUsage, CreditUsageLog, fetchUsageLimits, LimitCard } from '../../services/api';
import { Card } from '../../components/ui/card';
import Badge from '../../components/ui/badge/Badge';
+import ApiStatusMonitor from '../../components/debug/ApiStatusMonitor';
export default function Usage() {
const toast = useToast();
@@ -211,6 +212,15 @@ export default function Usage() {
)}
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
);
}
diff --git a/frontend/src/pages/Settings/AI.tsx b/frontend/src/pages/Settings/AI.tsx
index 1cffd29c..c75ac984 100644
--- a/frontend/src/pages/Settings/AI.tsx
+++ b/frontend/src/pages/Settings/AI.tsx
@@ -3,6 +3,7 @@ import PageMeta from '../../components/common/PageMeta';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import { Card } from '../../components/ui/card';
+import ApiStatusMonitor from '../../components/debug/ApiStatusMonitor';
export default function AISettings() {
const toast = useToast();
@@ -42,6 +43,14 @@ export default function AISettings() {
AI settings management interface coming soon.
)}
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
);
}
diff --git a/frontend/src/pages/Settings/Account.tsx b/frontend/src/pages/Settings/Account.tsx
index 6f3eaedf..e7c7fa51 100644
--- a/frontend/src/pages/Settings/Account.tsx
+++ b/frontend/src/pages/Settings/Account.tsx
@@ -3,6 +3,7 @@ import PageMeta from '../../components/common/PageMeta';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import { Card } from '../../components/ui/card';
+import ApiStatusMonitor from '../../components/debug/ApiStatusMonitor';
export default function AccountSettings() {
const toast = useToast();
@@ -42,6 +43,14 @@ export default function AccountSettings() {
Account settings management interface coming soon.
)}
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
);
}
diff --git a/frontend/src/pages/Settings/Integration.tsx b/frontend/src/pages/Settings/Integration.tsx
index 3a151cef..caba5400 100644
--- a/frontend/src/pages/Settings/Integration.tsx
+++ b/frontend/src/pages/Settings/Integration.tsx
@@ -14,6 +14,7 @@ import SelectDropdown from '../../components/form/SelectDropdown';
import { useToast } from '../../components/ui/toast/ToastContainer';
import Alert from '../../components/ui/alert/Alert';
import { fetchAPI } from '../../services/api';
+import ApiStatusMonitor from '../../components/debug/ApiStatusMonitor';
// OpenAI Icon SVG
const OpenAIIcon = () => (
@@ -1165,6 +1166,16 @@ export default function Integration() {
/>
>
)}
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
>
);
}
diff --git a/frontend/src/pages/Settings/Sites.tsx b/frontend/src/pages/Settings/Sites.tsx
index 0c8efd7f..42aae5e7 100644
--- a/frontend/src/pages/Settings/Sites.tsx
+++ b/frontend/src/pages/Settings/Sites.tsx
@@ -19,6 +19,7 @@ import {
Sector,
} from '../../services/api';
import Badge from '../../components/ui/badge/Badge';
+import ApiStatusMonitor from '../../components/debug/ApiStatusMonitor';
// Site Icon SVG
const SiteIcon = () => (
@@ -591,6 +592,16 @@ export default function Sites() {
/>
)}
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
>
);
}
diff --git a/frontend/src/pages/Settings/Status.tsx b/frontend/src/pages/Settings/Status.tsx
index db5e511a..62d06abf 100644
--- a/frontend/src/pages/Settings/Status.tsx
+++ b/frontend/src/pages/Settings/Status.tsx
@@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
import PageMeta from "../../components/common/PageMeta";
import ComponentCard from "../../components/common/ComponentCard";
import { fetchAPI } from "../../services/api";
+import ApiStatusMonitor from "../../components/debug/ApiStatusMonitor";
interface SystemStatus {
timestamp: string;
@@ -318,6 +319,14 @@ export default function Status() {
Last updated: {new Date(status.timestamp).toLocaleString()}
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
>
);
}
diff --git a/frontend/src/pages/Settings/System.tsx b/frontend/src/pages/Settings/System.tsx
index 1254a55f..3a9992fa 100644
--- a/frontend/src/pages/Settings/System.tsx
+++ b/frontend/src/pages/Settings/System.tsx
@@ -3,6 +3,7 @@ import PageMeta from '../../components/common/PageMeta';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
import { Card } from '../../components/ui/card';
+import ApiStatusMonitor from '../../components/debug/ApiStatusMonitor';
export default function SystemSettings() {
const toast = useToast();
@@ -42,6 +43,14 @@ export default function SystemSettings() {
System settings management interface coming soon.
)}
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
);
}
diff --git a/frontend/src/pages/Thinker/AuthorProfiles.tsx b/frontend/src/pages/Thinker/AuthorProfiles.tsx
index db943097..0ec4a76f 100644
--- a/frontend/src/pages/Thinker/AuthorProfiles.tsx
+++ b/frontend/src/pages/Thinker/AuthorProfiles.tsx
@@ -7,6 +7,7 @@ import Button from '../../components/ui/button/Button';
import FormModal, { FormField } from '../../components/common/FormModal';
import Badge from '../../components/ui/badge/Badge';
import { PlusIcon } from '../../icons';
+import ApiStatusMonitor from '../../components/debug/ApiStatusMonitor';
export default function AuthorProfiles() {
const toast = useToast();
@@ -158,6 +159,14 @@ export default function AuthorProfiles() {
data={formData}
onChange={setFormData}
/>
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
);
}
diff --git a/frontend/src/pages/Thinker/Prompts.tsx b/frontend/src/pages/Thinker/Prompts.tsx
index a3f8555a..7a1f8ec1 100644
--- a/frontend/src/pages/Thinker/Prompts.tsx
+++ b/frontend/src/pages/Thinker/Prompts.tsx
@@ -5,6 +5,7 @@ import TextArea from '../../components/form/input/TextArea';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { BoltIcon } from '../../icons';
import { fetchAPI } from '../../services/api';
+import ApiStatusMonitor from '../../components/debug/ApiStatusMonitor';
interface PromptData {
prompt_type: string;
@@ -427,6 +428,14 @@ export default function Prompts() {
+
+ {/* API Status Monitor - Only shows when debug toggle is enabled */}
+
>
);
}