From f11cc3868c956fb6254fe6bdcc860c678f9fade7 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 6 Jul 2026 11:09:25 +0530 Subject: [PATCH] feat(assistant-ui): add "Manage Connectors" option to ComposerAction and clean up DocumentsFilters component - Introduced a new dropdown menu item for managing connectors in the ComposerAction component. - Removed unused upload button and related props from the DocumentsFilters component for improved clarity and simplicity. - Updated LayoutDataProvider and RightPanel components to streamline state management and enhance layout consistency. --- .../components/assistant-ui/thread.tsx | 4 + .../components/documents/DocumentsFilters.tsx | 20 +-- .../layout/providers/LayoutDataProvider.tsx | 26 +--- .../layout/ui/right-panel/RightPanel.tsx | 75 ++--------- .../layout/ui/shell/LayoutShell.tsx | 14 +- .../layout/ui/sidebar/DocumentsSidebar.tsx | 127 ------------------ .../components/layout/ui/sidebar/Sidebar.tsx | 26 +++- 7 files changed, 44 insertions(+), 248 deletions(-) diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 34ff4888f..c12f0f247 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -1359,6 +1359,10 @@ const ComposerAction: FC = ({ Take a screenshot + setConnectorDialogOpen(true)}> + + Manage Connectors + { diff --git a/surfsense_web/components/documents/DocumentsFilters.tsx b/surfsense_web/components/documents/DocumentsFilters.tsx index cda34eb48..22075281f 100644 --- a/surfsense_web/components/documents/DocumentsFilters.tsx +++ b/surfsense_web/components/documents/DocumentsFilters.tsx @@ -1,9 +1,8 @@ "use client"; -import { FolderPlus, ListFilter, Search, Upload, X } from "lucide-react"; +import { FolderPlus, ListFilter, Search, X } from "lucide-react"; import { useTranslations } from "next-intl"; import React, { useCallback, useMemo, useRef, useState } from "react"; -import { useDocumentUploadDialog } from "@/components/assistant-ui/document-upload-popup"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; @@ -21,7 +20,6 @@ export function DocumentsFilters({ onToggleType, activeTypes, onCreateFolder, - onUploadClick, }: { typeCounts: Partial>; onSearch: (v: string) => void; @@ -29,15 +27,11 @@ export function DocumentsFilters({ onToggleType: (type: DocumentTypeEnum, checked: boolean) => void; activeTypes: DocumentTypeEnum[]; onCreateFolder?: () => void; - onUploadClick?: () => void; }) { const t = useTranslations("documents"); const id = React.useId(); const inputRef = useRef(null); - const { openDialog: openUploadDialog } = useDocumentUploadDialog(); - const handleUpload = onUploadClick ?? openUploadDialog; - const [typeSearchQuery, setTypeSearchQuery] = useState(""); const [scrollPos, setScrollPos] = useState<"top" | "middle" | "bottom">("top"); const handleScroll = useCallback((e: React.UIEvent) => { @@ -207,18 +201,6 @@ export function DocumentsFilters({ )} - - {/* Upload Button */} - ); diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 2624384d3..ff7aefc70 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -12,7 +12,6 @@ import { currentThreadAtom, resetCurrentThreadAtom } from "@/atoms/chat/current- import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms"; import { statusInboxItemsAtom } from "@/atoms/inbox/status-inbox.atom"; import { announcementsDialogAtom } from "@/atoms/layout/dialogs.atom"; -import { rightPanelCollapsedAtom } from "@/atoms/layout/right-panel.atom"; import { deleteSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms"; import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { removeChatTabAtom, syncChatTabAtom, type Tab } from "@/atoms/tabs/tabs.atom"; @@ -133,7 +132,6 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid // Documents sidebar state (shared atom so Composer can toggle it) const [isDocumentsSidebarOpen, setIsDocumentsSidebarOpen] = useAtom(documentsSidebarOpenAtom); - const setIsRightPanelCollapsed = useSetAtom(rightPanelCollapsedAtom); // Open documents sidebar by default on desktop (docked mode) const documentsInitialized = useRef(false); @@ -335,9 +333,9 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid }, [threadsData, searchSpaceId]); // Navigation items - // Inbox, Automations, and Documents are rendered explicitly below "New chat" + // Inbox, Automations, and Artifacts are rendered explicitly below "New chat" // in the sidebar (also surfaced in the icon rail's collapsed mode via this - // list). Announcements has been moved to the avatar dropdown. + // list). Documents is embedded below Recents; announcements live in the avatar dropdown. const isAutomationsActive = pathname?.includes("/automations") === true; const isArtifactsActive = pathname?.endsWith("/artifacts") === true; const navItems: NavItem[] = useMemo( @@ -523,27 +521,17 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid return; } if (item.url === "#documents") { - if (!isMobile) { - if (!isDocumentsSidebarOpen) { - setIsDocumentsSidebarOpen(true); - setIsRightPanelCollapsed(false); + setIsDocumentsSidebarOpen((prev) => { + if (!prev) { setActiveSlideoutPanel(null); - } else { - setIsRightPanelCollapsed((prev) => !prev); } - } else { - setIsDocumentsSidebarOpen((prev) => { - if (!prev) { - setActiveSlideoutPanel(null); - } - return !prev; - }); - } + return !prev; + }); return; } router.push(item.url); }, - [router, isMobile, isDocumentsSidebarOpen, setIsDocumentsSidebarOpen, setIsRightPanelCollapsed] + [router, setIsDocumentsSidebarOpen] ); const handleNewChat = useCallback(() => { diff --git a/surfsense_web/components/layout/ui/right-panel/RightPanel.tsx b/surfsense_web/components/layout/ui/right-panel/RightPanel.tsx index 8d9f0454f..501399bf5 100644 --- a/surfsense_web/components/layout/ui/right-panel/RightPanel.tsx +++ b/surfsense_web/components/layout/ui/right-panel/RightPanel.tsx @@ -3,10 +3,9 @@ import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { PanelRight } from "lucide-react"; import dynamic from "next/dynamic"; -import { type MouseEvent, startTransition, useEffect } from "react"; +import { startTransition, useEffect } from "react"; import { closeReportPanelAtom, reportPanelAtom } from "@/atoms/chat/report-panel.atom"; import { citationPanelAtom, closeCitationPanelAtom } from "@/atoms/citation/citation-panel.atom"; -import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms"; import { closeEditorPanelAtom, editorPanelAtom } from "@/atoms/editor/editor-panel.atom"; import { type RightPanelTab, @@ -18,7 +17,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip import { artifactsPanelOpenAtom, closeArtifactsPanelAtom } from "@/features/chat-artifacts"; import { closeHitlEditPanelAtom, hitlEditPanelAtom } from "@/features/chat-messages/hitl"; import { cn } from "@/lib/utils"; -import { DocumentsSidebar } from "../sidebar"; const EditorPanelContent = dynamic( () => @@ -61,41 +59,9 @@ const ArtifactsPanelContent = dynamic( ); interface RightPanelProps { - documentsPanel?: { - open: boolean; - onOpenChange: (open: boolean) => void; - }; - showCollapseButton?: boolean; showTopBorder?: boolean; } -function isKeyboardClick(event: MouseEvent) { - return event.detail === 0; -} - -function CollapseButton({ onClick }: { onClick: () => void }) { - return ( - - - - - Collapse panel - - ); -} - interface RightPanelToggleButtonProps { className?: string; iconClassName?: string; @@ -108,7 +74,6 @@ export function RightPanelToggleButton({ disabled = false, }: RightPanelToggleButtonProps) { const [collapsed, setCollapsed] = useAtom(rightPanelCollapsedAtom); - const documentsOpen = useAtomValue(documentsSidebarOpenAtom); const reportState = useAtomValue(reportPanelAtom); const editorState = useAtomValue(editorPanelAtom); const hitlEditState = useAtomValue(hitlEditPanelAtom); @@ -124,8 +89,7 @@ export function RightPanelToggleButton({ : !!editorState.localFilePath); const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave; const citationOpen = citationState.isOpen && citationState.chunkId != null; - const hasContent = - documentsOpen || reportOpen || editorOpen || hitlEditOpen || citationOpen || artifactsOpen; + const hasContent = reportOpen || editorOpen || hitlEditOpen || citationOpen || artifactsOpen; const label = collapsed ? "Expand panel" : "Collapse panel"; if (!hasContent) return null; @@ -162,7 +126,6 @@ export function RightPanelToggleButton({ */ export function RightPanelExpandButton() { const [collapsed] = useAtom(rightPanelCollapsedAtom); - const documentsOpen = useAtomValue(documentsSidebarOpenAtom); const reportState = useAtomValue(reportPanelAtom); const editorState = useAtomValue(editorPanelAtom); const hitlEditState = useAtomValue(hitlEditPanelAtom); @@ -178,8 +141,7 @@ export function RightPanelExpandButton() { : !!editorState.localFilePath); const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave; const citationOpen = citationState.isOpen && citationState.chunkId != null; - const hasContent = - documentsOpen || reportOpen || editorOpen || hitlEditOpen || citationOpen || artifactsOpen; + const hasContent = reportOpen || editorOpen || hitlEditOpen || citationOpen || artifactsOpen; if (!collapsed || !hasContent) return null; @@ -201,8 +163,7 @@ const PANEL_WIDTHS = { /** * Priority order used to fall back to another open surface when the active - * tab's content closes. Artifacts sit just above the always-available sources - * tab. + * tab's content closes. The neutral "sources" tab is kept as the closed state. */ const TAB_FALLBACK_ORDER: RightPanelTab[] = [ "hitl-edit", @@ -221,11 +182,7 @@ function resolveEffectiveTab( return TAB_FALLBACK_ORDER.find((tab) => openByTab[tab]) ?? "sources"; } -export function RightPanel({ - documentsPanel, - showCollapseButton = true, - showTopBorder = false, -}: RightPanelProps) { +export function RightPanel({ showTopBorder = false }: RightPanelProps) { const [activeTab] = useAtom(rightPanelTabAtom); const reportState = useAtomValue(reportPanelAtom); const closeReport = useSetAtom(closeReportPanelAtom); @@ -237,9 +194,8 @@ export function RightPanel({ const closeCitation = useSetAtom(closeCitationPanelAtom); const artifactsOpen = useAtomValue(artifactsPanelOpenAtom); const closeArtifacts = useSetAtom(closeArtifactsPanelAtom); - const [collapsed, setCollapsed] = useAtom(rightPanelCollapsedAtom); + const [collapsed] = useAtom(rightPanelCollapsedAtom); - const documentsOpen = documentsPanel?.open ?? false; const reportOpen = reportState.isOpen && !!reportState.reportId; const editorOpen = editorState.isOpen && @@ -278,11 +234,10 @@ export function RightPanel({ ]); const isVisible = - (documentsOpen || reportOpen || editorOpen || hitlEditOpen || citationOpen || artifactsOpen) && - !collapsed; + (reportOpen || editorOpen || hitlEditOpen || citationOpen || artifactsOpen) && !collapsed; const effectiveTab = resolveEffectiveTab(activeTab, { - sources: documentsOpen, + sources: false, report: reportOpen, editor: editorOpen, "hitl-edit": hitlEditOpen, @@ -291,10 +246,6 @@ export function RightPanel({ }); const targetWidth = PANEL_WIDTHS[effectiveTab]; - const collapseButton = showCollapseButton ? ( - setCollapsed(true)} /> - ) : null; - if (!isVisible) return null; return ( @@ -306,16 +257,6 @@ export function RightPanel({ )} >
- {effectiveTab === "sources" && documentsOpen && documentsPanel && ( -
- -
- )} {effectiveTab === "report" && reportOpen && (
- {/* Right panel — tabbed Sources/Report (desktop only) */} - {documentsPanel ? ( - - ) : null} + {/* Right panel — Report/Editor/Citations/Artifacts (desktop only) */} + )} diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 250f4e467..094ecab46 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -12,7 +12,6 @@ import { Paperclip, Server, Trash2, - Unplug, Upload, X, } from "lucide-react"; @@ -25,8 +24,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { agentFlagsAtom } from "@/atoms/agent/agent-flags-query.atom"; import { makeFolderMention, mentionedDocumentsAtom } from "@/atoms/chat/mentioned-documents.atom"; -import { connectorDialogOpenAtom } from "@/atoms/connector-dialog/connector-dialog.atoms"; -import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; import { deleteDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms"; import { expandedFolderIdsAtom } from "@/atoms/documents/folder.atoms"; import { agentCreatedDocumentsAtom } from "@/atoms/documents/ui.atoms"; @@ -58,7 +55,6 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { Drawer, DrawerContent, DrawerHandle, DrawerTitle } from "@/components/ui/drawer"; import { Skeleton } from "@/components/ui/skeleton"; @@ -67,7 +63,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useAnonymousMode, useIsAnonymous } from "@/contexts/anonymous-mode"; import { useLoginGate } from "@/contexts/login-gate"; -import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { DocumentTypeEnum } from "@/contracts/types/document.types"; import { useDebouncedValue } from "@/hooks/use-debounced-value"; import { useMediaQuery } from "@/hooks/use-media-query"; @@ -167,18 +162,6 @@ interface WatchedFolderEntry { active: boolean; } -const SHOWCASE_CONNECTORS = [ - { type: "GOOGLE_DRIVE_CONNECTOR", label: "Google Drive" }, - { type: "GOOGLE_GMAIL_CONNECTOR", label: "Gmail" }, - { type: "NOTION_CONNECTOR", label: "Notion" }, - { type: "YOUTUBE_CONNECTOR", label: "YouTube" }, - { type: "GOOGLE_CALENDAR_CONNECTOR", label: "Google Calendar" }, - { type: "SLACK_CONNECTOR", label: "Slack" }, - { type: "LINEAR_CONNECTOR", label: "Linear" }, - { type: "JIRA_CONNECTOR", label: "Jira" }, - { type: "GITHUB_CONNECTOR", label: "GitHub" }, -] as const; - interface DocumentsSidebarProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -228,11 +211,8 @@ function AuthenticatedDocumentsSidebarBase({ const electronAPI = desktopFeaturesEnabled ? platformElectronAPI : null; const { etlService } = useRuntimeConfig(); const searchSpaceId = getWorkspaceIdNumber(params) ?? 0; - const setConnectorDialogOpen = useSetAtom(connectorDialogOpenAtom); const openEditorPanel = useSetAtom(openEditorPanelAtom); const { data: agentFlags } = useAtomValue(agentFlagsAtom); - const { data: connectors } = useAtomValue(connectorsAtom); - const connectorCount = connectors?.length ?? 0; const [search, setSearch] = useState(""); const debouncedSearch = useDebouncedValue(search, 250); @@ -1116,76 +1096,9 @@ function AuthenticatedDocumentsSidebarBase({ const showCloudSkeleton = currentFilesystemTab === "cloud" && (zeroFoldersResult.type !== "complete" || zeroAllDocsResult.type !== "complete"); - const connectorButtonLabel = connectorCount > 0 ? "Manage connectors" : "Connect your connectors"; const cloudContent = ( <> - {/* Connected tools strip */} - - {isElectron && (
- {/* Connectors strip (gated) */} - - {/* Filters & upload */}
@@ -1873,7 +1747,6 @@ function AnonymousDocumentsSidebar({ onToggleType={() => {}} activeTypes={[]} onCreateFolder={() => gate("create folders")} - onUploadClick={handleAnonUploadClick} />
diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index 6f17a95ce..589c82eb8 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -16,6 +16,7 @@ import { SIDEBAR_MIN_WIDTH } from "../../hooks/useSidebarResize"; import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types"; import { ChatListItem } from "./ChatListItem"; import { CreditBalanceDisplay } from "./CreditBalanceDisplay"; +import { DocumentsSidebar } from "./DocumentsSidebar"; import { NavSection } from "./NavSection"; import { SidebarButton } from "./SidebarButton"; import { SidebarCollapseButton } from "./SidebarCollapseButton"; @@ -76,6 +77,10 @@ interface SidebarProps { onChatArchive?: (chat: ChatItem) => void; onViewAllChats?: () => void; isAllChatsActive?: boolean; + documentsPanel?: { + open: boolean; + onOpenChange: (open: boolean) => void; + }; user: User; onSettings?: () => void; onManageMembers?: () => void; @@ -113,6 +118,7 @@ export function Sidebar({ onChatArchive, onViewAllChats, isAllChatsActive = false, + documentsPanel, user, onSettings, onManageMembers, @@ -136,11 +142,9 @@ export function Sidebar({ const t = useTranslations("sidebar"); const [openDropdownChatId, setOpenDropdownChatId] = useState(null); - // Inbox, Automations, and Documents are rendered explicitly right below + // Inbox, Automations, and Artifacts are rendered explicitly right below // New Chat. Pull them out of the nav items list so they don't also appear - // in the bottom NavSection. Documents is only present in navItems on - // mobile; Automations is identified by URL suffix so the same code path - // works across search spaces. + // in the bottom NavSection. Documents is embedded below Recents. const inboxItem = useMemo(() => navItems.find((item) => item.url === "#inbox"), [navItems]); const automationsItem = useMemo( () => navItems.find((item) => item.url.endsWith("/automations")), @@ -280,7 +284,10 @@ export function Sidebar({ {t("no_chats")}

)}
+ {documentsPanel?.open ? ( +
+ +
+ ) : null}
)}