cesjavi commited on
Commit
93d283b
·
1 Parent(s): 2747836

UI: Optimized agent log polling (Reduced terminal noise)

Browse files
frontend/src/components/AgentConsole.tsx CHANGED
@@ -1,6 +1,7 @@
1
  import React, { useEffect, useState, useRef } from 'react';
2
  import { Terminal } from 'lucide-react';
3
  import { supabase } from '../services/supabase';
 
4
 
5
  interface LogEntry {
6
  id: string;
@@ -10,12 +11,24 @@ interface LogEntry {
10
  task_id: string | null;
11
  }
12
 
13
- const AgentConsole: React.FC = () => {
 
 
 
 
 
14
  const [logs, setLogs] = useState<LogEntry[]>([]);
15
  const [error, setError] = useState<string | null>(null);
16
  const scrollRef = useRef<HTMLDivElement>(null);
17
 
18
  useEffect(() => {
 
 
 
 
 
 
 
19
  const fetchLogs = async () => {
20
  const { data, error: supabaseError } = await supabase
21
  .from('agent_logs')
@@ -35,28 +48,75 @@ const AgentConsole: React.FC = () => {
35
  }
36
  };
37
 
38
- fetchLogs();
39
-
40
- // Fallback polling every 3 seconds
41
- const pollInterval = setInterval(fetchLogs, 3000);
42
-
43
- // Set up real-time subscription
44
- const channel = supabase
45
- .channel('agent_logs_changes')
46
- .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'agent_logs' }, (payload) => {
47
- setLogs(prev => {
48
- const newLog = payload.new as LogEntry;
49
- if (prev.some(l => l.id === newLog.id)) return prev;
50
- return [...prev, newLog].slice(-50);
51
- });
52
- })
53
- .subscribe();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  return () => {
56
- clearInterval(pollInterval);
57
- supabase.removeChannel(channel);
 
 
58
  };
59
- }, []);
60
 
61
  useEffect(() => {
62
  if (scrollRef.current) {
 
1
  import React, { useEffect, useState, useRef } from 'react';
2
  import { Terminal } from 'lucide-react';
3
  import { supabase } from '../services/supabase';
4
+ import { getApiUrl } from '../services/runtimeConfig';
5
 
6
  interface LogEntry {
7
  id: string;
 
11
  task_id: string | null;
12
  }
13
 
14
+ interface AgentConsoleProps {
15
+ projectId?: string | null;
16
+ taskId?: string | null;
17
+ }
18
+
19
+ const AgentConsole: React.FC<AgentConsoleProps> = ({ projectId, taskId }) => {
20
  const [logs, setLogs] = useState<LogEntry[]>([]);
21
  const [error, setError] = useState<string | null>(null);
22
  const scrollRef = useRef<HTMLDivElement>(null);
23
 
24
  useEffect(() => {
25
+ const appendLog = (newLog: LogEntry) => {
26
+ setLogs(prev => {
27
+ if (prev.some(l => l.id === newLog.id)) return prev;
28
+ return [...prev, newLog].slice(-50);
29
+ });
30
+ };
31
+
32
  const fetchLogs = async () => {
33
  const { data, error: supabaseError } = await supabase
34
  .from('agent_logs')
 
48
  }
49
  };
50
 
51
+ const apiUrl = getApiUrl();
52
+ let eventSource: EventSource | null = null;
53
+ let pollInterval: number | undefined;
54
+ let channel: ReturnType<typeof supabase.channel> | null = null;
55
+ let active = true;
56
+
57
+ const connectBackendStream = async () => {
58
+ const { data: sessionData } = await supabase.auth.getSession();
59
+ const accessToken = sessionData.session?.access_token;
60
+ if (!active || !accessToken) {
61
+ setError('Authenticated log stream unavailable. Falling back to polling.');
62
+ fetchLogs();
63
+ pollInterval = window.setInterval(() => {
64
+ if (document.visibilityState === 'visible') fetchLogs();
65
+ }, 15000);
66
+ return;
67
+ }
68
+ const params = new URLSearchParams();
69
+ params.set('access_token', accessToken);
70
+ if (taskId) {
71
+ params.set('task_id', taskId);
72
+ } else if (projectId) {
73
+ params.set('project_id', projectId);
74
+ }
75
+ const query = params.toString();
76
+ eventSource = new EventSource(`${apiUrl}/tasks/logs/stream${query ? `?${query}` : ''}`);
77
+ eventSource.addEventListener('ready', () => setError(null));
78
+ eventSource.addEventListener('log', (event) => {
79
+ try {
80
+ appendLog(JSON.parse((event as MessageEvent).data) as LogEntry);
81
+ setError(null);
82
+ } catch (parseError) {
83
+ console.error('Error parsing log stream event:', parseError);
84
+ }
85
+ });
86
+ eventSource.addEventListener('error', () => {
87
+ setError('Backend log stream unavailable. Falling back to polling.');
88
+ fetchLogs();
89
+ if (!pollInterval) {
90
+ pollInterval = window.setInterval(() => {
91
+ if (document.visibilityState === 'visible') fetchLogs();
92
+ }, 15000);
93
+ }
94
+ });
95
+ };
96
+
97
+ if (apiUrl) {
98
+ connectBackendStream();
99
+ } else {
100
+ fetchLogs();
101
+ pollInterval = window.setInterval(() => {
102
+ if (document.visibilityState === 'visible') fetchLogs();
103
+ }, 15000);
104
+
105
+ channel = supabase
106
+ .channel('agent_logs_changes')
107
+ .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'agent_logs' }, (payload) => {
108
+ appendLog(payload.new as LogEntry);
109
+ })
110
+ .subscribe();
111
+ }
112
 
113
  return () => {
114
+ active = false;
115
+ if (eventSource) eventSource.close();
116
+ if (pollInterval) window.clearInterval(pollInterval);
117
+ if (channel) supabase.removeChannel(channel);
118
  };
119
+ }, [projectId, taskId]);
120
 
121
  useEffect(() => {
122
  if (scrollRef.current) {