import { useState, useEffect, useCallback } from "react"; export interface SearchSourceConnector { id: number; name: string; connector_type: string; is_indexable: boolean; last_indexed_at: string | null; config: Record; user_id?: string; created_at?: string; } export interface ConnectorSourceItem { id: number; name: string; type: string; sources: any[]; } /** * Hook to fetch search source connectors from the API */ export const useSearchSourceConnectors = () => { const [connectors, setConnectors] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isLoaded, setIsLoaded] = useState(false); const [error, setError] = useState(null); const [connectorSourceItems, setConnectorSourceItems] = useState< ConnectorSourceItem[] >([ { id: 1, name: "Crawled URL", type: "CRAWLED_URL", sources: [], }, { id: 2, name: "File", type: "FILE", sources: [], }, { id: 3, name: "Extension", type: "EXTENSION", sources: [], }, { id: 4, name: "Youtube Video", type: "YOUTUBE_VIDEO", sources: [], }, ]); const fetchConnectors = useCallback(async () => { if (isLoaded) return; // Don't fetch if already loaded try { setIsLoading(true); setError(null); const token = localStorage.getItem("surfsense_bearer_token"); if (!token) { throw new Error("No authentication token found"); } const response = await fetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/`, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { throw new Error( `Failed to fetch connectors: ${response.statusText}` ); } const data = await response.json(); setConnectors(data); setIsLoaded(true); // Update connector source items when connectors change updateConnectorSourceItems(data); } catch (err) { setError( err instanceof Error ? err : new Error("An unknown error occurred") ); console.error("Error fetching search source connectors:", err); } finally { setIsLoading(false); } }, [isLoaded]); // Update connector source items when connectors change const updateConnectorSourceItems = ( currentConnectors: SearchSourceConnector[] ) => { // Start with the default hardcoded connectors const defaultConnectors: ConnectorSourceItem[] = [ { id: 1, name: "Crawled URL", type: "CRAWLED_URL", sources: [], }, { id: 2, name: "File", type: "FILE", sources: [], }, { id: 3, name: "Extension", type: "EXTENSION", sources: [], }, { id: 4, name: "Youtube Video", type: "YOUTUBE_VIDEO", sources: [], }, ]; // Add the API connectors const apiConnectors: ConnectorSourceItem[] = currentConnectors.map( (connector, index) => ({ id: 1000 + index, // Use a high ID to avoid conflicts with hardcoded IDs name: connector.name, type: connector.connector_type, sources: [], }) ); setConnectorSourceItems([...defaultConnectors, ...apiConnectors]); }; /** * Create a new search source connector */ const createConnector = async ( connectorData: Omit< SearchSourceConnector, "id" | "user_id" | "created_at" > ) => { try { const token = localStorage.getItem("surfsense_bearer_token"); if (!token) { throw new Error("No authentication token found"); } const response = await fetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(connectorData), } ); if (!response.ok) { throw new Error( `Failed to create connector: ${response.statusText}` ); } const newConnector = await response.json(); const updatedConnectors = [...connectors, newConnector]; setConnectors(updatedConnectors); updateConnectorSourceItems(updatedConnectors); return newConnector; } catch (err) { console.error("Error creating search source connector:", err); throw err; } }; /** * Update an existing search source connector */ const updateConnector = async ( connectorId: number, connectorData: Partial< Omit > ) => { try { const token = localStorage.getItem("surfsense_bearer_token"); if (!token) { throw new Error("No authentication token found"); } const response = await fetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify(connectorData), } ); if (!response.ok) { throw new Error( `Failed to update connector: ${response.statusText}` ); } const updatedConnector = await response.json(); const updatedConnectors = connectors.map((connector) => connector.id === connectorId ? updatedConnector : connector ); setConnectors(updatedConnectors); updateConnectorSourceItems(updatedConnectors); return updatedConnector; } catch (err) { console.error("Error updating search source connector:", err); throw err; } }; /** * Delete a search source connector */ const deleteConnector = async (connectorId: number) => { try { const token = localStorage.getItem("surfsense_bearer_token"); if (!token) { throw new Error("No authentication token found"); } const response = await fetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`, { method: "DELETE", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { throw new Error( `Failed to delete connector: ${response.statusText}` ); } const updatedConnectors = connectors.filter( (connector) => connector.id !== connectorId ); setConnectors(updatedConnectors); updateConnectorSourceItems(updatedConnectors); } catch (err) { console.error("Error deleting search source connector:", err); throw err; } }; /** * Index content from a connector to a search space */ const indexConnector = async ( connectorId: number, searchSpaceId: string | number, startDate?: string, endDate?: string ) => { try { const token = localStorage.getItem("surfsense_bearer_token"); if (!token) { throw new Error("No authentication token found"); } // Build query parameters const params = new URLSearchParams({ search_space_id: searchSpaceId.toString(), }); if (startDate) { params.append("start_date", startDate); } if (endDate) { params.append("end_date", endDate); } const response = await fetch( `${ process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL }/api/v1/search-source-connectors/${connectorId}/index?${params.toString()}`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { throw new Error( `Failed to index connector content: ${response.statusText}` ); } const result = await response.json(); // Update the connector's last_indexed_at timestamp const updatedConnectors = connectors.map((connector) => connector.id === connectorId ? { ...connector, last_indexed_at: new Date().toISOString(), } : connector ); setConnectors(updatedConnectors); return result; } catch (err) { console.error("Error indexing connector content:", err); throw err; } }; /** * Get connector source items - memoized to prevent unnecessary re-renders */ const getConnectorSourceItems = useCallback(() => { return connectorSourceItems; }, [connectorSourceItems]); return { connectors, isLoading, isLoaded, error, fetchConnectors, createConnector, updateConnector, deleteConnector, indexConnector, getConnectorSourceItems, connectorSourceItems, }; };