From 95f95558dc2153c5428f6c0f731f318cad292611 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Wed, 31 Dec 2025 14:04:29 +0530 Subject: [PATCH] feat: Remove YouTube connector support and add Linear connector benefits functionality, enhance Linear connect form with benefits display and accordion documentation section. --- .../assistant-ui/connector-popup.tsx | 2 - .../components/linear-connect-form.tsx | 149 +++++++++++++++++- .../connect-forms/connector-benefits.ts | 21 +++ .../components/youtube-config.tsx | 148 ----------------- .../connector-configs/index.tsx | 3 - .../views/connector-edit-view.tsx | 28 ++-- .../views/indexing-configuration-view.tsx | 28 ++-- .../constants/connector-constants.ts | 6 - .../hooks/use-connector-dialog.ts | 63 +------- .../tabs/all-connectors-tab.tsx | 15 +- 10 files changed, 208 insertions(+), 255 deletions(-) create mode 100644 surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts delete mode 100644 surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/youtube-config.tsx diff --git a/surfsense_web/components/assistant-ui/connector-popup.tsx b/surfsense_web/components/assistant-ui/connector-popup.tsx index 26a0836c8..f51a80f98 100644 --- a/surfsense_web/components/assistant-ui/connector-popup.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup.tsx @@ -75,7 +75,6 @@ export const ConnectorIndicator: FC = () => { handleConnectOAuth, handleConnectNonOAuth, handleCreateWebcrawler, - handleCreateYouTube, handleSubmitConnectForm, handleStartIndexing, handleSkipIndexing, @@ -272,7 +271,6 @@ export const ConnectorIndicator: FC = () => { onConnectOAuth={handleConnectOAuth} onConnectNonOAuth={handleConnectNonOAuth} onCreateWebcrawler={handleCreateWebcrawler} - onCreateYouTube={handleCreateYouTube} onManage={handleStartEdit} /> diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/linear-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/linear-connect-form.tsx index a87591ee1..3e1bb370a 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/linear-connect-form.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/linear-connect-form.tsx @@ -6,6 +6,12 @@ import type { FC } from "react"; import { useRef } from "react"; import { useForm } from "react-hook-form"; import * as z from "zod"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Form, @@ -19,6 +25,7 @@ import { import { Input } from "@/components/ui/input"; import { EnumConnectorName } from "@/contracts/enums/connector"; import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; const linearConnectorFormSchema = z.object({ name: z.string().min(3, { @@ -106,7 +113,7 @@ export const LinearConnectForm: FC = ({ @@ -129,7 +136,7 @@ export const LinearConnectForm: FC = ({ @@ -144,6 +151,144 @@ export const LinearConnectForm: FC = ({ + + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.LINEAR_CONNECTOR) && ( +
+

What you get with Linear integration:

+
    + {getConnectorBenefits(EnumConnectorName.LINEAR_CONNECTOR)?.map((benefit, i) => ( +
  • {benefit}
  • + ))} +
+
+ )} + + {/* Documentation Section */} + + + + Documentation + + +
+

How it works

+

+ The Linear connector uses the Linear GraphQL API to fetch all issues and + comments that the API key has access to within a workspace. +

+
    +
  • + For follow up indexing runs, the connector retrieves issues and comments that + have been updated since the last indexing attempt. +
  • +
  • + Indexing is configured to run periodically, so updates should appear in your + search results within minutes. +
  • +
+
+ +
+
+

Authorization

+ + + Read-Only Access is Sufficient + + You only need a read-only API key for this connector to work. This limits + the permissions to just reading your Linear data. + + + +
+
+

Step 1: Create an API key

+
    +
  1. Log in to your Linear account
  2. +
  3. + Navigate to{" "} + + https://linear.app/settings/api + {" "} + in your browser. +
  4. +
  5. Alternatively, click on your profile picture → Settings → API
  6. +
  7. + Click the + New API key button. +
  8. +
  9. Enter a description for your key (like "Search Connector").
  10. +
  11. Select "Read-only" as the permission.
  12. +
  13. + Click Create to generate the API key. +
  14. +
  15. + Copy the generated API key that starts with 'lin_api_' as it will only + be shown once. +
  16. +
+
+ +
+

Step 2: Grant necessary access

+

+ The API key will have access to all issues and comments that your user + account can see. If you're creating the key as an admin, it will have + access to all issues in the workspace. +

+ + + Data Privacy + + Only issues and comments will be indexed. Linear attachments and + linked files are not indexed by this connector. + + +
+
+
+
+ +
+
+

Indexing

+
    +
  1. + Navigate to the Connector Dashboard and select the Linear{" "} + Connector. +
  2. +
  3. + Place the API Key in the form field. +
  4. +
  5. + Click Connect to establish the connection. +
  6. +
  7. Once connected, your Linear issues will be indexed automatically.
  8. +
+ + + + What Gets Indexed + +

The Linear connector indexes the following data:

+
    +
  • Issue titles and identifiers (e.g., PROJ-123)
  • +
  • Issue descriptions
  • +
  • Issue comments
  • +
  • Issue status and metadata
  • +
+
+
+
+
+
+
+
); }; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts new file mode 100644 index 000000000..3851a2692 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/connector-benefits.ts @@ -0,0 +1,21 @@ +/** + * Helper function to get connector-specific benefits list + * Returns null if no benefits are defined for the connector + */ +export function getConnectorBenefits(connectorType: string): string[] | null { + const benefits: Record = { + LINEAR_CONNECTOR: [ + "Search through all your Linear issues and comments", + "Access issue titles, descriptions, and full discussion threads", + "Connect your team's project management directly to your search space", + "Keep your search results up-to-date with latest Linear content", + "Index your Linear issues for enhanced search capabilities", + ], + // Add other connectors as needed + // TAVILY_API: [...], + // GITHUB_CONNECTOR: [...], + }; + + return benefits[connectorType] || null; +} + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/youtube-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/youtube-config.tsx deleted file mode 100644 index 5fe7f0c48..000000000 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/youtube-config.tsx +++ /dev/null @@ -1,148 +0,0 @@ -"use client"; - -import { IconBrandYoutube } from "@tabler/icons-react"; -import { Info } from "lucide-react"; -import { TagInput, type Tag as TagType } from "emblor"; -import { useState, useEffect } from "react"; -import type { FC } from "react"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; -import { Label } from "@/components/ui/label"; -import { toast } from "sonner"; -import type { ConnectorConfigProps } from "../index"; - -const youtubeRegex = - /^(https:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})$/; - -export const YouTubeConfig: FC = ({ - connector, - onConfigChange, -}) => { - // Initialize with existing YouTube URLs from connector config - const existingUrls = (connector.config?.youtube_urls as string[] | undefined) || []; - const [youtubeTags, setYoutubeTags] = useState( - existingUrls.map((url) => ({ - id: url, - text: url, - })) - ); - const [activeTagIndex, setActiveTagIndex] = useState(null); - - // Update YouTube tags when connector config changes - useEffect(() => { - const urls = (connector.config?.youtube_urls as string[] | undefined) || []; - setYoutubeTags( - urls.map((url) => ({ - id: url, - text: url, - })) - ); - }, [connector.config]); - - const isValidYoutubeUrl = (url: string): boolean => { - return youtubeRegex.test(url); - }; - - const handleTagsChange = (tags: TagType[]) => { - setYoutubeTags(tags); - if (onConfigChange) { - // Extract URLs from tags and validate - const urls = tags.map((tag) => tag.text).filter(isValidYoutubeUrl); - onConfigChange({ - ...connector.config, - youtube_urls: urls, - }); - } - }; - - const handleAddTag = (text: string) => { - if (!isValidYoutubeUrl(text)) { - toast("Invalid YouTube URL", { - description: "Please enter a valid YouTube video URL (youtube.com/watch?v= or youtu.be/)", - }); - return; - } - - if (youtubeTags.some((tag) => tag.text === text)) { - toast("Duplicate URL", { - description: "This YouTube video has already been added", - }); - return; - } - - const newTag: TagType = { - id: Date.now().toString(), - text: text, - }; - - handleTagsChange([...youtubeTags, newTag]); - }; - - return ( -
-
-

- - YouTube Video URLs -

-

- Add YouTube video URLs to index. Enter a URL and press Enter to add multiple videos. -

-
- -
- - -

- Add multiple YouTube URLs by pressing Enter after each one -

-
- - {youtubeTags.length > 0 && ( -
-

- {youtubeTags.length} video{youtubeTags.length > 1 ? "s" : ""} added -

-
- )} - -
-

Tips for adding YouTube videos:

-
    -
  • Use standard YouTube URLs (youtube.com/watch?v= or youtu.be/)
  • -
  • Make sure videos are publicly accessible
  • -
  • Supported formats: youtube.com/watch?v=VIDEO_ID or youtu.be/VIDEO_ID
  • -
  • Processing may take some time depending on video length
  • -
-
- - - - - YouTube URLs are used when indexing. You can change this selection when you start indexing. - - -
- ); -}; - diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx index 199d191ea..71c549403 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/index.tsx @@ -6,7 +6,6 @@ import { GoogleDriveConfig } from "./components/google-drive-config"; import { LinearConfig } from "./components/linear-config"; import { TavilyApiConfig } from "./components/tavily-api-config"; import { WebcrawlerConfig } from "./components/webcrawler-config"; -import { YouTubeConfig } from "./components/youtube-config"; export interface ConnectorConfigProps { connector: SearchSourceConnector; @@ -31,8 +30,6 @@ export function getConnectorConfigComponent( return LinearConfig; case "WEBCRAWLER_CONNECTOR": return WebcrawlerConfig; - case "YOUTUBE_CONNECTOR": - return YouTubeConfig; // OAuth connectors (Gmail, Calendar, Airtable) and others don't need special config UI default: return null; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx index 290b08825..5fda12a7a 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx @@ -155,8 +155,8 @@ export const ConnectorEditView: FC = ({ {/* Date range selector and periodic sync - only shown for indexable connectors */} {connector.is_indexable && ( <> - {/* Date range selector - not shown for Google Drive (uses folder selection), Webcrawler (uses config), or YouTube (uses URL selection) */} - {connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && connector.connector_type !== "WEBCRAWLER_CONNECTOR" && connector.connector_type !== "YOUTUBE_CONNECTOR" && ( + {/* Date range selector - not shown for Google Drive (uses folder selection) or Webcrawler (uses config) */} + {connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && connector.connector_type !== "WEBCRAWLER_CONNECTOR" && ( = ({ )} - {/* Info box */} -
-
- + {/* Info box - only shown for indexable connectors */} + {connector.is_indexable && ( +
+
+ +
+
+

Re-indexing runs in the background

+

+ You can continue using SurfSense while we sync your data. Check the Active tab to see progress. +

+
-
-

Re-indexing runs in the background

-

- You can continue using SurfSense while we sync your data. Check the Active tab to see progress. -

-
-
+ )}
{/* Top fade shadow - appears when scrolled */} diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx index 1b49b2046..2a82b1b75 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/indexing-configuration-view.tsx @@ -136,8 +136,8 @@ export const IndexingConfigurationView: FC = ({ {/* Date range selector and periodic sync - only shown for indexable connectors */} {connector?.is_indexable && ( <> - {/* Date range selector - not shown for Google Drive (uses folder selection), Webcrawler (uses config), or YouTube (uses URL selection) */} - {config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && config.connectorType !== "WEBCRAWLER_CONNECTOR" && config.connectorType !== "YOUTUBE_CONNECTOR" && ( + {/* Date range selector - not shown for Google Drive (uses folder selection) or Webcrawler (uses config) */} + {config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && config.connectorType !== "WEBCRAWLER_CONNECTOR" && ( = ({ )} - {/* Info box */} -
-
- + {/* Info box - only shown for indexable connectors */} + {connector?.is_indexable && ( +
+
+ +
+
+

Indexing runs in the background

+

+ You can continue using SurfSense while we sync your data. Check the Active tab to see progress. +

+
-
-

Indexing runs in the background

-

- You can continue using SurfSense while we sync your data. Check the Active tab to see progress. -

-
-
+ )}
{/* Top fade shadow - appears when scrolled */} 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 c233b9f29..189b5cc5c 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 @@ -106,12 +106,6 @@ export const OTHER_CONNECTORS = [ description: "Crawl web content", connectorType: EnumConnectorName.WEBCRAWLER_CONNECTOR, }, - { - id: "youtube-connector", - title: "YouTube", - description: "Index YouTube videos", - connectorType: EnumConnectorName.YOUTUBE_CONNECTOR, - }, { id: "tavily-api", title: "Tavily AI", diff --git a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts index c3dbf1b4c..905e7cf32 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts @@ -325,61 +325,6 @@ export const useConnectorDialog = () => { } }, [searchSpaceId, createConnector, refetchAllConnectors]); - // Handle creating YouTube connector - const handleCreateYouTube = useCallback(async () => { - if (!searchSpaceId) return; - - setConnectingId("youtube-connector"); - try { - const newConnector = await createConnector({ - data: { - name: "YouTube", - connector_type: EnumConnectorName.YOUTUBE_CONNECTOR, - config: { youtube_urls: [] }, - is_indexable: true, - last_indexed_at: null, - periodic_indexing_enabled: false, - indexing_frequency_minutes: null, - next_scheduled_at: null, - }, - queryParams: { - search_space_id: searchSpaceId, - }, - }); - - // Refetch connectors to get the new one - const result = await refetchAllConnectors(); - if (result.data) { - const connector = result.data.find( - (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.YOUTUBE_CONNECTOR - ); - if (connector) { - const connectorValidation = searchSourceConnector.safeParse(connector); - if (connectorValidation.success) { - const config = validateIndexingConfigState({ - connectorType: EnumConnectorName.YOUTUBE_CONNECTOR, - connectorId: connector.id, - connectorTitle: "YouTube", - }); - setIndexingConfig(config); - setIndexingConnector(connector); - setIndexingConnectorConfig(connector.config || { youtube_urls: [] }); - setIsOpen(true); - const url = new URL(window.location.href); - url.searchParams.set("modal", "connectors"); - url.searchParams.set("view", "configure"); - window.history.pushState({ modal: true }, "", url.toString()); - } - } - } - } catch (error) { - console.error("Error creating YouTube connector:", error); - toast.error("Failed to create YouTube connector"); - } finally { - setConnectingId(null); - } - }, [searchSpaceId, createConnector, refetchAllConnectors]); - // Handle connecting non-OAuth connectors (like Tavily API) const handleConnectNonOAuth = useCallback((connectorType: string) => { if (!searchSpaceId) return; @@ -616,12 +561,13 @@ export const useConnectorDialog = () => { (oauthConnector) => oauthConnector.connectorType === connector.connector_type ); - // Check if this is webcrawler or Tavily API (can be managed in popup) + // Check if this is webcrawler, Tavily API, or Linear (can be managed in popup) const isWebcrawler = connector.connector_type === EnumConnectorName.WEBCRAWLER_CONNECTOR; const isTavilyApi = connector.connector_type === EnumConnectorName.TAVILY_API; + const isLinear = connector.connector_type === EnumConnectorName.LINEAR_CONNECTOR; - // If not OAuth, not webcrawler, and not Tavily API, redirect to old connector edit page - if (!isOAuthConnector && !isWebcrawler && !isTavilyApi) { + // If not OAuth, not webcrawler, not Tavily API, and not Linear, redirect to old connector edit page + if (!isOAuthConnector && !isWebcrawler && !isTavilyApi && !isLinear) { router.push(`/dashboard/${searchSpaceId}/connectors/${connector.id}/edit`); return; } @@ -898,7 +844,6 @@ export const useConnectorDialog = () => { handleConnectOAuth, handleConnectNonOAuth, handleCreateWebcrawler, - handleCreateYouTube, handleSubmitConnectForm, handleStartIndexing, handleSkipIndexing, 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 3928b2266..ef74c1269 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 @@ -15,7 +15,6 @@ interface AllConnectorsTabProps { onConnectOAuth: (connector: (typeof OAUTH_CONNECTORS)[0]) => void; onConnectNonOAuth?: (connectorType: string) => void; onCreateWebcrawler?: () => void; - onCreateYouTube?: () => void; onManage?: (connector: SearchSourceConnector) => void; } @@ -28,7 +27,6 @@ export const AllConnectorsTab: FC = ({ onConnectOAuth, onConnectNonOAuth, onCreateWebcrawler, - onCreateYouTube, onManage, }) => { const router = useRouter(); @@ -93,22 +91,21 @@ export const AllConnectorsTab: FC = ({
{filteredOther.map((connector) => { + // Special handling for connectors that can be created in popup + const isWebcrawler = connector.id === "webcrawler-connector"; + const isTavily = connector.id === "tavily-api"; + const isLinear = connector.id === "linear-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; - // Special handling for connectors that can be created in popup - const isWebcrawler = connector.id === "webcrawler-connector"; - const isYouTube = connector.id === "youtube-connector"; - const isTavily = connector.id === "tavily-api"; - const isLinear = connector.id === "linear-connector"; const handleConnect = isWebcrawler && onCreateWebcrawler ? onCreateWebcrawler - : isYouTube && onCreateYouTube - ? onCreateYouTube : (isTavily || isLinear) && onConnectNonOAuth ? () => onConnectNonOAuth(connector.connectorType) : () => router.push(`/dashboard/${searchSpaceId}/connectors/add/${connector.id}`);