diff --git a/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx index 9fc0a121a..684bf6b85 100644 --- a/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/InboxSidebar.tsx @@ -45,10 +45,12 @@ import { Skeleton } from "@/components/ui/skeleton"; import { Spinner } from "@/components/ui/spinner"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { getDocumentTypeLabel } from "@/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentTypeIcon"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { isCommentReplyMetadata, isConnectorIndexingMetadata, + isDocumentProcessingMetadata, isNewMentionMetadata, isPageLimitExceededMetadata, } from "@/contracts/types/inbox.types"; @@ -193,7 +195,7 @@ export function InboxSidebar({ const isSearchMode = !!debouncedSearch.trim(); const [activeTab, setActiveTab] = useState("comments"); const [activeFilter, setActiveFilter] = useState("all"); - const [selectedConnector, setSelectedConnector] = useState(null); + const [selectedSource, setSelectedSource] = useState(null); const [mounted, setMounted] = useState(false); // Dropdown state for filter menu (desktop only) const [openDropdown, setOpenDropdown] = useState<"filter" | null>(null); @@ -257,10 +259,10 @@ export function InboxSidebar({ }; }, [open, isMobile]); - // Reset connector filter when switching away from status tab + // Reset source filter when switching away from status tab useEffect(() => { if (activeTab !== "status") { - setSelectedConnector(null); + setSelectedSource(null); } }, [activeTab]); @@ -289,23 +291,47 @@ export function InboxSidebar({ activeTab === "comments" ? (mentions.hasMore ?? false) : (status.hasMore ?? false); const loadMore = activeTab === "comments" ? mentions.loadMore : status.loadMore; - // Get unique connector types from status items for filtering - const uniqueConnectorTypes = useMemo(() => { - const connectorTypes = new Set(); + // Get unique source types (connectors + document types) from status items for filtering + const statusSourceOptions = useMemo(() => { + const sources: Array<{ + key: string; + type: string; + category: "connector" | "document"; + displayName: string; + }> = []; + const seenConnectors = new Set(); + const seenDocTypes = new Set(); - statusItems - .filter((item) => item.type === "connector_indexing") - .forEach((item) => { - // Use type guard for safe metadata access - if (isConnectorIndexingMetadata(item.metadata)) { - connectorTypes.add(item.metadata.connector_type); + for (const item of statusItems) { + if (item.type === "connector_indexing" && isConnectorIndexingMetadata(item.metadata)) { + const ct = item.metadata.connector_type; + if (!seenConnectors.has(ct)) { + seenConnectors.add(ct); + sources.push({ + key: `connector:${ct}`, + type: ct, + category: "connector", + displayName: getConnectorTypeDisplayName(ct), + }); } - }); + } else if ( + item.type === "document_processing" && + isDocumentProcessingMetadata(item.metadata) + ) { + const dt = item.metadata.document_type; + if (!seenDocTypes.has(dt)) { + seenDocTypes.add(dt); + sources.push({ + key: `doctype:${dt}`, + type: dt, + category: "document", + displayName: getDocumentTypeLabel(dt), + }); + } + } + } - return Array.from(connectorTypes).map((type) => ({ - type, - displayName: getConnectorTypeDisplayName(type), - })); + return sources; }, [statusItems]); // Get items for current tab @@ -334,22 +360,31 @@ export function InboxSidebar({ items = items.filter((item) => !item.read); } - // Apply connector filter (only for status tab) - if (activeTab === "status" && selectedConnector) { + // Apply source filter (connector type or document type, for status tab only) + if (activeTab === "status" && selectedSource) { items = items.filter((item) => { - if (item.type === "connector_indexing") { - // Use type guard for safe metadata access - if (isConnectorIndexingMetadata(item.metadata)) { - return item.metadata.connector_type === selectedConnector; - } - return false; + if (selectedSource.startsWith("connector:")) { + const connectorType = selectedSource.slice("connector:".length); + return ( + item.type === "connector_indexing" && + isConnectorIndexingMetadata(item.metadata) && + item.metadata.connector_type === connectorType + ); } - return false; // Hide document_processing when a specific connector is selected + if (selectedSource.startsWith("doctype:")) { + const docType = selectedSource.slice("doctype:".length); + return ( + item.type === "document_processing" && + isDocumentProcessingMetadata(item.metadata) && + item.metadata.document_type === docType + ); + } + return true; }); } return items; - }, [displayItems, searchResponse, isSearchMode, activeFilter, activeTab, selectedConnector]); + }, [displayItems, searchResponse, isSearchMode, activeFilter, activeTab, selectedSource]); // Intersection Observer for infinite scroll with prefetching // Re-runs when active tab changes so each tab gets its own pagination @@ -651,59 +686,59 @@ export function InboxSidebar({ - {/* Connectors section - only for status tab */} - {activeTab === "status" && uniqueConnectorTypes.length > 0 && ( -
-

- {t("connectors") || "Connectors"} -

-
+ {/* Sources section - only for status tab */} + {activeTab === "status" && statusSourceOptions.length > 0 && ( +
+

+ {t("sources") || "Sources"} +

+
+ + {statusSourceOptions.map((source) => ( - {uniqueConnectorTypes.map((connector) => ( - - ))} -
+ ))}
- )} +
+ )}
@@ -727,7 +762,7 @@ export function InboxSidebar({ {t("filter") || "Filter"} @@ -752,45 +787,45 @@ export function InboxSidebar({ {activeFilter === "unread" && } - {activeTab === "status" && uniqueConnectorTypes.length > 0 && ( - <> - - {t("connectors") || "Connectors"} - -
0 && ( + <> + + {t("sources") || "Sources"} + +
+ setSelectedSource(null)} + className="flex items-center justify-between" > + + + {t("all_sources") || "All sources"} + + {selectedSource === null && } + + {statusSourceOptions.map((source) => ( setSelectedConnector(null)} + key={source.key} + onClick={() => setSelectedSource(source.key)} className="flex items-center justify-between" > - - {t("all_connectors") || "All connectors"} + {getConnectorIcon(source.type, "h-4 w-4")} + {source.displayName} - {selectedConnector === null && } + {selectedSource === source.key && } - {uniqueConnectorTypes.map((connector) => ( - setSelectedConnector(connector.type)} - className="flex items-center justify-between" - > - - {getConnectorIcon(connector.type, "h-4 w-4")} - {connector.displayName} - - {selectedConnector === connector.type && } - - ))} -
- - )} + ))} +
+ + )}
)} diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json index 2964cca22..d491899ad 100644 --- a/surfsense_web/messages/en.json +++ b/surfsense_web/messages/en.json @@ -721,6 +721,8 @@ "unread": "Unread", "connectors": "Connectors", "all_connectors": "All connectors", + "sources": "Sources", + "all_sources": "All sources", "close": "Close", "cancel": "Cancel" }, diff --git a/surfsense_web/messages/es.json b/surfsense_web/messages/es.json index af9c3b34f..758c93baf 100644 --- a/surfsense_web/messages/es.json +++ b/surfsense_web/messages/es.json @@ -721,6 +721,8 @@ "unread": "No leído", "connectors": "Conectores", "all_connectors": "Todos los conectores", + "sources": "Fuentes", + "all_sources": "Todas las fuentes", "close": "Cerrar", "cancel": "Cancelar" }, diff --git a/surfsense_web/messages/hi.json b/surfsense_web/messages/hi.json index ca967a8da..07dcddbcc 100644 --- a/surfsense_web/messages/hi.json +++ b/surfsense_web/messages/hi.json @@ -721,6 +721,8 @@ "unread": "अपठित", "connectors": "कनेक्टर", "all_connectors": "सभी कनेक्टर", + "sources": "स्रोत", + "all_sources": "सभी स्रोत", "close": "बंद करें", "cancel": "रद्द करें" }, diff --git a/surfsense_web/messages/pt.json b/surfsense_web/messages/pt.json index 5ddab2c22..9691d4727 100644 --- a/surfsense_web/messages/pt.json +++ b/surfsense_web/messages/pt.json @@ -721,6 +721,8 @@ "unread": "Não lido", "connectors": "Conectores", "all_connectors": "Todos os conectores", + "sources": "Fontes", + "all_sources": "Todas as fontes", "close": "Fechar", "cancel": "Cancelar" }, diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json index 6a3b00c4f..969ea2b25 100644 --- a/surfsense_web/messages/zh.json +++ b/surfsense_web/messages/zh.json @@ -705,6 +705,8 @@ "unread": "未读", "connectors": "连接器", "all_connectors": "所有连接器", + "sources": "来源", + "all_sources": "所有来源", "close": "关闭", "cancel": "取消" },