feat: Remove YouTube connector support and add Linear connector benefits functionality, enhance Linear connect form with benefits display and accordion documentation section.

This commit is contained in:
Anish Sarkar 2025-12-31 14:04:29 +05:30
parent e7a60924ce
commit 95f95558dc
10 changed files with 208 additions and 255 deletions

View file

@ -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}
/>
</TabsContent>

View file

@ -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<ConnectFormProps> = ({
<FormControl>
<Input
placeholder="My Linear Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
/>
@ -129,7 +136,7 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
<Input
type="password"
placeholder="lin_api_..."
className="border-slate-400/20 focus-visible:border-slate-400/40"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
/>
@ -144,6 +151,144 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
</form>
</Form>
</div>
{/* What you get section */}
{getConnectorBenefits(EnumConnectorName.LINEAR_CONNECTOR) && (
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 px-3 sm:px-6 py-4 space-y-2">
<h4 className="text-xs sm:text-sm font-medium">What you get with Linear integration:</h4>
<ul className="list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
{getConnectorBenefits(EnumConnectorName.LINEAR_CONNECTOR)?.map((benefit, i) => (
<li key={i}>{benefit}</li>
))}
</ul>
</div>
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<AccordionItem value="documentation" className="border-0">
<AccordionTrigger className="text-sm sm:text-base font-medium px-3 sm:px-6 no-underline hover:no-underline">
Documentation
</AccordionTrigger>
<AccordionContent className="px-3 sm:px-6 pb-3 sm:pb-6 space-y-6">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3>
<p className="text-[10px] sm:text-xs text-muted-foreground">
The Linear connector uses the Linear GraphQL API to fetch all issues and
comments that the API key has access to within a workspace.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves issues and comments that
have been updated since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your
search results within minutes.
</li>
</ul>
</div>
<div className="space-y-4">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 mb-4">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Read-Only Access is Sufficient</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You only need a read-only API key for this connector to work. This limits
the permissions to just reading your Linear data.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Create an API key</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Log in to your Linear account</li>
<li>
Navigate to{" "}
<a
href="https://linear.app/settings/api"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
https://linear.app/settings/api
</a>{" "}
in your browser.
</li>
<li>Alternatively, click on your profile picture Settings API</li>
<li>
Click the <strong>+ New API key</strong> button.
</li>
<li>Enter a description for your key (like "Search Connector").</li>
<li>Select "Read-only" as the permission.</li>
<li>
Click <strong>Create</strong> to generate the API key.
</li>
<li>
Copy the generated API key that starts with 'lin_api_' as it will only
be shown once.
</li>
</ol>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Grant necessary access</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
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.
</p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
Only issues and comments will be indexed. Linear attachments and
linked files are not indexed by this connector.
</AlertDescription>
</Alert>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
Navigate to the Connector Dashboard and select the <strong>Linear</strong>{" "}
Connector.
</li>
<li>
Place the <strong>API Key</strong> in the form field.
</li>
<li>
Click <strong>Connect</strong> to establish the connection.
</li>
<li>Once connected, your Linear issues will be indexed automatically.</li>
</ol>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">What Gets Indexed</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
<p className="mb-2">The Linear connector indexes the following data:</p>
<ul className="list-disc pl-5 space-y-1">
<li>Issue titles and identifiers (e.g., PROJ-123)</li>
<li>Issue descriptions</li>
<li>Issue comments</li>
<li>Issue status and metadata</li>
</ul>
</AlertDescription>
</Alert>
</div>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
};

View file

@ -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<string, string[]> = {
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;
}

View file

@ -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<ConnectorConfigProps> = ({
connector,
onConfigChange,
}) => {
// Initialize with existing YouTube URLs from connector config
const existingUrls = (connector.config?.youtube_urls as string[] | undefined) || [];
const [youtubeTags, setYoutubeTags] = useState<TagType[]>(
existingUrls.map((url) => ({
id: url,
text: url,
}))
);
const [activeTagIndex, setActiveTagIndex] = useState<number | null>(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 (
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<div className="space-y-1 sm:space-y-2">
<h3 className="font-medium text-sm sm:text-base flex items-center gap-2">
<IconBrandYoutube className="h-4 w-4" />
YouTube Video URLs
</h3>
<p className="text-xs sm:text-sm text-muted-foreground">
Add YouTube video URLs to index. Enter a URL and press Enter to add multiple videos.
</p>
</div>
<div className="space-y-2">
<Label htmlFor="youtube-urls" className="text-xs sm:text-sm">
Enter YouTube Video URLs
</Label>
<TagInput
id="youtube-urls"
tags={youtubeTags}
setTags={handleTagsChange}
placeholder="Enter a YouTube URL and press Enter"
onAddTag={handleAddTag}
styleClasses={{
inlineTagsContainer:
"border-input rounded-lg bg-background shadow-sm shadow-black/5 transition-shadow focus-within:border-ring focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20 p-1 gap-1",
input: "w-full min-w-[80px] focus-visible:outline-none shadow-none px-2 h-7 text-xs sm:text-sm",
tag: {
body: "h-7 relative bg-background border border-input hover:bg-background rounded-md font-medium text-xs ps-2 pe-7 flex",
closeButton:
"absolute -inset-y-px -end-px p-0 rounded-e-lg flex size-7 transition-colors outline-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70 text-muted-foreground/80 hover:text-foreground",
},
}}
activeTagIndex={activeTagIndex}
setActiveTagIndex={setActiveTagIndex}
/>
<p className="text-[10px] sm:text-xs text-muted-foreground">
Add multiple YouTube URLs by pressing Enter after each one
</p>
</div>
{youtubeTags.length > 0 && (
<div className="p-2 sm:p-3 bg-muted rounded-lg text-xs sm:text-sm space-y-1 sm:space-y-2">
<p className="font-medium">
{youtubeTags.length} video{youtubeTags.length > 1 ? "s" : ""} added
</p>
</div>
)}
<div className="bg-muted/50 rounded-lg p-3 sm:p-4 text-xs sm:text-sm">
<h4 className="font-medium mb-2">Tips for adding YouTube videos:</h4>
<ul className="list-disc pl-5 space-y-1 text-muted-foreground">
<li>Use standard YouTube URLs (youtube.com/watch?v= or youtu.be/)</li>
<li>Make sure videos are publicly accessible</li>
<li>Supported formats: youtube.com/watch?v=VIDEO_ID or youtu.be/VIDEO_ID</li>
<li>Processing may take some time depending on video length</li>
</ul>
</div>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3 flex items-center gap-2 [&>svg]:relative [&>svg]:left-0 [&>svg]:top-0 [&>svg+div]:translate-y-0">
<Info className="h-3 w-3 sm:h-4 sm:w-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
YouTube URLs are used when indexing. You can change this selection when you start indexing.
</AlertDescription>
</Alert>
</div>
);
};

View file

@ -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;

View file

@ -155,8 +155,8 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
{/* 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" && (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
@ -174,18 +174,20 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
</>
)}
{/* Info box */}
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5">
<Info className="size-4" />
{/* Info box - only shown for indexable connectors */}
{connector.is_indexable && (
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5">
<Info className="size-4" />
</div>
<div className="text-xs sm:text-sm">
<p className="font-medium text-xs sm:text-sm">Re-indexing runs in the background</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
You can continue using SurfSense while we sync your data. Check the Active tab to see progress.
</p>
</div>
</div>
<div className="text-xs sm:text-sm">
<p className="font-medium text-xs sm:text-sm">Re-indexing runs in the background</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
You can continue using SurfSense while we sync your data. Check the Active tab to see progress.
</p>
</div>
</div>
)}
</div>
</div>
{/* Top fade shadow - appears when scrolled */}

View file

@ -136,8 +136,8 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
{/* 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" && (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
@ -155,18 +155,20 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
</>
)}
{/* Info box */}
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5">
<Info className="size-4" />
{/* Info box - only shown for indexable connectors */}
{connector?.is_indexable && (
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0 mt-0.5">
<Info className="size-4" />
</div>
<div className="text-xs sm:text-sm">
<p className="font-medium text-xs sm:text-sm">Indexing runs in the background</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
You can continue using SurfSense while we sync your data. Check the Active tab to see progress.
</p>
</div>
</div>
<div className="text-xs sm:text-sm">
<p className="font-medium text-xs sm:text-sm">Indexing runs in the background</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
You can continue using SurfSense while we sync your data. Check the Active tab to see progress.
</p>
</div>
</div>
)}
</div>
</div>
{/* Top fade shadow - appears when scrolled */}

View file

@ -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",

View file

@ -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,

View file

@ -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<AllConnectorsTabProps> = ({
onConnectOAuth,
onConnectNonOAuth,
onCreateWebcrawler,
onCreateYouTube,
onManage,
}) => {
const router = useRouter();
@ -93,22 +91,21 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{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}`);