From 79f5e8f88c22afba5075dac7b5302bb06bb285c5 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 22:40:22 +0530 Subject: [PATCH] feat(web): add connector display definitions and enhance composer suggestion components --- .../constants/connector-constants.ts | 15 ++- .../new-chat/composer-suggestion-popup.tsx | 43 +++++-- .../new-chat/document-mention-picker.tsx | 119 +++++++++--------- 3 files changed, 105 insertions(+), 72 deletions(-) diff --git a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts index 01a911d70..dedb35465 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts @@ -1,5 +1,4 @@ import { EnumConnectorName } from "@/contracts/enums/connector"; -import type { SearchSourceConnector } from "@/contracts/types/connector.types"; /** * Connectors that operate in real time (no background indexing). @@ -230,6 +229,20 @@ export const COMPOSIO_CONNECTORS = [ }, ] as const; +export const CONNECTOR_DISPLAY_DEFINITIONS = [ + ...OAUTH_CONNECTORS, + ...CRAWLERS, + ...OTHER_CONNECTORS, + ...COMPOSIO_CONNECTORS, +] as const; + +export function getConnectorTitle(connectorType: string): string { + return ( + CONNECTOR_DISPLAY_DEFINITIONS.find((connector) => connector.connectorType === connectorType) + ?.title ?? connectorType + ); +} + // Composio Toolkits (available integrations via Composio) export const COMPOSIO_TOOLKITS = [ { diff --git a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx index fd46d8ee7..d72cf1366 100644 --- a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx +++ b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx @@ -31,7 +31,7 @@ function ComposerSuggestionPopoverContent({ onCloseAutoFocus?.(event); }} className={cn( - "w-[280px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[320px]", + "w-[256px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[288px]", "data-[state=open]:!animate-none data-[state=closed]:!animate-none data-[state=open]:!duration-0 data-[state=closed]:!duration-0", className )} @@ -47,14 +47,14 @@ const ComposerSuggestionList = React.forwardRef< >(({ className, ...props }, ref) => (
)); ComposerSuggestionList.displayName = "ComposerSuggestionList"; function ComposerSuggestionGroup({ className, ...props }: React.HTMLAttributes) { - return
; + return
; } function ComposerSuggestionGroupHeading({ @@ -63,12 +63,32 @@ function ComposerSuggestionGroupHeading({ }: React.HTMLAttributes) { return (
); } +function ComposerSuggestionHeader({ + className, + icon, + children, + ...props +}: React.HTMLAttributes & { icon?: React.ReactNode }) { + return ( +
+ {icon ? {icon} : null} + {children} +
+ ); +} + const ComposerSuggestionItem = React.forwardRef< HTMLButtonElement, Omit, "variant"> & { @@ -83,7 +103,7 @@ const ComposerSuggestionItem = React.forwardRef< variant="ghost" disabled={disabled} className={cn( - "h-auto w-full justify-start gap-2 rounded-md px-3 py-2 text-left text-sm font-normal transition-colors", + "h-auto w-full justify-start gap-2 rounded-md px-2.5 py-1.5 text-left text-sm font-normal transition-colors", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", muted && !selected && "text-muted-foreground hover:bg-accent hover:text-accent-foreground", selected && "bg-accent text-accent-foreground", @@ -99,7 +119,7 @@ ComposerSuggestionItem.displayName = "ComposerSuggestionItem"; function ComposerSuggestionSeparator({ className, ...props }: React.ComponentProps) { return ( -
+
); @@ -111,10 +131,10 @@ function ComposerSuggestionMessage({ variant = "muted", }: React.HTMLAttributes & { variant?: "muted" | "destructive" }) { return ( -
+

-

+
+
{["a", "b", "c", "d", "e"].map((id, index) => (
= 3 && "hidden sm:flex" )} > @@ -156,6 +176,7 @@ export { ComposerSuggestionList, ComposerSuggestionGroup, ComposerSuggestionGroupHeading, + ComposerSuggestionHeader, ComposerSuggestionItem, ComposerSuggestionSeparator, ComposerSuggestionMessage, diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index c424ae6c3..c6829507f 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -8,7 +8,7 @@ import { ChevronRight, Files, Folder as FolderIcon, - Plug, + Unplug, } from "lucide-react"; import { forwardRef, @@ -23,14 +23,12 @@ import type * as React from "react"; import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; import { useAtomValue } from "jotai"; import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; -import { - COMPOSIO_CONNECTORS, - OAUTH_CONNECTORS, -} from "@/components/assistant-ui/connector-popup/constants/connector-constants"; +import { getConnectorTitle } from "@/components/assistant-ui/connector-popup/constants/connector-constants"; import { getConnectorDisplayName } from "@/components/assistant-ui/connector-popup/tabs/all-connectors-tab"; import { ComposerSuggestionGroup, ComposerSuggestionGroupHeading, + ComposerSuggestionHeader, ComposerSuggestionItem, ComposerSuggestionList, ComposerSuggestionMessage, @@ -94,19 +92,6 @@ function useDebounced(value: T, delay = DEBOUNCE_MS) { return debounced; } -function titleForConnectorType(connectorType: string) { - const configured = - OAUTH_CONNECTORS.find((c) => c.connectorType === connectorType) || - COMPOSIO_CONNECTORS.find((c) => c.connectorType === connectorType); - return ( - configured?.title || - connectorType - .replace(/_/g, " ") - .replace(/connector/gi, "") - .trim() - ); -} - function makeDocMention(doc: Pick): MentionedDocumentInfo { return { id: doc.id, @@ -130,7 +115,7 @@ function makeConnectorMention( connector: SearchSourceConnector ): Extract { const accountName = getConnectorDisplayName(connector.name); - const connectorTitle = titleForConnectorType(connector.connector_type); + const connectorTitle = getConnectorTitle(connector.connector_type); return { id: connector.id, title: `${connectorTitle}: ${accountName}`, @@ -319,6 +304,7 @@ export const DocumentMentionPicker = forwardRef< () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), [initialSelectedDocuments] ); + const showSurfsenseDocsRootRef = useRef((surfsenseDocs?.items?.length ?? 0) > 0); const selectMention = useCallback( (mention: MentionedDocumentInfo) => { @@ -329,35 +315,41 @@ export const DocumentMentionPicker = forwardRef< ); const rootNodes = useMemo[]>( - () => [ - { - id: "surfsense-docs", - label: "SurfSense Docs", - subtitle: "Browse product documentation", - icon: , - type: "branch", - value: { kind: "view", view: { kind: "surfsense-docs" } }, - }, - { - id: "files-folders", - label: "Files & Folders", - subtitle: "Browse your knowledge base", - icon: , - type: "branch", - value: { kind: "view", view: { kind: "files-folders" } }, - }, - { - id: "connectors", - label: "Connectors", - subtitle: activeConnectors.length - ? "Choose the exact account for tool use" - : "No connected accounts yet", - icon: , - type: "branch", - disabled: activeConnectors.length === 0, - value: { kind: "view", view: { kind: "connectors" } }, - }, - ], + () => { + const nodes: ComposerSuggestionNode[] = []; + if (showSurfsenseDocsRootRef.current) { + nodes.push({ + id: "surfsense-docs", + label: "SurfSense Docs", + subtitle: "Browse product documentation", + icon: , + type: "branch", + value: { kind: "view", view: { kind: "surfsense-docs" } }, + }); + } + nodes.push( + { + id: "files-folders", + label: "Files & Folders", + subtitle: "Browse your knowledge base", + icon: , + type: "branch", + value: { kind: "view", view: { kind: "files-folders" } }, + }, + { + id: "connectors", + label: "Connectors", + subtitle: activeConnectors.length + ? "Choose the exact account for tool use" + : "No connected accounts yet", + icon: , + type: "branch", + disabled: activeConnectors.length === 0, + value: { kind: "view", view: { kind: "connectors" } }, + } + ); + return nodes; + }, [activeConnectors.length] ); @@ -389,7 +381,7 @@ export const DocumentMentionPicker = forwardRef< id: getMentionDocKey(mention), label: mention.title, subtitle: "Connector account", - icon: getConnectorIcon(mention.connector_type, "size-4") ?? , + icon: getConnectorIcon(mention.connector_type, "size-4") ?? , type: "item" as const, disabled: selectedKeys.has(getMentionDocKey(mention)), value: { kind: "mention" as const, mention }, @@ -414,7 +406,7 @@ export const DocumentMentionPicker = forwardRef< byType.set(connector.connector_type, list); } return Array.from(byType.entries()).sort(([a], [b]) => - titleForConnectorType(a).localeCompare(titleForConnectorType(b)) + getConnectorTitle(a).localeCompare(getConnectorTitle(b)) ); }, [activeConnectors]); @@ -459,16 +451,16 @@ export const DocumentMentionPicker = forwardRef< if (view.kind === "connectors") { return connectorTypeEntries.map(([connectorType, typeConnectors]) => ({ id: `connector-type:${connectorType}`, - label: titleForConnectorType(connectorType), + label: getConnectorTitle(connectorType), subtitle: `${typeConnectors.length} ${typeConnectors.length === 1 ? "account" : "accounts"}`, - icon: getConnectorIcon(connectorType, "size-4") ?? , + icon: getConnectorIcon(connectorType, "size-4") ?? , type: "branch" as const, value: { kind: "view" as const, view: { kind: "connector-type" as const, connectorType, - title: titleForConnectorType(connectorType), + title: getConnectorTitle(connectorType), }, }, })); @@ -481,7 +473,7 @@ export const DocumentMentionPicker = forwardRef< id: getMentionDocKey(mention), label: getConnectorDisplayName(connector.name), subtitle: `${view.title} account`, - icon: getConnectorIcon(connector.connector_type, "size-4") ?? , + icon: getConnectorIcon(connector.connector_type, "size-4") ?? , type: "item" as const, disabled: selectedKeys.has(getMentionDocKey(mention)), value: { kind: "mention" as const, mention }, @@ -571,13 +563,20 @@ export const DocumentMentionPicker = forwardRef< {title ? ( <> - } - muted - onClick={handleBack} + + + + } > - {title} - + {title} + ) : null}