From 3dd454a12d13517fda72ec65c31aa1fff4d264ed Mon Sep 17 00:00:00 2001 From: Titouan BENOIT Date: Tue, 13 Jan 2026 13:38:44 +0100 Subject: [PATCH] opti: polling only after receiving an agent message --- apps/web/src/App.tsx | 3 +- .../src/components/family/family-panel.tsx | 8 +++- apps/web/src/hooks/use-chat.ts | 39 ++++++++++++++++++- apps/web/src/hooks/use-family-data.ts | 22 ++++++++--- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index c2db320..4e6f82b 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -16,6 +16,7 @@ function App() { sendMessage, familyId, setFamilyId, + isPolling, } = useChat(); return ( @@ -38,7 +39,7 @@ function App() { {/* Right Panel: Family Data (55% width) */}
{familyId ? ( - + ) : (
diff --git a/apps/web/src/components/family/family-panel.tsx b/apps/web/src/components/family/family-panel.tsx index e97aa1e..85dea55 100644 --- a/apps/web/src/components/family/family-panel.tsx +++ b/apps/web/src/components/family/family-panel.tsx @@ -9,11 +9,15 @@ type TabType = 'overview' | 'members' | 'availability' | 'settings'; interface FamilyPanelProps { familyId: string; + /** When true, enables polling for family data updates (during agent conversation) */ + isPolling?: boolean; } -export function FamilyPanel({ familyId }: FamilyPanelProps) { +export function FamilyPanel({ familyId, isPolling = false }: FamilyPanelProps) { const [activeTab, setActiveTab] = useState('overview'); - const { family, members, availability, settings, isLoading, error } = useFamilyData(familyId); + const { family, members, availability, settings, isLoading, error } = useFamilyData(familyId, { + isPolling, + }); const tabs: { id: TabType; label: string }[] = [ { id: 'overview', label: 'Overview' }, diff --git a/apps/web/src/hooks/use-chat.ts b/apps/web/src/hooks/use-chat.ts index 0461815..c531910 100644 --- a/apps/web/src/hooks/use-chat.ts +++ b/apps/web/src/hooks/use-chat.ts @@ -1,10 +1,12 @@ -import { useState, useRef, useEffect } from 'react'; +import { useState, useRef, useEffect, useCallback } from 'react'; export interface Message { role: 'user' | 'assistant'; content: string; } +const POLLING_DURATION_MS = 5000; // Duration to poll after receiving a message + export function useChat() { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); @@ -18,6 +20,36 @@ export function useChat() { // Family ID state - will be set when agent creates a family const [familyId, setFamilyId] = useState(null); + // Polling state - controls when family data should be polled + const [isPolling, setIsPolling] = useState(false); + const pollingTimeoutRef = useRef | null>(null); + + // Start or reset the polling timer + const triggerPolling = useCallback(() => { + // Clear existing timeout if any + if (pollingTimeoutRef.current) { + clearTimeout(pollingTimeoutRef.current); + } + + // Enable polling + setIsPolling(true); + + // Set timeout to disable polling after the duration + pollingTimeoutRef.current = setTimeout(() => { + setIsPolling(false); + pollingTimeoutRef.current = null; + }, POLLING_DURATION_MS); + }, []); + + // Clean up timeout on unmount + useEffect(() => { + return () => { + if (pollingTimeoutRef.current) { + clearTimeout(pollingTimeoutRef.current); + } + }; + }, []); + const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; @@ -98,6 +130,9 @@ export function useChat() { newMessages[newMessages.length - 1] = { ...assistantMessage }; return newMessages; }); + // Trigger polling when receiving agent messages + // This resets the 5s timer on each chunk received + triggerPolling(); } else if (data.type === 'error') { throw new Error(data.content); } @@ -135,5 +170,7 @@ export function useChat() { setInput, familyId, setFamilyId: updateFamilyId, + /** Whether family data polling is currently active (5s after last agent message) */ + isPolling, }; } diff --git a/apps/web/src/hooks/use-family-data.ts b/apps/web/src/hooks/use-family-data.ts index adc6880..dc0488b 100644 --- a/apps/web/src/hooks/use-family-data.ts +++ b/apps/web/src/hooks/use-family-data.ts @@ -1,33 +1,45 @@ import { useQuery } from '@tanstack/react-query'; import { apiClient } from '@/lib/api-client'; -export function useFamilyData(familyId: string) { +interface UseFamilyDataOptions { + /** When true, enables polling every 1 second. When false, polling is disabled. */ + isPolling?: boolean; +} + +const POLLING_INTERVAL = 1000; // Poll every 1 second when active + +export function useFamilyData(familyId: string, options: UseFamilyDataOptions = {}) { + const { isPolling = false } = options; + + // Only poll when isPolling is true, otherwise disable refetchInterval + const refetchInterval = isPolling ? POLLING_INTERVAL : false; + const familyQuery = useQuery({ queryKey: ['family', familyId], queryFn: () => apiClient.getFamily(familyId), enabled: !!familyId, - refetchInterval: 5000, // Poll every 5 seconds for real-time updates + refetchInterval, }); const membersQuery = useQuery({ queryKey: ['members', familyId], queryFn: () => apiClient.getMembers(familyId), enabled: !!familyId, - refetchInterval: 5000, + refetchInterval, }); const availabilityQuery = useQuery({ queryKey: ['availability', familyId], queryFn: () => apiClient.getAvailability(familyId), enabled: !!familyId, - refetchInterval: 5000, + refetchInterval, }); const settingsQuery = useQuery({ queryKey: ['settings', familyId], queryFn: () => apiClient.getSettings(familyId), enabled: !!familyId, - refetchInterval: 5000, + refetchInterval, }); return {