Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function App() {
sendMessage,
familyId,
setFamilyId,
isPolling,
} = useChat();

return (
Expand All @@ -38,7 +39,7 @@ function App() {
{/* Right Panel: Family Data (55% width) */}
<div className="w-[55%]">
{familyId ? (
<FamilyPanel familyId={familyId} />
<FamilyPanel familyId={familyId} isPolling={isPolling} />
) : (
<div className="flex items-center justify-center h-full bg-white relative">
<div className="text-center p-8">
Expand Down
8 changes: 6 additions & 2 deletions apps/web/src/components/family/family-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<TabType>('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' },
Expand Down
39 changes: 38 additions & 1 deletion apps/web/src/hooks/use-chat.ts
Original file line number Diff line number Diff line change
@@ -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<Message[]>([]);
const [input, setInput] = useState('');
Expand All @@ -18,6 +20,36 @@ export function useChat() {
// Family ID state - will be set when agent creates a family
const [familyId, setFamilyId] = useState<string | null>(null);

// Polling state - controls when family data should be polled
const [isPolling, setIsPolling] = useState(false);
const pollingTimeoutRef = useRef<ReturnType<typeof setTimeout> | 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' });
};
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -135,5 +170,7 @@ export function useChat() {
setInput,
familyId,
setFamilyId: updateFamilyId,
/** Whether family data polling is currently active (5s after last agent message) */
isPolling,
};
}
22 changes: 17 additions & 5 deletions apps/web/src/hooks/use-family-data.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down