diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/discord-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/discord-connect-form.tsx new file mode 100644 index 000000000..4ab416aa1 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/discord-connect-form.tsx @@ -0,0 +1,354 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Info } from "lucide-react"; +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, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; +import { DateRangeSelector } from "../../components/date-range-selector"; +import { useState } from "react"; + +const discordConnectorFormSchema = z.object({ + name: z.string().min(3, { + message: "Connector name must be at least 3 characters.", + }), + bot_token: z + .string() + .min(10, { + message: "Discord Bot Token is required and must be valid.", + }), +}); + +type DiscordConnectorFormValues = z.infer; + +export const DiscordConnectForm: FC = ({ + onSubmit, + isSubmitting, +}) => { + const isSubmittingRef = useRef(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [periodicEnabled, setPeriodicEnabled] = useState(false); + const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); + const form = useForm({ + resolver: zodResolver(discordConnectorFormSchema), + defaultValues: { + name: "Discord Connector", + bot_token: "", + }, + }); + + const handleSubmit = async (values: DiscordConnectorFormValues) => { + // Prevent multiple submissions + if (isSubmittingRef.current || isSubmitting) { + return; + } + + isSubmittingRef.current = true; + try { + await onSubmit({ + name: values.name, + connector_type: EnumConnectorName.DISCORD_CONNECTOR, + config: { + DISCORD_BOT_TOKEN: values.bot_token, + }, + is_indexable: true, + last_indexed_at: null, + periodic_indexing_enabled: periodicEnabled, + indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, + next_scheduled_at: null, + startDate, + endDate, + periodicEnabled, + frequencyMinutes, + }); + } finally { + isSubmittingRef.current = false; + } + }; + + return ( +
+ + +
+ Bot Token Required + + You'll need a Discord Bot Token to use this connector. You can create one from{" "} + + Discord Developer Portal + + +
+
+ +
+
+ + ( + + Connector Name + + + + + A friendly name to identify this connector. + + + + )} + /> + + ( + + Discord Bot Token + + + + + Your Discord Bot Token will be encrypted and stored securely. + + + + )} + /> + + {/* Indexing Configuration */} +
+

Indexing Configuration

+ + {/* Date Range Selector */} + + + {/* Periodic Sync Config */} +
+
+
+

Enable Periodic Sync

+

+ Automatically re-index at regular intervals +

+
+ +
+ + {periodicEnabled && ( +
+
+ + +
+
+ )} +
+
+ + +
+ + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.DISCORD_CONNECTOR) && ( +
+

What you get with Discord integration:

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

How it works

+

+ The Discord connector uses the Discord API to fetch messages from all accessible channels + that the bot token has access to within a server. +

+
    +
  • + For follow up indexing runs, the connector retrieves messages 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

+ + + Bot Token Required + + You need to create a Discord application and bot to get a bot token. + The bot needs read access to channels and messages. + + + +
+
+

Step 1: Create a Discord Application

+
    +
  1. Go to{" "} + + https://discord.com/developers/applications + +
  2. +
  3. Click New Application
  4. +
  5. Enter an application name and click Create
  6. +
+
+ +
+

Step 2: Create a Bot

+
    +
  1. Navigate to Bot in the sidebar
  2. +
  3. Click Add Bot and confirm
  4. +
  5. Under Privileged Gateway Intents, enable: +
      +
    • MESSAGE CONTENT INTENT - Required to read message content
    • +
    +
  6. +
+
+ +
+

Step 3: Get Bot Token and Invite Bot

+
    +
  1. Under Token, click Reset Token and copy the token
  2. +
  3. Navigate to OAuth2 → URL Generator
  4. +
  5. Select bot scope and Read Messages permission
  6. +
  7. Copy the generated URL and open it in your browser
  8. +
  9. Select your server and authorize the bot
  10. +
+
+
+
+
+ +
+
+

Indexing

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

The Discord connector indexes the following data:

+
    +
  • Messages from all accessible channels
  • +
  • Direct messages (if bot has access)
  • +
  • Message timestamps and metadata
  • +
  • Thread replies and conversations
  • +
+
+
+
+
+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/notion-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/notion-connect-form.tsx new file mode 100644 index 000000000..8a1ee740d --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/notion-connect-form.tsx @@ -0,0 +1,353 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Info } from "lucide-react"; +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, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; +import { DateRangeSelector } from "../../components/date-range-selector"; +import { useState } from "react"; + +const notionConnectorFormSchema = z.object({ + name: z.string().min(3, { + message: "Connector name must be at least 3 characters.", + }), + integration_token: z + .string() + .min(10, { + message: "Notion Integration Token is required and must be valid.", + }), +}); + +type NotionConnectorFormValues = z.infer; + +export const NotionConnectForm: FC = ({ + onSubmit, + isSubmitting, +}) => { + const isSubmittingRef = useRef(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [periodicEnabled, setPeriodicEnabled] = useState(false); + const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); + const form = useForm({ + resolver: zodResolver(notionConnectorFormSchema), + defaultValues: { + name: "Notion Connector", + integration_token: "", + }, + }); + + const handleSubmit = async (values: NotionConnectorFormValues) => { + // Prevent multiple submissions + if (isSubmittingRef.current || isSubmitting) { + return; + } + + isSubmittingRef.current = true; + try { + await onSubmit({ + name: values.name, + connector_type: EnumConnectorName.NOTION_CONNECTOR, + config: { + NOTION_INTEGRATION_TOKEN: values.integration_token, + }, + is_indexable: true, + last_indexed_at: null, + periodic_indexing_enabled: periodicEnabled, + indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, + next_scheduled_at: null, + startDate, + endDate, + periodicEnabled, + frequencyMinutes, + }); + } finally { + isSubmittingRef.current = false; + } + }; + + return ( +
+ + +
+ Integration Token Required + + You'll need a Notion Integration Token to use this connector. You can create one from{" "} + + Notion Integrations + + +
+
+ +
+
+ + ( + + Connector Name + + + + + A friendly name to identify this connector. + + + + )} + /> + + ( + + Notion Integration Token + + + + + Your Notion Integration Token will be encrypted and stored securely. It typically starts with "ntn_". + + + + )} + /> + + {/* Indexing Configuration */} +
+

Indexing Configuration

+ + {/* Date Range Selector */} + + + {/* Periodic Sync Config */} +
+
+
+

Enable Periodic Sync

+

+ Automatically re-index at regular intervals +

+
+ +
+ + {periodicEnabled && ( +
+
+ + +
+
+ )} +
+
+ + +
+ + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.NOTION_CONNECTOR) && ( +
+

What you get with Notion integration:

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

How it works

+

+ The Notion connector uses the Notion API to fetch pages from all accessible workspaces + that the integration token has access to. +

+
    +
  • + For follow up indexing runs, the connector retrieves pages 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

+ + + Integration Token Required + + You need to create a Notion integration and share pages with it to get access. + The integration needs read access to pages. + + + +
+
+

Step 1: Create a Notion Integration

+
    +
  1. Go to{" "} + + https://www.notion.so/my-integrations + +
  2. +
  3. Click + New integration
  4. +
  5. Enter a name for your integration (e.g., "Search Connector")
  6. +
  7. Select your workspace
  8. +
  9. Under Capabilities, enable Read content
  10. +
  11. Click Submit to create the integration
  12. +
  13. Copy the Internal Integration Token (starts with "ntn_")
  14. +
+
+ +
+

Step 2: Share Pages with Integration

+
    +
  1. Open the Notion pages or databases you want to index
  2. +
  3. Click the (three dots) menu in the top right
  4. +
  5. Select Add connections or Connections
  6. +
  7. Search for and select your integration
  8. +
  9. Repeat for all pages you want to index
  10. +
+ + + Important + + The integration can only access pages that have been explicitly shared with it. + Make sure to share all pages you want to index. + + +
+
+
+
+ +
+
+

Indexing

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

The Notion connector indexes the following data:

+
    +
  • Page titles and content
  • +
  • Database entries and properties
  • +
  • Page metadata and properties
  • +
  • Nested pages and sub-pages
  • +
+
+
+
+
+
+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx new file mode 100644 index 000000000..2e4424bc2 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/slack-connect-form.tsx @@ -0,0 +1,358 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Info } from "lucide-react"; +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, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { ConnectFormProps } from "../index"; +import { getConnectorBenefits } from "../connector-benefits"; +import { DateRangeSelector } from "../../components/date-range-selector"; +import { useState } from "react"; + +const slackConnectorFormSchema = z.object({ + name: z.string().min(3, { + message: "Connector name must be at least 3 characters.", + }), + bot_token: z + .string() + .min(10, { + message: "Slack Bot Token is required and must be valid.", + }), +}); + +type SlackConnectorFormValues = z.infer; + +export const SlackConnectForm: FC = ({ + onSubmit, + isSubmitting, +}) => { + const isSubmittingRef = useRef(false); + const [startDate, setStartDate] = useState(undefined); + const [endDate, setEndDate] = useState(undefined); + const [periodicEnabled, setPeriodicEnabled] = useState(false); + const [frequencyMinutes, setFrequencyMinutes] = useState("1440"); + const form = useForm({ + resolver: zodResolver(slackConnectorFormSchema), + defaultValues: { + name: "Slack Connector", + bot_token: "", + }, + }); + + const handleSubmit = async (values: SlackConnectorFormValues) => { + // Prevent multiple submissions + if (isSubmittingRef.current || isSubmitting) { + return; + } + + isSubmittingRef.current = true; + try { + await onSubmit({ + name: values.name, + connector_type: EnumConnectorName.SLACK_CONNECTOR, + config: { + SLACK_BOT_TOKEN: values.bot_token, + }, + is_indexable: true, + last_indexed_at: null, + periodic_indexing_enabled: periodicEnabled, + indexing_frequency_minutes: periodicEnabled ? parseInt(frequencyMinutes, 10) : null, + next_scheduled_at: null, + startDate, + endDate, + periodicEnabled, + frequencyMinutes, + }); + } finally { + isSubmittingRef.current = false; + } + }; + + return ( +
+ + +
+ Bot User OAuth Token Required + + You'll need a Slack Bot User OAuth Token to use this connector. You can create a Slack app and get the token from{" "} + + Slack API Dashboard + + +
+
+ +
+
+ + ( + + Connector Name + + + + + A friendly name to identify this connector. + + + + )} + /> + + ( + + Slack Bot User OAuth Token + + + + + Your Bot User OAuth Token will be encrypted and stored securely. It typically starts with "xoxb-". + + + + )} + /> + + {/* Indexing Configuration */} +
+

Indexing Configuration

+ + {/* Date Range Selector */} + + + {/* Periodic Sync Config */} +
+
+
+

Enable Periodic Sync

+

+ Automatically re-index at regular intervals +

+
+ +
+ + {periodicEnabled && ( +
+
+ + +
+
+ )} +
+
+ + +
+ + {/* What you get section */} + {getConnectorBenefits(EnumConnectorName.SLACK_CONNECTOR) && ( +
+

What you get with Slack integration:

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

How it works

+

+ The Slack connector uses the Slack Web API to fetch messages from all accessible channels + that the bot token has access to within a workspace. +

+
    +
  • + For follow up indexing runs, the connector retrieves messages 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

+ + + Bot User OAuth Token Required + + You need to create a Slack app and install it to your workspace to get a Bot User OAuth Token. + The bot needs read access to channels and messages. + + + +
+
+

Step 1: Create a Slack App

+
    +
  1. Go to{" "} + + https://api.slack.com/apps + +
  2. +
  3. Click Create New App and choose "From scratch"
  4. +
  5. Enter an app name and select your workspace
  6. +
  7. Click Create App
  8. +
+
+ +
+

Step 2: Configure Bot Scopes

+
    +
  1. Navigate to OAuth & Permissions in the sidebar
  2. +
  3. Under Bot Token Scopes, add the following scopes: +
      +
    • channels:read - View basic information about public channels
    • +
    • channels:history - View messages in public channels
    • +
    • groups:read - View basic information about private channels
    • +
    • groups:history - View messages in private channels
    • +
    • im:read - View basic information about direct messages
    • +
    • im:history - View messages in direct messages
    • +
    +
  4. +
+
+ +
+

Step 3: Install App to Workspace

+
    +
  1. Go to Install App in the sidebar
  2. +
  3. Click Install to Workspace
  4. +
  5. Review the permissions and click Allow
  6. +
  7. Copy the Bot User OAuth Token from the "OAuth & Permissions" page (starts with "xoxb-")
  8. +
+
+
+
+
+ +
+
+

Indexing

+
    +
  1. + Navigate to the Connector Dashboard and select the Slack{" "} + Connector. +
  2. +
  3. + Place the Bot User OAuth Token in the form field. +
  4. +
  5. + Click Connect to establish the connection. +
  6. +
  7. Once connected, your Slack messages will be indexed automatically.
  8. +
+ + + + What Gets Indexed + +

The Slack connector indexes the following data:

+
    +
  • Messages from all accessible channels (public and private)
  • +
  • Direct messages (if bot has access)
  • +
  • Message timestamps and metadata
  • +
  • Thread replies and conversations
  • +
+
+
+
+
+
+
+
+
+ ); +}; + 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 index 362a8ffa3..ab70d7835 100644 --- 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 @@ -38,6 +38,27 @@ export function getConnectorBenefits(connectorType: string): string[] | null { "Real-time information from Baidu's search index", "AI-powered summarization with source references", ], + SLACK_CONNECTOR: [ + "Search through all your Slack messages and conversations", + "Access messages from public and private channels", + "Connect your team's communications directly to your search space", + "Keep your search results up-to-date with latest Slack content", + "Index your Slack conversations for enhanced search capabilities", + ], + DISCORD_CONNECTOR: [ + "Search through all your Discord messages and conversations", + "Access messages from all accessible channels", + "Connect your community's communications directly to your search space", + "Keep your search results up-to-date with latest Discord content", + "Index your Discord conversations for enhanced search capabilities", + ], + NOTION_CONNECTOR: [ + "Search through all your Notion pages and databases", + "Access page content, properties, and metadata", + "Connect your knowledge base directly to your search space", + "Keep your search results up-to-date with latest Notion content", + "Index your Notion workspace for enhanced search capabilities", + ], // Add other connectors as needed // GITHUB_CONNECTOR: [...], }; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx index 2a1170303..d06fd7125 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/index.tsx @@ -1,9 +1,12 @@ import type { FC } from "react"; import { BaiduSearchApiConnectForm } from "./components/baidu-search-api-connect-form"; +import { DiscordConnectForm } from "./components/discord-connect-form"; import { ElasticsearchConnectForm } from "./components/elasticsearch-connect-form"; import { LinearConnectForm } from "./components/linear-connect-form"; import { LinkupApiConnectForm } from "./components/linkup-api-connect-form"; +import { NotionConnectForm } from "./components/notion-connect-form"; import { SearxngConnectForm } from "./components/searxng-connect-form"; +import { SlackConnectForm } from "./components/slack-connect-form"; import { TavilyApiConnectForm } from "./components/tavily-api-connect-form"; export interface ConnectFormProps { @@ -47,6 +50,12 @@ export function getConnectFormComponent( return LinearConnectForm; case "ELASTICSEARCH_CONNECTOR": return ElasticsearchConnectForm; + case "SLACK_CONNECTOR": + return SlackConnectForm; + case "DISCORD_CONNECTOR": + return DiscordConnectForm; + case "NOTION_CONNECTOR": + return NotionConnectForm; // Add other connector types here as needed default: return null; diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/discord-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/discord-config.tsx new file mode 100644 index 000000000..a4f833367 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/discord-config.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { useState, useEffect } from "react"; +import type { FC } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { ConnectorConfigProps } from "../index"; + +export interface DiscordConfigProps extends ConnectorConfigProps { + onNameChange?: (name: string) => void; +} + +export const DiscordConfig: FC = ({ + connector, + onConfigChange, + onNameChange, +}) => { + const [botToken, setBotToken] = useState( + (connector.config?.DISCORD_BOT_TOKEN as string) || "" + ); + const [name, setName] = useState(connector.name || ""); + + // Update bot token and name when connector changes + useEffect(() => { + const token = (connector.config?.DISCORD_BOT_TOKEN as string) || ""; + setBotToken(token); + setName(connector.name || ""); + }, [connector.config, connector.name]); + + const handleBotTokenChange = (value: string) => { + setBotToken(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + DISCORD_BOT_TOKEN: value, + }); + } + }; + + const handleNameChange = (value: string) => { + setName(value); + if (onNameChange) { + onNameChange(value); + } + }; + + return ( +
+ {/* Connector Name */} +
+
+ + handleNameChange(e.target.value)} + placeholder="My Discord Connector" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ A friendly name to identify this connector. +

+
+
+ + {/* Configuration */} +
+
+

Configuration

+
+ +
+ + handleBotTokenChange(e.target.value)} + placeholder="Your Bot Token" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Update your Discord Bot Token if needed. +

+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/notion-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/notion-config.tsx new file mode 100644 index 000000000..505e0afa8 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/notion-config.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { useState, useEffect } from "react"; +import type { FC } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { ConnectorConfigProps } from "../index"; + +export interface NotionConfigProps extends ConnectorConfigProps { + onNameChange?: (name: string) => void; +} + +export const NotionConfig: FC = ({ + connector, + onConfigChange, + onNameChange, +}) => { + const [integrationToken, setIntegrationToken] = useState( + (connector.config?.NOTION_INTEGRATION_TOKEN as string) || "" + ); + const [name, setName] = useState(connector.name || ""); + + // Update integration token and name when connector changes + useEffect(() => { + const token = (connector.config?.NOTION_INTEGRATION_TOKEN as string) || ""; + setIntegrationToken(token); + setName(connector.name || ""); + }, [connector.config, connector.name]); + + const handleIntegrationTokenChange = (value: string) => { + setIntegrationToken(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + NOTION_INTEGRATION_TOKEN: value, + }); + } + }; + + const handleNameChange = (value: string) => { + setName(value); + if (onNameChange) { + onNameChange(value); + } + }; + + return ( +
+ {/* Connector Name */} +
+
+ + handleNameChange(e.target.value)} + placeholder="My Notion Connector" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ A friendly name to identify this connector. +

+
+
+ + {/* Configuration */} +
+
+

Configuration

+
+ +
+ + handleIntegrationTokenChange(e.target.value)} + placeholder="Begins with secret_..." + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Update your Notion Integration Token if needed. +

+
+
+
+ ); +}; + diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/slack-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/slack-config.tsx new file mode 100644 index 000000000..554b5dd66 --- /dev/null +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/slack-config.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { KeyRound } from "lucide-react"; +import { useState, useEffect } from "react"; +import type { FC } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import type { ConnectorConfigProps } from "../index"; + +export interface SlackConfigProps extends ConnectorConfigProps { + onNameChange?: (name: string) => void; +} + +export const SlackConfig: FC = ({ + connector, + onConfigChange, + onNameChange, +}) => { + const [botToken, setBotToken] = useState( + (connector.config?.SLACK_BOT_TOKEN as string) || "" + ); + const [name, setName] = useState(connector.name || ""); + + // Update bot token and name when connector changes + useEffect(() => { + const token = (connector.config?.SLACK_BOT_TOKEN as string) || ""; + setBotToken(token); + setName(connector.name || ""); + }, [connector.config, connector.name]); + + const handleBotTokenChange = (value: string) => { + setBotToken(value); + if (onConfigChange) { + onConfigChange({ + ...connector.config, + SLACK_BOT_TOKEN: value, + }); + } + }; + + const handleNameChange = (value: string) => { + setName(value); + if (onNameChange) { + onNameChange(value); + } + }; + + return ( +
+ {/* Connector Name */} +
+
+ + handleNameChange(e.target.value)} + placeholder="My Slack Connector" + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ A friendly name to identify this connector. +

+
+
+ + {/* Configuration */} +
+
+

Configuration

+
+ +
+ + handleBotTokenChange(e.target.value)} + placeholder="Begins with xoxb-..." + className="border-slate-400/20 focus-visible:border-slate-400/40" + /> +

+ Update your Bot User OAuth Token if needed. +

+
+
+
+ ); +}; + 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 5674a4a3e..6f5afaec0 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 @@ -3,11 +3,14 @@ import type { FC } from "react"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { BaiduSearchApiConfig } from "./components/baidu-search-api-config"; +import { DiscordConfig } from "./components/discord-config"; import { ElasticsearchConfig } from "./components/elasticsearch-config"; import { GoogleDriveConfig } from "./components/google-drive-config"; import { LinearConfig } from "./components/linear-config"; import { LinkupApiConfig } from "./components/linkup-api-config"; +import { NotionConfig } from "./components/notion-config"; import { SearxngConfig } from "./components/searxng-config"; +import { SlackConfig } from "./components/slack-config"; import { TavilyApiConfig } from "./components/tavily-api-config"; import { WebcrawlerConfig } from "./components/webcrawler-config"; @@ -42,6 +45,12 @@ export function getConnectorConfigComponent( return WebcrawlerConfig; case "ELASTICSEARCH_CONNECTOR": return ElasticsearchConfig; + case "SLACK_CONNECTOR": + return SlackConfig; + case "DISCORD_CONNECTOR": + return DiscordConfig; + case "NOTION_CONNECTOR": + return NotionConfig; // 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-connect-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx index a7c95fea8..9965f721b 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-connect-view.tsx @@ -49,6 +49,9 @@ export const ConnectorConnectView: FC = ({ BAIDU_SEARCH_API: "baidu-search-api-connect-form", LINEAR_CONNECTOR: "linear-connect-form", ELASTICSEARCH_CONNECTOR: "elasticsearch-connect-form", + SLACK_CONNECTOR: "slack-connect-form", + DISCORD_CONNECTOR: "discord-connect-form", + NOTION_CONNECTOR: "notion-connect-form", }; const formId = formIdMap[connectorType]; if (formId) { 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 7843561e8..97bc06685 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 @@ -657,7 +657,7 @@ export const useConnectorDialog = () => { (oauthConnector) => oauthConnector.connectorType === connector.connector_type ); - // Check if this is webcrawler, Tavily API, SearxNG, Linkup, Baidu, Linear, or Elasticsearch (can be managed in popup) + // Check if this is webcrawler, Tavily API, SearxNG, Linkup, Baidu, Linear, Elasticsearch, Slack, Discord, or Notion (can be managed in popup) const isWebcrawler = connector.connector_type === EnumConnectorName.WEBCRAWLER_CONNECTOR; const isTavilyApi = connector.connector_type === EnumConnectorName.TAVILY_API; const isSearxng = connector.connector_type === EnumConnectorName.SEARXNG_API; @@ -665,9 +665,12 @@ export const useConnectorDialog = () => { const isBaidu = connector.connector_type === EnumConnectorName.BAIDU_SEARCH_API; const isLinear = connector.connector_type === EnumConnectorName.LINEAR_CONNECTOR; const isElasticsearch = connector.connector_type === EnumConnectorName.ELASTICSEARCH_CONNECTOR; + const isSlack = connector.connector_type === EnumConnectorName.SLACK_CONNECTOR; + const isDiscord = connector.connector_type === EnumConnectorName.DISCORD_CONNECTOR; + const isNotion = connector.connector_type === EnumConnectorName.NOTION_CONNECTOR; - // If not OAuth, not webcrawler, not Tavily API, not SearxNG, not Linkup, not Baidu, not Linear, and not Elasticsearch, redirect to old connector edit page - if (!isOAuthConnector && !isWebcrawler && !isTavilyApi && !isSearxng && !isLinkup && !isBaidu && !isLinear && !isElasticsearch) { + // If not OAuth, not webcrawler, not Tavily API, not SearxNG, not Linkup, not Baidu, not Linear, not Elasticsearch, not Slack, not Discord, and not Notion, redirect to old connector edit page + if (!isOAuthConnector && !isWebcrawler && !isTavilyApi && !isSearxng && !isLinkup && !isBaidu && !isLinear && !isElasticsearch && !isSlack && !isDiscord && !isNotion) { router.push(`/dashboard/${searchSpaceId}/connectors/${connector.id}/edit`); return; } 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 0d5d0ef1c..76b93b930 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 @@ -99,6 +99,9 @@ export const AllConnectorsTab: FC = ({ const isBaidu = connector.id === "baidu-search-api"; const isLinear = connector.id === "linear-connector"; const isElasticsearch = connector.id === "elasticsearch-connector"; + const isSlack = connector.id === "slack-connector"; + const isDiscord = connector.id === "discord-connector"; + const isNotion = connector.id === "notion-connector"; const isConnected = connectedTypes.has(connector.connectorType); const isConnecting = connectingId === connector.id; @@ -110,7 +113,7 @@ export const AllConnectorsTab: FC = ({ const handleConnect = isWebcrawler && onCreateWebcrawler ? onCreateWebcrawler - : (isTavily || isSearxng || isLinkup || isBaidu || isLinear || isElasticsearch) && onConnectNonOAuth + : (isTavily || isSearxng || isLinkup || isBaidu || isLinear || isElasticsearch || isSlack || isDiscord || isNotion) && onConnectNonOAuth ? () => onConnectNonOAuth(connector.connectorType) : () => router.push(`/dashboard/${searchSpaceId}/connectors/add/${connector.id}`);