State & Hooks
Frontend State & Hooks
The frontend is built with Next.js 15 and utilizes a combination of TanStack Query for asynchronous server state and Custom Hooks for real-time WebSocket communication.
Real-Time Updates with useWebSocket
The application uses a generic useWebSocket hook to subscribe to server-side events, primarily for tracking long-running ingestion jobs.
Usage: Monitoring Ingestion Progress
In the ingestion flow, this hook connects to the status endpoint to receive ProgressEvent updates.
import { useWebSocket } from '@/lib/hooks/useWebSocket';
import { ProgressEvent } from '@/lib/types';
const { lastMessage } = useWebSocket<ProgressEvent>(`/api/v1/ws/jobs/${jobId}`);
useEffect(() => {
if (lastMessage) {
console.log(`Current Stage: ${lastMessage.stage}`);
console.log(`Progress: ${lastMessage.progress_percent}%`);
}
}, [lastMessage]);
Key Event Types:
ProgressEvent: Contains thestage(e.g.,transcribing,embedding),progress_percent, and a human-readablemessage.ChatEvent: Used in the Chat Assistant to stream agent thinking states (e.g.,searching,found,pii_anonymized).
Data Fetching Hooks
The system leverages TanStack Query for caching and synchronizing video metadata.
fetchVideos Query
Used to manage the video library state. It provides built-in handling for loading states and manual refetching.
import { useQuery } from '@tanstack/react-query';
import { fetchVideos } from '@/lib/api/videos';
const { data, isLoading, refetch } = useQuery({
queryKey: ['videos'],
queryFn: fetchVideos,
});
deleteVideo Mutation
Handles the removal of video assets and their associated vector embeddings, triggering a cache invalidation upon success.
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { deleteVideo } from '@/lib/api/videos';
const queryClient = useQueryClient();
const deleteMutation = useMutation({
mutationFn: deleteVideo,
onSuccess: () => {
// Refresh the video list after deletion
queryClient.invalidateQueries({ queryKey: ['videos'] });
},
});
// Usage
deleteMutation.mutate(videoId);
Search & Discovery State
Search state is managed locally within the Discovery page to handle complex multimodal results.
handleSearch Logic
This interaction coordinates the transition from a natural language query to a structured SearchResponse.
const [results, setResults] = useState<SearchResponse | null>(null);
const [isLoading, setIsLoading] = useState(false);
const handleSearch = async (query: string) => {
setIsLoading(true);
try {
const res = await searchVideos({
query,
top_k: 10,
include_visual: true,
include_audio: true
});
setResults(res);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
Core Data Models
When interacting with hooks or state, the following interfaces define the public data structures:
| Interface | Description |
| :--- | :--- |
| VideoInfo | Core metadata for an ingested video (ID, duration, searchable status). |
| SearchResult | A specific video segment matching a query, including chunk_type (audio/visual) and timestamps. |
| ProgressEvent | Real-time payload containing stage_number, total_stages, and eta_seconds. |
| ChatSource | Attribution data for AI answers, linking responses back to specific video timestamps. |
Error Handling
Errors are captured via the BaseAppException on the backend and surfaced in the frontend through standard state variables (error, setError). All API calls are wrapped in try-catch blocks to ensure the UI remains responsive even if a service (like the Vector Store or Ollama) is temporarily unavailable.