From a3b22e61e5fc754ee2a65c40f60a7b12e8361805 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:57:16 +0530 Subject: [PATCH] refactor: update connector navigation to use router.back() for improved user experience --- .../add/airtable-connector/page.tsx | 2 +- .../connectors/add/baidu-search-api/page.tsx | 2 +- .../add/bookstack-connector/page.tsx | 2 +- .../connectors/add/clickup-connector/page.tsx | 2 +- .../add/confluence-connector/page.tsx | 2 +- .../connectors/add/discord-connector/page.tsx | 2 +- .../add/elasticsearch-connector/page.tsx | 2 +- .../connectors/add/github-connector/page.tsx | 2 +- .../add/google-calendar-connector/page.tsx | 2 +- .../add/google-gmail-connector/page.tsx | 2 +- .../connectors/add/jira-connector/page.tsx | 2 +- .../connectors/add/linear-connector/page.tsx | 2 +- .../connectors/add/linkup-api/page.tsx | 2 +- .../connectors/add/luma-connector/page.tsx | 2 +- .../connectors/add/notion-connector/page.tsx | 2 +- .../connectors/add/searxng/page.tsx | 2 +- .../connectors/add/slack-connector/page.tsx | 2 +- .../connectors/add/tavily-api/page.tsx | 2 +- .../add/webcrawler-connector/page.tsx | 2 +- .../components/assistant-ui/thread.tsx | 223 ++++++++++++++++-- 20 files changed, 228 insertions(+), 33 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx index cc4330203..7ebbeec3f 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/airtable-connector/page.tsx @@ -123,7 +123,7 @@ export default function AirtableConnectorPage() { diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/baidu-search-api/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/baidu-search-api/page.tsx index 3e9f4898e..e1a84a8ab 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/baidu-search-api/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/baidu-search-api/page.tsx @@ -126,7 +126,7 @@ export default function BaiduSearchApiPage() { diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx index 8659d937c..41b9e42f8 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/google-gmail-connector/page.tsx @@ -133,7 +133,7 @@ export default function GoogleGmailConnectorPage() { diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx index 6f4e31114..132c58521 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/jira-connector/page.tsx @@ -125,7 +125,7 @@ export default function JiraConnectorPage() { diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx index 310c31811..5cfd16a5f 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/add/notion-connector/page.tsx @@ -105,7 +105,7 @@ export default function NotionConnectorPage() { diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index b33ad3c87..1296ab28f 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -21,14 +21,22 @@ import { DownloadIcon, Loader2, PencilIcon, + Plug2, + Plus, RefreshCwIcon, Search, Sparkles, SquareIcon, } from "lucide-react"; import Image from "next/image"; -import type { FC } from "react"; +import Link from "next/link"; +import { type FC, useState, useRef, useCallback } from "react"; import { useAtomValue } from "jotai"; +import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; +import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors"; +import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { connectorCategories } from "@/components/sources/connector-data"; import { ComposerAddAttachment, ComposerAttachments, @@ -197,14 +205,12 @@ const getTimeBasedGreeting = (userEmail?: string): string => { "Rise and shine", "Morning", "Hey there", - "Welcome back", ]; const afternoonGreetings = [ "Good afternoon", "Afternoon", "Hey there", - "Welcome back", "Hope you're having a great day", ]; @@ -212,27 +218,36 @@ const getTimeBasedGreeting = (userEmail?: string): string => { "Good evening", "Evening", "Hey there", - "Welcome back", "Hope you had a great day", ]; const nightGreetings = [ - "Late night", - "Still up", + "Good night", + "Evening", "Hey there", - "Welcome back", + "Winding down", + ]; + + const lateNightGreetings = [ "Burning the midnight oil", + "Still up", + "Night owl mode", + "The night is young", ]; // Select a random greeting based on time let greeting: string; - if (hour < 12) { + if (hour < 5) { + // Late night: midnight to 5 AM + greeting = lateNightGreetings[Math.floor(Math.random() * lateNightGreetings.length)]; + } else if (hour < 12) { greeting = morningGreetings[Math.floor(Math.random() * morningGreetings.length)]; - } else if (hour < 17) { + } else if (hour < 18) { greeting = afternoonGreetings[Math.floor(Math.random() * afternoonGreetings.length)]; - } else if (hour < 21) { + } else if (hour < 22) { greeting = eveningGreetings[Math.floor(Math.random() * eveningGreetings.length)]; } else { + // Night: 10 PM to midnight greeting = nightGreetings[Math.floor(Math.random() * nightGreetings.length)]; } @@ -251,7 +266,7 @@ const ThreadWelcome: FC = () => {
{/* Greeting positioned near the composer */}
-

+

{/** biome-ignore lint/a11y/noStaticElementInteractions: wrong lint error, this is a workaround to fix the lint error */}
{ SurfSense { ); }; +const ConnectorIndicator: FC = () => { + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const { connectors, isLoading } = useSearchSourceConnectors(false, searchSpaceId ? Number(searchSpaceId) : undefined); + const [isOpen, setIsOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); + const closeTimeoutRef = useRef(null); + + const hasConnectors = connectors.length > 0; + + // Get connected connector types for comparison + const connectedTypes = new Set(connectors.map(c => c.connector_type)); + + // Flatten all available connectors from categories + const allAvailableConnectors = connectorCategories.flatMap(category => + category.connectors.filter(c => c.status === "available") + ); + + // Filter connectors based on search query + const filteredConnectors = allAvailableConnectors.filter(connector => + connector.title.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // Filter connected connectors based on search query + const filteredConnectedConnectors = connectors.filter(connector => + connector.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // Filter available (not connected) connectors + const filteredAvailableConnectors = filteredConnectors.filter(connector => { + // Map connector id to connector_type for comparison + const connectorTypeMap: Record = { + "webcrawler-connector": "WEBCRAWLER_CONNECTOR", + "tavily-api": "TAVILY_API", + "searxng": "SEARXNG_API", + "linkup-api": "LINKUP_API", + "baidu-search-api": "BAIDU_SEARCH_API", + "slack-connector": "SLACK_CONNECTOR", + "discord-connector": "DISCORD_CONNECTOR", + "linear-connector": "LINEAR_CONNECTOR", + "jira-connector": "JIRA_CONNECTOR", + "clickup-connector": "CLICKUP_CONNECTOR", + "notion-connector": "NOTION_CONNECTOR", + "confluence-connector": "CONFLUENCE_CONNECTOR", + "bookstack-connector": "BOOKSTACK_CONNECTOR", + "github-connector": "GITHUB_CONNECTOR", + "elasticsearch-connector": "ELASTICSEARCH_CONNECTOR", + "airtable-connector": "AIRTABLE_CONNECTOR", + "google-calendar-connector": "GOOGLE_CALENDAR_CONNECTOR", + "google-gmail-connector": "GOOGLE_GMAIL_CONNECTOR", + "luma-connector": "LUMA_CONNECTOR", + }; + const connectorType = connectorTypeMap[connector.id]; + return !connectorType || !connectedTypes.has(connectorType); + }); + + const handleMouseEnter = useCallback(() => { + // Clear any pending close timeout + if (closeTimeoutRef.current) { + clearTimeout(closeTimeoutRef.current); + closeTimeoutRef.current = null; + } + setIsOpen(true); + }, []); + + const handleMouseLeave = useCallback(() => { + // Delay closing by 150ms for better UX + closeTimeoutRef.current = setTimeout(() => { + setIsOpen(false); + setSearchQuery(""); // Reset search when closing + }, 150); + }, []); + + if (!searchSpaceId) return null; + + return ( + + + + + +
+ {/* Search input - sticky at top */} +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-8 pr-3 py-1.5 text-sm bg-transparent border-none outline-none focus:ring-0 placeholder:text-muted-foreground" + /> +
+
+ + {/* Connectors list - scrollable */} +
+ {/* Connected connectors first */} + {filteredConnectedConnectors.length > 0 && ( + <> + {filteredConnectedConnectors.map((connector) => ( + +
+
+ {getConnectorIcon(connector.connector_type, "size-5")} +
+ {connector.name} +
+ + + ))} + {filteredAvailableConnectors.length > 0 && ( +
+ )} + + )} + + {/* Available connectors */} + {filteredAvailableConnectors.length > 0 ? ( + filteredAvailableConnectors.map((connector) => ( + +
+
+ {connector.icon} +
+ {connector.title} +
+ + + )) + ) : filteredConnectedConnectors.length === 0 ? ( +
+ No connectors found +
+ ) : null} +
+
+ + + ); +}; + const ComposerAction: FC = () => { // Check if any attachments are still being processed (running AND progress < 100) // When progress is 100, processing is done but waiting for send() @@ -329,7 +521,10 @@ const ComposerAction: FC = () => { return (
- +
+ + +
{/* Show processing indicator when attachments are being processed */} {hasProcessingAttachments && (