Skip to content

Commit 5018736

Browse files
authored
Merge pull request #3973 from elizaOS/v2-connection-status
chore: simplify connection status
2 parents 1429872 + fd3432f commit 5018736

File tree

5 files changed

+29
-499
lines changed

5 files changed

+29
-499
lines changed

packages/cli/src/server/api/index.ts

+11
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,17 @@ export function createApiRouter(
307307
);
308308
});
309309

310+
// Check if the server is running
311+
router.get('/ping', (_req, res) => {
312+
res.setHeader('Content-Type', 'application/json');
313+
res.send(
314+
JSON.stringify({
315+
pong: true,
316+
timestamp: Date.now(),
317+
})
318+
);
319+
});
320+
310321
// Define plugin routes middleware function
311322
const handlePluginRoutes = (
312323
req: express.Request,

packages/client/src/components/connection-status.tsx

+16-143
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,44 @@
1-
import { STALE_TIMES } from '@/hooks/use-query-hooks';
21
import { apiClient } from '@/lib/api';
32
import { cn } from '@/lib/utils';
4-
import { useQuery, useQueryClient } from '@tanstack/react-query';
5-
import { Activity, RefreshCw } from 'lucide-react';
6-
import { useEffect, useRef, useState } from 'react';
3+
import { useQuery } from '@tanstack/react-query';
4+
import { RefreshCw } from 'lucide-react';
5+
import { useState } from 'react';
76
import { SidebarMenuButton, SidebarMenuItem } from './ui/sidebar';
8-
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
7+
import { Tooltip, TooltipTrigger } from './ui/tooltip';
98

109
export default function ConnectionStatus() {
11-
const [queryTime, setQueryTime] = useState<number | null>(null);
12-
const [isTabVisible, setIsTabVisible] = useState<boolean>(true);
13-
const [isOnline, setIsOnline] = useState<boolean>(
14-
typeof navigator !== 'undefined' ? navigator.onLine : true
15-
);
16-
const [isHovered, setIsHovered] = useState<boolean>(false);
17-
const queryClient = useQueryClient();
18-
19-
// Keep track of the last refresh time to limit window focus refetches
20-
const [lastRefreshTime, setLastRefreshTime] = useState<number>(Date.now());
21-
22-
// Track consecutive failures to implement exponential backoff
23-
const failureCountRef = useRef(0);
24-
const lastSuccessRef = useRef<number>(Date.now());
25-
26-
// Listen for tab visibility and network status changes
27-
useEffect(() => {
28-
const handleVisibilityChange = () => {
29-
setIsTabVisible(document.visibilityState === 'visible');
30-
31-
// When tab becomes visible again, reset failure count to retry immediately
32-
if (document.visibilityState === 'visible') {
33-
failureCountRef.current = 0;
34-
35-
// Force a refetch when tab becomes visible again if it's been a while
36-
const timeSinceLastRefresh = Date.now() - lastRefreshTime;
37-
if (timeSinceLastRefresh > 10000) {
38-
// 10 seconds
39-
queryClient.invalidateQueries({ queryKey: ['status'] });
40-
}
41-
}
42-
};
43-
44-
const handleOnlineStatusChange = () => {
45-
setIsOnline(navigator.onLine);
46-
47-
// When network comes back online, reset failure count
48-
if (navigator.onLine) {
49-
failureCountRef.current = 0;
50-
// Force a refetch when coming back online
51-
queryClient.invalidateQueries({ queryKey: ['status'] });
52-
}
53-
};
54-
55-
// Add event listeners
56-
document.addEventListener('visibilitychange', handleVisibilityChange);
57-
window.addEventListener('online', handleOnlineStatusChange);
58-
window.addEventListener('offline', handleOnlineStatusChange);
59-
60-
// Initial check
61-
setIsTabVisible(document.visibilityState === 'visible');
62-
63-
// Clean up listeners
64-
return () => {
65-
document.removeEventListener('visibilitychange', handleVisibilityChange);
66-
window.removeEventListener('online', handleOnlineStatusChange);
67-
window.removeEventListener('offline', handleOnlineStatusChange);
68-
};
69-
}, [queryClient, lastRefreshTime]);
70-
71-
// Dynamic poll interval based on tab visibility, online status, and failure history
72-
const getPollInterval = () => {
73-
if (!isOnline) return false; // Don't poll when offline
74-
75-
// If we've had consecutive failures, increase the polling interval exponentially
76-
if (failureCountRef.current > 0) {
77-
const backoffTime = Math.min(5000 * 2 ** (failureCountRef.current - 1), 60000);
78-
return backoffTime; // Up to 1 minute maximum backoff
79-
}
80-
81-
// More frequent polling
82-
if (!isTabVisible) return 15000; // 15s when tab is not visible (reduced from 20s)
83-
return 3000; // 3s when tab is visible (reduced from 5s)
84-
};
10+
const [isHovered, setIsHovered] = useState(false);
8511

8612
const query = useQuery({
87-
queryKey: ['status'],
13+
queryKey: ['ping'],
8814
queryFn: async () => {
89-
try {
90-
const start = performance.now();
91-
const data = await apiClient.getAgents();
92-
const end = performance.now();
93-
94-
// Reset failure count on success
95-
failureCountRef.current = 0;
96-
lastSuccessRef.current = Date.now();
97-
98-
setQueryTime(end - start);
99-
setLastRefreshTime(Date.now());
100-
return data;
101-
} catch (error) {
102-
// Increment failure count on error
103-
failureCountRef.current += 1;
104-
console.error('Connection check failed:', error);
105-
throw error;
106-
}
15+
return await apiClient.ping();
10716
},
108-
refetchInterval: getPollInterval(),
109-
retry: 3,
110-
retryDelay: (attemptIndex) => Math.min(1000 * 1.5 ** attemptIndex, 10000),
111-
// Ensure we refetch when the window becomes focused to quickly update status
112-
refetchOnWindowFocus: true,
113-
refetchOnReconnect: true,
114-
// Shorter stale time to ensure more frequent refresh
115-
staleTime: 500,
116-
// Increase cache time to prevent unnecessary refetches
117-
gcTime: STALE_TIMES.STANDARD,
17+
refetchInterval: 5000,
18+
retry: 2,
19+
staleTime: 1000,
11820
});
11921

120-
const connected = query?.isSuccess && !query?.isError;
121-
const isLoading = query?.isRefetching || query?.isPending;
122-
123-
// For long outages, show a different status
124-
const isLongOutage =
125-
connected === false &&
126-
failureCountRef.current > 5 &&
127-
Date.now() - lastSuccessRef.current > 60000; // 1 minute
22+
const connected = query.isSuccess && !query.isError;
23+
const isLoading = query.isRefetching || query.isPending;
12824

129-
// Show the connection status color with the network status
13025
const getStatusColor = () => {
131-
if (!isOnline) return 'bg-orange-600'; // Orange for offline
132-
if (isLongOutage) return 'bg-yellow-600'; // Yellow for long-term issues
13326
if (isLoading) return 'bg-muted-foreground';
13427
return connected ? 'bg-green-600' : 'bg-red-600';
13528
};
13629

137-
// Show the connection status text
13830
const getStatusText = () => {
139-
if (!isOnline) return 'Offline';
140-
if (isLongOutage) return 'Reconnecting...';
14131
if (isLoading) return 'Connecting...';
14232
return connected ? 'Connected' : 'Disconnected';
14333
};
14434

145-
// Get the text color based on status
14635
const getTextColor = () => {
147-
if (!isOnline) return 'text-orange-600';
148-
if (isLongOutage) return 'text-yellow-600';
14936
if (isLoading) return 'text-muted-foreground';
15037
return connected ? 'text-green-600' : 'text-red-600';
15138
};
15239

153-
// Function to refresh agents and status
154-
const refreshData = () => {
155-
// Reset failure tracking
156-
failureCountRef.current = 0;
157-
158-
// Force refetch even if within stale time
159-
queryClient.invalidateQueries({ queryKey: ['agents'] });
160-
queryClient.invalidateQueries({ queryKey: ['status'] });
40+
const refreshStatus = () => {
41+
query.refetch();
16142
};
16243

16344
return (
@@ -167,9 +48,9 @@ export default function ConnectionStatus() {
16748
<SidebarMenuButton
16849
onMouseEnter={() => setIsHovered(true)}
16950
onMouseLeave={() => setIsHovered(false)}
170-
onClick={refreshData}
51+
onClick={refreshStatus}
17152
>
172-
<div className="flex flex-col gap-1 select-none transition-all duration-200">
53+
<div className="flex flex-col gap-1 select-none">
17354
<div className="flex items-center gap-1">
17455
{isHovered ? (
17556
<RefreshCw className="h-3.5 w-3.5 text-muted-foreground animate-pulse" />
@@ -185,14 +66,6 @@ export default function ConnectionStatus() {
18566
</div>
18667
</SidebarMenuButton>
18768
</TooltipTrigger>
188-
{connected && !isHovered ? (
189-
<TooltipContent side="top">
190-
<div className="flex items-center gap-1">
191-
<Activity className="size-4" />
192-
<span>{queryTime?.toFixed(2)} ms</span>
193-
</div>
194-
</TooltipContent>
195-
) : null}
19669
</Tooltip>
19770
</SidebarMenuItem>
19871
);

packages/client/src/lib/api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ interface AgentLog {
183183
export const apiClient = {
184184
getAgents: () => fetcher({ url: '/agents' }),
185185
getAgent: (agentId: string): Promise<{ data: Agent }> => fetcher({ url: `/agents/${agentId}` }),
186+
ping: (): Promise<{ pong: boolean; timestamp: number }> => fetcher({ url: '/ping' }),
186187
tts: (agentId: string, text: string) =>
187188
fetcher({
188189
url: `/agents/${agentId}/speech/generate`,

packages/client/src/lib/socketio-manager.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const BASE_URL = `http://localhost:${import.meta.env.VITE_SERVER_PORT}`;
1414
class SocketIOManager extends EventEmitter {
1515
private static instance: SocketIOManager | null = null;
1616
private socket: Socket | null = null;
17-
private isConnected: boolean = false;
17+
private isConnected = false;
1818
private connectPromise: Promise<void> | null = null;
1919
private resolveConnect: (() => void) | null = null;
2020
private activeRooms: Set<string> = new Set();

0 commit comments

Comments
 (0)