diff --git a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx index 0268ab761..5e671b359 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/tabs/all-connectors-tab.tsx @@ -13,6 +13,24 @@ import { } from "../constants/connector-constants"; import { getDocumentCountForConnector } from "../utils/connector-document-mapping"; +type OAuthConnector = (typeof OAUTH_CONNECTORS)[number]; +type ComposioConnector = (typeof COMPOSIO_CONNECTORS)[number]; +type OtherConnector = (typeof OTHER_CONNECTORS)[number]; +type CrawlerConnector = (typeof CRAWLERS)[number]; + +const DOCUMENT_FILE_CONNECTOR_TYPES = new Set([ + EnumConnectorName.GOOGLE_DRIVE_CONNECTOR, + EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR, + EnumConnectorName.ONEDRIVE_CONNECTOR, + EnumConnectorName.DROPBOX_CONNECTOR, +]); + +const OTHER_DOCUMENT_CONNECTOR_TYPES = new Set([ + EnumConnectorName.YOUTUBE_CONNECTOR, + EnumConnectorName.NOTION_CONNECTOR, + EnumConnectorName.AIRTABLE_CONNECTOR, +]); + /** * Extract the display name from a full connector name. * Full names are in format "Base Name - identifier" (e.g., "Gmail - john@example.com"). @@ -34,9 +52,7 @@ interface AllConnectorsTabProps { allConnectors: SearchSourceConnector[] | undefined; documentTypeCounts?: Record; indexingConnectorIds?: Set; - onConnectOAuth: ( - connector: (typeof OAUTH_CONNECTORS)[number] | (typeof COMPOSIO_CONNECTORS)[number] - ) => void; + onConnectOAuth: (connector: OAuthConnector | ComposioConnector) => void; onConnectNonOAuth?: (connectorType: string) => void; onCreateWebcrawler?: () => void; onCreateYouTubeCrawler?: () => void; @@ -92,241 +108,233 @@ export const AllConnectorsTab: FC = ({ c.description.toLowerCase().includes(searchQuery.toLowerCase()) ); + const nativeGoogleDriveConnectors = filteredOAuth.filter( + (c) => c.connectorType === EnumConnectorName.GOOGLE_DRIVE_CONNECTOR + ); + const composioGoogleDriveConnectors = filteredComposio.filter( + (c) => c.connectorType === EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR + ); + const fileStorageConnectors = filteredOAuth.filter( + (c) => + c.connectorType === EnumConnectorName.ONEDRIVE_CONNECTOR || + c.connectorType === EnumConnectorName.DROPBOX_CONNECTOR + ); + + const otherDocumentYouTubeConnectors = filteredCrawlers.filter( + (c) => c.connectorType === EnumConnectorName.YOUTUBE_CONNECTOR + ); + const otherDocumentNotionConnectors = filteredOAuth.filter( + (c) => c.connectorType === EnumConnectorName.NOTION_CONNECTOR + ); + const otherDocumentAirtableConnectors = filteredOAuth.filter( + (c) => c.connectorType === EnumConnectorName.AIRTABLE_CONNECTOR + ); + + const moreIntegrationsComposio = filteredComposio.filter( + (c) => + !DOCUMENT_FILE_CONNECTOR_TYPES.has(c.connectorType) && + !OTHER_DOCUMENT_CONNECTOR_TYPES.has(c.connectorType) + ); + const moreIntegrationsOAuth = filteredOAuth.filter( + (c) => + !DOCUMENT_FILE_CONNECTOR_TYPES.has(c.connectorType) && + !OTHER_DOCUMENT_CONNECTOR_TYPES.has(c.connectorType) + ); + const moreIntegrationsOther = filteredOther; + const moreIntegrationsCrawlers = filteredCrawlers.filter( + (c) => + !c.connectorType || + (!DOCUMENT_FILE_CONNECTOR_TYPES.has(c.connectorType) && + !OTHER_DOCUMENT_CONNECTOR_TYPES.has(c.connectorType)) + ); + + const renderOAuthCard = (connector: OAuthConnector | ComposioConnector) => { + const isConnected = connectedTypes.has(connector.connectorType); + const isConnecting = connectingId === connector.id; + + const typeConnectors = + isConnected && allConnectors + ? allConnectors.filter( + (c: SearchSourceConnector) => c.connector_type === connector.connectorType + ) + : []; + + const accountCount = typeConnectors.length; + const documentCount = getDocumentCountForConnector( + connector.connectorType, + documentTypeCounts + ); + const isIndexing = typeConnectors.some((c) => indexingConnectorIds?.has(c.id)); + + return ( + onConnectOAuth(connector)} + onManage={ + isConnected && onViewAccountsList + ? () => onViewAccountsList(connector.connectorType, connector.title) + : undefined + } + /> + ); + }; + + const renderOtherCard = (connector: OtherConnector) => { + const isConnected = connectedTypes.has(connector.connectorType); + const isConnecting = connectingId === connector.id; + + const actualConnector = + isConnected && allConnectors + ? allConnectors.find( + (c: SearchSourceConnector) => c.connector_type === connector.connectorType + ) + : undefined; + + const documentCount = getDocumentCountForConnector( + connector.connectorType, + documentTypeCounts + ); + const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id); + + const isMCP = connector.connectorType === EnumConnectorName.MCP_CONNECTOR; + const mcpConnectorCount = + isMCP && allConnectors + ? allConnectors.filter( + (c: SearchSourceConnector) => + c.connector_type === EnumConnectorName.MCP_CONNECTOR + ).length + : undefined; + + const handleConnect = onConnectNonOAuth + ? () => onConnectNonOAuth(connector.connectorType) + : () => {}; + + return ( + onManage(actualConnector) : undefined + } + /> + ); + }; + + const renderCrawlerCard = (crawler: CrawlerConnector) => { + const isYouTube = crawler.id === "youtube-crawler"; + const isWebcrawler = crawler.id === "webcrawler-connector"; + const isConnected = crawler.connectorType + ? connectedTypes.has(crawler.connectorType) + : false; + const isConnecting = connectingId === crawler.id; + + const actualConnector = + isConnected && crawler.connectorType && allConnectors + ? allConnectors.find( + (c: SearchSourceConnector) => c.connector_type === crawler.connectorType + ) + : undefined; + + const documentCount = crawler.connectorType + ? getDocumentCountForConnector(crawler.connectorType, documentTypeCounts) + : undefined; + const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id); + + const handleConnect = + isYouTube && onCreateYouTubeCrawler + ? onCreateYouTubeCrawler + : isWebcrawler && onCreateWebcrawler + ? onCreateWebcrawler + : crawler.connectorType && onConnectNonOAuth + ? () => { + if (crawler.connectorType) { + onConnectNonOAuth(crawler.connectorType); + } + } + : () => {}; + + return ( + onManage(actualConnector) : undefined + } + /> + ); + }; + + const hasDocumentFileConnectors = + nativeGoogleDriveConnectors.length > 0 || + composioGoogleDriveConnectors.length > 0 || + fileStorageConnectors.length > 0; + const hasMoreIntegrations = + otherDocumentYouTubeConnectors.length > 0 || + otherDocumentNotionConnectors.length > 0 || + otherDocumentAirtableConnectors.length > 0 || + moreIntegrationsComposio.length > 0 || + moreIntegrationsOAuth.length > 0 || + moreIntegrationsOther.length > 0 || + moreIntegrationsCrawlers.length > 0; + return (
- {/* Managed OAuth (Composio Integrations) */} - {filteredComposio.length > 0 && ( + {/* Document/Files Connectors */} + {hasDocumentFileConnectors && (

- Managed OAuth (Composio) + Document/Files Connectors

- {filteredComposio.map((connector) => { - const isConnected = connectedTypes.has(connector.connectorType); - const isConnecting = connectingId === connector.id; - - // Find all connectors of this type - const typeConnectors = - isConnected && allConnectors - ? allConnectors.filter( - (c: SearchSourceConnector) => c.connector_type === connector.connectorType - ) - : []; - - const accountCount = typeConnectors.length; - - const documentCount = getDocumentCountForConnector( - connector.connectorType, - documentTypeCounts - ); - - // Check if any account is currently indexing - const isIndexing = typeConnectors.some((c) => indexingConnectorIds?.has(c.id)); - - return ( - onConnectOAuth(connector)} - onManage={ - isConnected && onViewAccountsList - ? () => onViewAccountsList(connector.connectorType, connector.title) - : undefined - } - /> - ); - })} -
-
- )} - - {/* Quick Connect */} - {filteredOAuth.length > 0 && ( -
-
-

Quick Connect

-
-
- {filteredOAuth.map((connector) => { - const isConnected = connectedTypes.has(connector.connectorType); - const isConnecting = connectingId === connector.id; - - // Find all connectors of this type - const typeConnectors = - isConnected && allConnectors - ? allConnectors.filter( - (c: SearchSourceConnector) => c.connector_type === connector.connectorType - ) - : []; - - const accountCount = typeConnectors.length; - - const documentCount = getDocumentCountForConnector( - connector.connectorType, - documentTypeCounts - ); - - // Check if any account is currently indexing - const isIndexing = typeConnectors.some((c) => indexingConnectorIds?.has(c.id)); - - return ( - onConnectOAuth(connector)} - onManage={ - isConnected && onViewAccountsList - ? () => onViewAccountsList(connector.connectorType, connector.title) - : undefined - } - /> - ); - })} + {nativeGoogleDriveConnectors.map(renderOAuthCard)} + {composioGoogleDriveConnectors.map(renderOAuthCard)} + {fileStorageConnectors.map(renderOAuthCard)}
)} {/* More Integrations */} - {filteredOther.length > 0 && ( + {hasMoreIntegrations && (

More Integrations

- {filteredOther.map((connector) => { - const isConnected = connectedTypes.has(connector.connectorType); - const isConnecting = connectingId === connector.id; - - // Find the actual connector object if connected - const actualConnector = - isConnected && allConnectors - ? allConnectors.find( - (c: SearchSourceConnector) => c.connector_type === connector.connectorType - ) - : undefined; - - const documentCount = getDocumentCountForConnector( - connector.connectorType, - documentTypeCounts - ); - const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id); - - // For MCP connectors, count total MCP connectors instead of document count - const isMCP = connector.connectorType === EnumConnectorName.MCP_CONNECTOR; - const mcpConnectorCount = - isMCP && allConnectors - ? allConnectors.filter( - (c: SearchSourceConnector) => - c.connector_type === EnumConnectorName.MCP_CONNECTOR - ).length - : undefined; - - const handleConnect = onConnectNonOAuth - ? () => onConnectNonOAuth(connector.connectorType) - : () => {}; // Fallback - connector popup should handle all connector types - - return ( - onManage(actualConnector) : undefined - } - /> - ); - })} -
-
- )} - - {/* Content Sources */} - {filteredCrawlers.length > 0 && ( -
-
-

Content Sources

-
-
- {filteredCrawlers.map((crawler) => { - const isYouTube = crawler.id === "youtube-crawler"; - const isWebcrawler = crawler.id === "webcrawler-connector"; - - // For crawlers that are actual connectors, check connection status - const isConnected = crawler.connectorType - ? connectedTypes.has(crawler.connectorType) - : false; - const isConnecting = connectingId === crawler.id; - - // Find the actual connector object if connected - const actualConnector = - isConnected && crawler.connectorType && allConnectors - ? allConnectors.find( - (c: SearchSourceConnector) => c.connector_type === crawler.connectorType - ) - : undefined; - - const documentCount = crawler.connectorType - ? getDocumentCountForConnector(crawler.connectorType, documentTypeCounts) - : undefined; - const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id); - - const handleConnect = - isYouTube && onCreateYouTubeCrawler - ? onCreateYouTubeCrawler - : isWebcrawler && onCreateWebcrawler - ? onCreateWebcrawler - : crawler.connectorType && onConnectNonOAuth - ? () => { - if (crawler.connectorType) { - onConnectNonOAuth(crawler.connectorType); - } - } - : () => {}; // Fallback for non-connector crawlers - - return ( - onManage(actualConnector) : undefined - } - /> - ); - })} + {otherDocumentYouTubeConnectors.map(renderCrawlerCard)} + {otherDocumentNotionConnectors.map(renderOAuthCard)} + {otherDocumentAirtableConnectors.map(renderOAuthCard)} + {moreIntegrationsComposio.map(renderOAuthCard)} + {moreIntegrationsOAuth.map(renderOAuthCard)} + {moreIntegrationsOther.map(renderOtherCard)} + {moreIntegrationsCrawlers.map(renderCrawlerCard)}
)}