Merge branch 'dev' into fix/env-config-connector-forms

This commit is contained in:
Varun Shukla 2026-05-20 03:26:12 +05:30 committed by GitHub
commit 81ce9e4071
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
291 changed files with 8271 additions and 7022 deletions

View file

@ -81,8 +81,8 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
className={cn(
"group relative flex items-center gap-4 p-4 rounded-xl text-left transition-all duration-200 w-full border",
status.status === "warning"
? "border-yellow-500/30 bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
: "border-border bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
? "border-yellow-500/30 bg-slate-400/5 dark:bg-white/5 hover:bg-accent hover:text-accent-foreground"
: "border-border bg-slate-400/5 dark:bg-white/5 hover:bg-accent hover:text-accent-foreground"
)}
>
<div
@ -145,9 +145,9 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
size="sm"
variant={isConnected ? "secondary" : "default"}
className={cn(
"relative h-8 text-[11px] px-3 rounded-lg shrink-0 font-medium items-center justify-center",
"relative h-8 text-[11px] px-3 shrink-0 font-medium items-center justify-center",
isConnected &&
"bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80",
"bg-white text-slate-700 hover:bg-accent hover:text-accent-foreground border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground",
!isConnected && "shadow-xs"
)}
onClick={isConnected ? onManage : onConnect}

View file

@ -2,6 +2,7 @@
import { Search, X } from "lucide-react";
import type { FC } from "react";
import { Button } from "@/components/ui/button";
import { DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
@ -25,7 +26,7 @@ export const ConnectorDialogHeader: FC<ConnectorDialogHeaderProps> = ({
<div
className={cn(
"flex-shrink-0 px-4 sm:px-12 pt-5 sm:pt-10 transition-shadow duration-200 relative z-10",
isScrolled && "shadow-xl bg-muted/50 backdrop-blur-md"
isScrolled && "bg-popover shadow-xl"
)}
>
<DialogHeader>
@ -37,7 +38,7 @@ export const ConnectorDialogHeader: FC<ConnectorDialogHeaderProps> = ({
</DialogDescription>
</DialogHeader>
<div className="flex flex-col-reverse sm:flex-row sm:items-end justify-between gap-4 sm:gap-8 mt-4 sm:mt-8 border-b border-border/80 dark:border-white/5">
<div className="flex flex-col-reverse sm:flex-row sm:items-end justify-between gap-4 sm:gap-8 mt-4 sm:mt-8 border-b border-popover-border">
<TabsList className="bg-transparent p-0 gap-4 sm:gap-8 h-auto w-full sm:w-auto justify-center sm:justify-start">
<TabsTrigger
value="all"
@ -63,27 +64,29 @@ export const ConnectorDialogHeader: FC<ConnectorDialogHeaderProps> = ({
<div className="w-full sm:w-72 sm:pb-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-gray-500 dark:text-gray-500" />
<Search className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
<input
type="text"
autoComplete="off"
placeholder="Search"
className={cn(
"w-full bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10 focus:bg-slate-400/10 dark:focus:bg-white/10 border border-border rounded-xl pl-9 py-2 text-sm transition-all outline-none placeholder:text-muted-foreground/50",
"w-full bg-slate-400/5 dark:bg-white/5 hover:bg-accent hover:text-accent-foreground focus:bg-slate-400/10 dark:focus:bg-white/10 border border-border rounded-xl pl-9 py-2 text-sm transition-all outline-none placeholder:text-muted-foreground/50",
searchQuery ? "pr-9" : "pr-4"
)}
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
/>
{searchQuery && (
<button
<Button
variant="ghost"
size="icon"
type="button"
onClick={() => onSearchChange("")}
className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-gray-500 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
className="absolute right-1.5 top-1/2 size-7 -translate-y-1/2 text-muted-foreground transition-colors hover:bg-transparent hover:text-accent-foreground"
aria-label="Clear search"
>
<X className="size-4" />
</button>
<X data-icon="inline-start" />
</Button>
)}
</div>
</div>

View file

@ -3,6 +3,7 @@
import { AlertTriangle, X } from "lucide-react";
import type { FC } from "react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
interface ConnectorWarningBannerProps {
@ -42,14 +43,16 @@ export const ConnectorWarningBanner: FC<ConnectorWarningBannerProps> = ({
)}
</div>
{onDismiss && (
<button
<Button
variant="ghost"
size="icon"
type="button"
onClick={handleDismiss}
className="shrink-0 p-0.5 rounded hover:bg-yellow-500/20 transition-colors"
className="size-6 shrink-0 rounded p-0 transition-colors hover:bg-yellow-500/20"
aria-label="Dismiss warning"
>
<X className="size-3.5 text-yellow-700 dark:text-yellow-300" />
</button>
<X data-icon="inline-start" className="text-yellow-700 dark:text-yellow-300" />
</Button>
)}
</div>
);

View file

@ -136,7 +136,7 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
variant="outline"
size="sm"
onClick={handleClearDates}
className="text-xs sm:text-sm bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 hover:bg-slate-400/10 dark:hover:bg-slate-400/10"
className="text-xs sm:text-sm bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 hover:bg-accent hover:text-accent-foreground"
>
Clear Dates
</Button>
@ -145,7 +145,7 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
variant="outline"
size="sm"
onClick={handleLast30Days}
className="text-xs sm:text-sm bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 hover:bg-slate-400/10 dark:hover:bg-slate-400/10"
className="text-xs sm:text-sm bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 hover:bg-accent hover:text-accent-foreground"
>
Last 30 Days
</Button>
@ -155,7 +155,7 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
variant="outline"
size="sm"
onClick={handleNext30Days}
className="text-xs sm:text-sm bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 hover:bg-slate-400/10 dark:hover:bg-slate-400/10"
className="text-xs sm:text-sm bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 hover:bg-accent hover:text-accent-foreground"
>
Next 30 Days
</Button>
@ -165,7 +165,7 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
variant="outline"
size="sm"
onClick={handleLastYear}
className="text-xs sm:text-sm bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 hover:bg-slate-400/10 dark:hover:bg-slate-400/10"
className="text-xs sm:text-sm bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 hover:bg-accent hover:text-accent-foreground"
>
Last Year
</Button>

View file

@ -70,20 +70,22 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSu
return (
<div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0" />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
You'll need a Baidu AppBuilder API key to use this connector. You can get one by signing
up at{" "}
<a
href="https://qianfan.cloud.baidu.com/"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
qianfan.cloud.baidu.com
</a>
<Alert>
<Info />
<AlertTitle>API Key Required</AlertTitle>
<AlertDescription>
<p>
You'll need a Baidu AppBuilder API key to use this connector. You can get one by signing
up at{" "}
<a
href="https://qianfan.cloud.baidu.com/"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
qianfan.cloud.baidu.com
</a>
</p>
</AlertDescription>
</Alert>

View file

@ -96,10 +96,10 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitt
return (
<div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0" />
<AlertTitle className="text-xs sm:text-sm">API Token Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
<Alert>
<Info />
<AlertTitle>API Token Required</AlertTitle>
<AlertDescription>
You'll need a BookStack API Token to use this connector. You can create one from your
BookStack instance settings.
</AlertDescription>

View file

@ -172,10 +172,10 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSub
return (
<div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0" />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
<Alert>
<Info />
<AlertTitle>API Key Required</AlertTitle>
<AlertDescription>
Enter your Elasticsearch cluster endpoint URL and authentication credentials to connect.
</AlertDescription>
</Alert>
@ -428,10 +428,10 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSub
</div>
)}
<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">Index Selection Tips</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px] mt-2">
<Alert>
<Info />
<AlertTitle>Index Selection Tips</AlertTitle>
<AlertDescription>
<ul className="list-disc pl-4 space-y-1">
<li>Use wildcards like "logs-*" to match multiple indices</li>
<li>Separate multiple indices with commas</li>
@ -643,231 +643,6 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSub
</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 Elasticsearch connector allows you to search and retrieve documents from your
Elasticsearch cluster. Configure connection details, select specific indices, and
set search parameters to make your existing data searchable within SurfSense.
</p>
</div>
<div className="space-y-4">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">Connection Setup</h3>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Get your Elasticsearch endpoint
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
You'll need the endpoint URL for your Elasticsearch cluster. This typically
looks like:
</p>
<ul className="list-disc pl-5 space-y-1 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
Cloud:{" "}
<code className="bg-muted px-1 py-0.5 rounded">
https://your-cluster.es.region.aws.com:443
</code>
</li>
<li>
Self-hosted:{" "}
<code className="bg-muted px-1 py-0.5 rounded">
https://elasticsearch.example.com:9200
</code>
</li>
</ul>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Configure authentication
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
Elasticsearch requires authentication. You can use either:
</p>
<ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
<strong>API Key:</strong> A base64-encoded API key. You can create one in
Elasticsearch by running:
<pre className="bg-muted p-2 rounded mt-1 text-[9px] overflow-x-auto">
<code>POST /_security/api_key</code>
</pre>
</li>
<li>
<strong>Username & Password:</strong> Basic authentication using your
Elasticsearch username and password.
</li>
</ul>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 3: Select indices
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
Specify which indices to search. You can:
</p>
<ul className="list-disc pl-5 space-y-1 text-[10px] sm:text-xs text-muted-foreground">
<li>
Use wildcards: <code className="bg-muted px-1 py-0.5 rounded">logs-*</code>{" "}
to match multiple indices
</li>
<li>
List specific indices:{" "}
<code className="bg-muted px-1 py-0.5 rounded">
logs-2024, documents-2024
</code>
</li>
<li>
Leave empty to search all accessible indices (not recommended for
performance)
</li>
</ul>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">Advanced Configuration</h3>
<div className="space-y-4">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Search Query</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-2">
The default query used for searches. Use{" "}
<code className="bg-muted px-1 py-0.5 rounded">*</code> to match all
documents, or specify a more complex Elasticsearch query.
</p>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Search Fields</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-2">
Limit searches to specific fields for better performance. Common fields
include:
</p>
<ul className="list-disc pl-5 space-y-1 text-[10px] sm:text-xs text-muted-foreground">
<li>
<code className="bg-muted px-1 py-0.5 rounded">title</code> - Document
titles
</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">content</code> - Main content
</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">description</code> -
Descriptions
</li>
</ul>
<p className="text-[10px] sm:text-xs text-muted-foreground mt-2">
Leave empty to search all fields in your documents.
</p>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Maximum Documents</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground">
Set a limit on the number of documents retrieved per search (1-10,000). This
helps control response times and resource usage. Leave empty to use
Elasticsearch's default limit.
</p>
</div>
</div>
</div>
</div>
<div className="space-y-4">
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">Troubleshooting</h3>
<div className="space-y-4">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Connection Issues</h4>
<ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>
<strong>Invalid URL:</strong> Ensure your endpoint URL includes the protocol
(https://) and port number if required.
</li>
<li>
<strong>SSL/TLS Errors:</strong> Verify that your cluster uses HTTPS and the
certificate is valid. Self-signed certificates may require additional
configuration.
</li>
<li>
<strong>Connection Timeout:</strong> Check your network connectivity and
firewall settings. Ensure the Elasticsearch cluster is accessible from
SurfSense servers.
</li>
</ul>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Authentication Issues
</h4>
<ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>
<strong>Invalid Credentials:</strong> Double-check your username/password or
API key. API keys must be base64-encoded.
</li>
<li>
<strong>Permission Denied:</strong> Ensure your API key or user account has
read permissions for the indices you want to search.
</li>
<li>
<strong>API Key Format:</strong> Elasticsearch API keys are typically
base64-encoded strings. Make sure you're using the full key value.
</li>
</ul>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Search Issues</h4>
<ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>
<strong>No Results:</strong> Verify that your index selection matches
existing indices. Use wildcards carefully.
</li>
<li>
<strong>Slow Searches:</strong> Limit the number of indices or use specific
index names instead of wildcards. Reduce the maximum documents limit.
</li>
<li>
<strong>Field Not Found:</strong> Ensure the search fields you specify
actually exist in your Elasticsearch documents.
</li>
</ul>
</div>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 mt-4">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Need More Help?</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
If you continue to experience issues, check your Elasticsearch cluster logs
and ensure your cluster version is compatible. For Elasticsearch Cloud
deployments, verify your access policies and IP allowlists.
</AlertDescription>
</Alert>
</div>
</div>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
};

View file

@ -105,20 +105,23 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting
return (
<div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0" />
<AlertTitle className="text-xs sm:text-sm">Personal Access Token (Optional)</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
A GitHub PAT is only required for private repositories. Public repos work without a token.{" "}
<a
href="https://github.com/settings/tokens/new?description=surfsense&scopes=repo"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4 inline-flex items-center gap-1.5"
>
Get your token
<ExternalLink className="h-3 w-3 sm:h-4 sm:w-4" />
</a>{" "}
<Alert>
<Info />
<AlertTitle>Personal Access Token (Optional)</AlertTitle>
<AlertDescription>
<p>
A GitHub PAT is only required for private repositories. Public repos work without a
token.{" "}
<a
href="https://github.com/settings/tokens/new?description=surfsense&scopes=repo"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4 inline-flex items-center gap-1.5"
>
Get your token
<ExternalLink className="h-3 w-3 sm:h-4 sm:w-4" />
</a>
</p>
</AlertDescription>
</Alert>

View file

@ -70,19 +70,21 @@ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitt
return (
<div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0" />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
You'll need a Linkup API key to use this connector. You can get one by signing up at{" "}
<a
href="https://linkup.so"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
linkup.so
</a>
<Alert>
<Info />
<AlertTitle>API Key Required</AlertTitle>
<AlertDescription>
<p>
You'll need a Linkup API key to use this connector. You can get one by signing up at{" "}
<a
href="https://linkup.so"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
linkup.so
</a>
</p>
</AlertDescription>
</Alert>

View file

@ -88,19 +88,21 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }
return (
<div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0" />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
You'll need a Luma API Key to use this connector. You can create one from{" "}
<a
href="https://lu.ma/api"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
Luma API Settings
</a>
<Alert>
<Info />
<AlertTitle>API Key Required</AlertTitle>
<AlertDescription>
<p>
You'll need a Luma API Key to use this connector. You can create one from{" "}
<a
href="https://lu.ma/api"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
Luma API Settings
</a>
</p>
</AlertDescription>
</Alert>

View file

@ -155,7 +155,7 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
type="button"
variant="ghost"
size="sm"
className="h-6 px-2 text-xs text-muted-foreground hover:text-foreground"
className="h-6 px-2 text-xs text-muted-foreground hover:text-accent-foreground"
onClick={() => handleConfigChange(DEFAULT_STDIO_CONFIG)}
>
Local Example
@ -164,7 +164,7 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
type="button"
variant="ghost"
size="sm"
className="h-6 px-2 text-xs text-muted-foreground hover:text-foreground"
className="h-6 px-2 text-xs text-muted-foreground hover:text-accent-foreground"
onClick={() => handleConfigChange(DEFAULT_HTTP_CONFIG)}
>
Remote Example
@ -210,7 +210,7 @@ export const MCPConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting })
onClick={handleTestConnection}
disabled={isTesting}
variant="secondary"
className="w-full h-8 text-[13px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80"
className="w-full h-8 text-[13px] px-3 font-medium bg-white text-slate-700 hover:bg-accent hover:text-accent-foreground border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground"
>
{isTesting ? (
<>

View file

@ -1,12 +1,11 @@
"use client";
import { Check, Copy, Info } from "lucide-react";
import { type FC, useCallback, useRef, useState } from "react";
import type { FC } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { EnumConnectorName } from "@/contracts/enums/connector";
import { useApiKey } from "@/hooks/use-api-key";
import { copyToClipboard as copyToClipboardUtil } from "@/lib/utils";
import { getConnectorBenefits } from "../connector-benefits";
import type { ConnectFormProps } from "../index";
import { BACKEND_URL } from "@/lib/env-config";
@ -30,16 +29,6 @@ const PLUGIN_RELEASES_URL =
*/
export const ObsidianConnectForm: FC<ConnectFormProps> = ({ onBack }) => {
const { apiKey, isLoading, copied, copyToClipboard } = useApiKey();
const [copiedUrl, setCopiedUrl] = useState(false);
const urlCopyTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const copyServerUrl = useCallback(async () => {
const ok = await copyToClipboardUtil(BACKEND_URL);
if (!ok) return;
setCopiedUrl(true);
if (urlCopyTimerRef.current) clearTimeout(urlCopyTimerRef.current);
urlCopyTimerRef.current = setTimeout(() => setCopiedUrl(false), 2000);
}, []);
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
@ -52,10 +41,10 @@ export const ObsidianConnectForm: FC<ConnectFormProps> = ({ onBack }) => {
that just closes the dialog (see component-level docstring). */}
<form id="obsidian-connect-form" onSubmit={handleSubmit} />
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0 text-purple-500" />
<AlertTitle className="text-xs sm:text-sm">Plugin-based sync</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
<Alert>
<Info />
<AlertTitle>Plugin-based sync</AlertTitle>
<AlertDescription>
SurfSense now syncs Obsidian via an official plugin that runs inside Obsidian itself.
Works on desktop and mobile, in cloud and self-hosted deployments.
</AlertDescription>
@ -123,7 +112,7 @@ export const ObsidianConnectForm: FC<ConnectFormProps> = ({ onBack }) => {
variant="ghost"
size="icon"
onClick={copyToClipboard}
className="size-7 shrink-0 text-muted-foreground hover:text-foreground"
className="size-7 shrink-0 text-muted-foreground hover:text-accent-foreground"
aria-label={copied ? "Copied" : "Copy API key"}
>
{copied ? (

View file

@ -123,20 +123,22 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmittin
return (
<div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0" />
<AlertTitle className="text-xs sm:text-sm">SearxNG Instance Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
You need access to a running SearxNG instance. Refer to the{" "}
<a
href="https://docs.searxng.org/admin/installation-docker.html"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
SearxNG installation guide
</a>{" "}
for setup instructions. If your instance requires an API key, include it below.
<Alert>
<Info />
<AlertTitle>SearxNG Instance Required</AlertTitle>
<AlertDescription>
<p>
You need access to a running SearxNG instance. Refer to the{" "}
<a
href="https://docs.searxng.org/admin/installation-docker.html"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
SearxNG installation guide
</a>{" "}
for setup instructions. If your instance requires an API key, include it below.
</p>
</AlertDescription>
</Alert>

View file

@ -70,19 +70,21 @@ export const TavilyApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitt
return (
<div className="space-y-6 pb-6">
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0" />
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs">
You'll need a Tavily API key to use this connector. You can get one by signing up at{" "}
<a
href="https://tavily.com"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
tavily.com
</a>
<Alert>
<Info />
<AlertTitle>API Key Required</AlertTitle>
<AlertDescription>
<p>
You'll need a Tavily API key to use this connector. You can get one by signing up at{" "}
<a
href="https://tavily.com"
target="_blank"
rel="noopener noreferrer"
className="font-medium underline underline-offset-4"
>
tavily.com
</a>
</p>
</AlertDescription>
</Alert>

View file

@ -166,10 +166,10 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameC
)}
{webhookInfo && (
<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-xs sm:text-sm">Configuration Instructions</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs mt-1">
<Alert>
<Info />
<AlertTitle>Configuration Instructions</AlertTitle>
<AlertDescription>
Configure this URL in Circleback Settings Automations Create automation Send
webhook request. The webhook will automatically send meeting notes, transcripts, and
action items to this search space.

View file

@ -3,6 +3,7 @@
import { Info, KeyRound } from "lucide-react";
import type { FC } from "react";
import { useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import type { ConnectorConfigProps } from "../index";
@ -47,21 +48,17 @@ export const ClickUpConfig: FC<ClickUpConfigProps> = ({
return (
<div className="space-y-6">
{/* OAuth Info */}
<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">Connected via OAuth</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
<Alert>
<Info />
<AlertTitle>Connected via OAuth</AlertTitle>
<AlertDescription>
<p>
Workspace:{" "}
<code className="bg-muted px-1 py-0.5 rounded text-inherit">{workspaceName}</code>
</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
To update your connection, reconnect this connector.
</p>
</div>
</div>
<p>To update your connection, reconnect this connector.</p>
</AlertDescription>
</Alert>
</div>
);
}

View file

@ -14,6 +14,7 @@ import {
import type { FC } from "react";
import { useCallback, useState } from "react";
import { DriveFolderTree, type SelectedFolder } from "@/components/connectors/drive-folder-tree";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
Select,
@ -196,14 +197,16 @@ export const ComposioDriveConfig: FC<ConnectorConfigProps> = ({ connector, onCon
>
<FolderClosed className="size-3.5 shrink-0 text-muted-foreground" />
<span className="flex-1 truncate">{folder.name}</span>
<button
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveFolder(folder.id)}
className="shrink-0 p-0.5 hover:bg-muted-foreground/20 rounded transition-colors"
className="size-5 shrink-0 rounded p-0 hover:bg-accent hover:text-accent-foreground"
aria-label={`Remove ${folder.name}`}
>
<X className="size-3.5" />
</button>
</Button>
</div>
))}
{selectedFiles.map((file) => (
@ -214,14 +217,16 @@ export const ComposioDriveConfig: FC<ConnectorConfigProps> = ({ connector, onCon
>
{getFileIconFromName(file.name)}
<span className="flex-1 truncate">{file.name}</span>
<button
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveFile(file.id)}
className="shrink-0 p-0.5 hover:bg-muted-foreground/20 rounded transition-colors"
className="size-5 shrink-0 rounded p-0 hover:bg-accent hover:text-accent-foreground"
aria-label={`Remove ${file.name}`}
>
<X className="size-3.5" />
</button>
</Button>
</div>
))}
</div>
@ -237,10 +242,11 @@ export const ComposioDriveConfig: FC<ConnectorConfigProps> = ({ connector, onCon
{isEditMode ? (
<div className="space-y-2">
<button
<Button
type="button"
variant="ghost"
onClick={() => setIsFolderTreeOpen((prev) => !prev)}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground transition-colors w-fit"
className="h-auto w-fit gap-2 px-0 py-0 text-xs font-normal text-muted-foreground hover:bg-transparent hover:text-accent-foreground sm:text-sm"
>
Change Selection
{isFolderTreeOpen ? (
@ -248,7 +254,7 @@ export const ComposioDriveConfig: FC<ConnectorConfigProps> = ({ connector, onCon
) : (
<ChevronRight className="size-4" />
)}
</button>
</Button>
{isFolderTreeOpen && (
<DriveFolderTree
fetchItems={fetchItems}

View file

@ -3,6 +3,7 @@
import { Info, KeyRound } from "lucide-react";
import type { FC } from "react";
import { useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import type { ConnectorConfigProps } from "../index";
@ -72,23 +73,17 @@ export const ConfluenceConfig: FC<ConfluenceConfigProps> = ({
return (
<div className="space-y-6">
{/* OAuth Info */}
<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">Connected via OAuth</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
This connector is authenticated using OAuth 2.0. Your Confluence instance is:
</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
<Alert>
<Info />
<AlertTitle>Connected via OAuth</AlertTitle>
<AlertDescription>
<p>This connector is authenticated using OAuth 2.0. Your Confluence instance is:</p>
<p>
<code className="bg-muted px-1 py-0.5 rounded text-inherit">{siteUrl}</code>
</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
To update your connection, reconnect this connector.
</p>
</div>
</div>
<p>To update your connection, reconnect this connector.</p>
</AlertDescription>
</Alert>
</div>
);
}

View file

@ -2,6 +2,7 @@
import { AlertCircle, CheckCircle2, Hash, Info, Megaphone, RefreshCw } from "lucide-react";
import { type FC, useCallback, useEffect, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { connectorsApiService, type DiscordChannel } from "@/lib/apis/connectors-api.service";
@ -73,17 +74,14 @@ export const DiscordConfig: FC<DiscordConfigProps> = ({ connector }) => {
return (
<div className="space-y-6">
{/* 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" />
</div>
<div className="text-xs sm:text-sm">
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
The bot needs &quot;Read Message History&quot; permission to access channels. Ask a
server admin to grant this permission for channels shown below.
</p>
</div>
</div>
<Alert>
<Info />
<AlertTitle>Grant Channel Permissions</AlertTitle>
<AlertDescription>
The bot needs &quot;Read Message History&quot; permission to access channels. Ask a server
admin to grant this permission for channels shown below.
</AlertDescription>
</Alert>
{/* Channels Section */}
<div className="space-y-3">
@ -100,7 +98,7 @@ export const DiscordConfig: FC<DiscordConfigProps> = ({ connector }) => {
size="sm"
onClick={fetchChannels}
disabled={isLoading}
className="h-7 px-2.5 text-[11px] bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 border-slate-400/20 dark:border-white/20"
className="h-7 px-2.5 text-[11px] bg-slate-400/10 dark:bg-white/10 hover:bg-accent hover:text-accent-foreground border-slate-400/20 dark:border-white/20"
>
<RefreshCw className={cn("mr-1.5 size-3", isLoading && "animate-spin")} />
Refresh
@ -175,7 +173,7 @@ interface ChannelPillProps {
const ChannelPill: FC<ChannelPillProps> = ({ channel }) => {
return (
<div className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 transition-colors">
<div className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-slate-400/10 dark:bg-white/10 hover:bg-accent hover:text-accent-foreground transition-colors">
{channel.type === "announcement" ? (
<Megaphone className="size-2.5 text-muted-foreground" />
) : (

View file

@ -14,6 +14,7 @@ import {
import type { FC } from "react";
import { useCallback, useEffect, useState } from "react";
import { DriveFolderTree, type SelectedFolder } from "@/components/connectors/drive-folder-tree";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
Select,
@ -177,14 +178,16 @@ export const DropboxConfig: FC<ConnectorConfigProps> = ({ connector, onConfigCha
>
<FolderClosed className="size-3.5 shrink-0 text-muted-foreground" />
<span className="flex-1 truncate">{folder.name}</span>
<button
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveFolder(folder.id)}
className="shrink-0 p-0.5 hover:bg-muted-foreground/20 rounded transition-colors"
className="size-5 shrink-0 rounded p-0 hover:bg-accent hover:text-accent-foreground"
aria-label={`Remove ${folder.name}`}
>
<X className="size-3.5" />
</button>
</Button>
</div>
))}
{selectedFiles.map((file) => (
@ -195,14 +198,16 @@ export const DropboxConfig: FC<ConnectorConfigProps> = ({ connector, onConfigCha
>
{getFileIconFromName(file.name)}
<span className="flex-1 truncate">{file.name}</span>
<button
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveFile(file.id)}
className="shrink-0 p-0.5 hover:bg-muted-foreground/20 rounded transition-colors"
className="size-5 shrink-0 rounded p-0 hover:bg-accent hover:text-accent-foreground"
aria-label={`Remove ${file.name}`}
>
<X className="size-3.5" />
</button>
</Button>
</div>
))}
</div>
@ -217,10 +222,11 @@ export const DropboxConfig: FC<ConnectorConfigProps> = ({ connector, onConfigCha
{isEditMode ? (
<div className="space-y-2">
<button
<Button
type="button"
variant="ghost"
onClick={() => setIsFolderTreeOpen(!isFolderTreeOpen)}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground transition-colors w-fit"
className="h-auto w-fit gap-2 px-0 py-0 text-xs font-normal text-muted-foreground hover:bg-transparent hover:text-accent-foreground sm:text-sm"
>
Change Selection
{isFolderTreeOpen ? (
@ -228,7 +234,7 @@ export const DropboxConfig: FC<ConnectorConfigProps> = ({ connector, onConfigCha
) : (
<ChevronRight className="size-4" />
)}
</button>
</Button>
{isFolderTreeOpen && (
<DriveFolderTree
fetchItems={fetchItems}

View file

@ -92,20 +92,19 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfi
const [selectedFiles, setSelectedFiles] = useState<SelectedItem[]>(existingFiles);
const [indexingOptions, setIndexingOptions] = useState<IndexingOptions>(existingIndexingOptions);
const updateConfig = (
folders: SelectedItem[],
files: SelectedItem[],
options: IndexingOptions
) => {
if (onConfigChange) {
onConfigChange({
...connector.config,
selected_folders: folders,
selected_files: files,
indexing_options: options,
});
}
};
const updateConfig = useCallback(
(folders: SelectedItem[], files: SelectedItem[], options: IndexingOptions) => {
if (onConfigChange) {
onConfigChange({
...connector.config,
selected_folders: folders,
selected_files: files,
indexing_options: options,
});
}
},
[connector.config, onConfigChange]
);
const handlePicked = useCallback(
(result: PickerResult) => {
@ -115,8 +114,7 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfi
setSelectedFiles(files);
updateConfig(folders, files, indexingOptions);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[indexingOptions, connector.config]
[indexingOptions, updateConfig]
);
const {
@ -188,14 +186,16 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfi
>
<FolderClosed className="size-3.5 shrink-0 text-muted-foreground" />
<span className="flex-1 truncate">{folder.name}</span>
<button
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveFolder(folder.id)}
className="shrink-0 p-0.5 hover:bg-muted-foreground/20 rounded transition-colors"
className="size-5 shrink-0 rounded p-0 hover:bg-accent hover:text-accent-foreground"
aria-label={`Remove ${folder.name}`}
>
<X className="size-3.5" />
</button>
</Button>
</div>
))}
{selectedFiles.map((file) => (
@ -206,14 +206,16 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfi
>
{getFileIconFromName(file.name)}
<span className="flex-1 truncate">{file.name}</span>
<button
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveFile(file.id)}
className="shrink-0 p-0.5 hover:bg-muted-foreground/20 rounded transition-colors"
className="size-5 shrink-0 rounded p-0 hover:bg-accent hover:text-accent-foreground"
aria-label={`Remove ${file.name}`}
>
<X className="size-3.5" />
</button>
</Button>
</div>
))}
</div>
@ -225,7 +227,7 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfi
variant="outline"
onClick={openPicker}
disabled={pickerLoading || isAuthExpired}
className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 hover:bg-slate-400/10 dark:hover:bg-white/10 text-xs sm:text-sm h-8 sm:h-9"
className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 hover:bg-accent hover:text-accent-foreground text-xs sm:text-sm h-8 sm:h-9"
>
{pickerLoading && <Spinner size="xs" className="mr-1.5" />}
{totalSelected > 0 ? "Change Selection" : "Select from Google Drive"}

View file

@ -3,6 +3,7 @@
import { Info, KeyRound } from "lucide-react";
import type { FC } from "react";
import { useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import type { ConnectorConfigProps } from "../index";
@ -65,23 +66,17 @@ export const JiraConfig: FC<JiraConfigProps> = ({ connector, onConfigChange, onN
return (
<div className="space-y-6">
{/* OAuth Info */}
<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">Connected via OAuth</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
This connector is authenticated using OAuth 2.0. Your Jira instance is:
</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
<Alert>
<Info />
<AlertTitle>Connected via OAuth</AlertTitle>
<AlertDescription>
<p>This connector is authenticated using OAuth 2.0. Your Jira instance is:</p>
<p>
<code className="bg-muted px-1 py-0.5 rounded text-inherit">{baseUrl}</code>
</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
To update your connection, reconnect this connector.
</p>
</div>
</div>
<p>To update your connection, reconnect this connector.</p>
</AlertDescription>
</Alert>
</div>
);
}

View file

@ -215,7 +215,7 @@ export const MCPConfig: FC<MCPConfigProps> = ({ connector, onConfigChange, onNam
onClick={handleTestConnection}
disabled={isTesting}
variant="secondary"
className="w-full h-8 text-[13px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80"
className="w-full h-8 text-[13px] px-3 font-medium bg-white text-slate-700 hover:bg-accent hover:text-accent-foreground border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground"
>
{isTesting ? (
<>

View file

@ -47,12 +47,10 @@ export const ObsidianConfig: FC<ConnectorConfigProps> = ({ connector }) => {
const LegacyBanner: FC = () => {
return (
<div className="space-y-6">
<Alert className="border-amber-500/40 bg-amber-500/10">
<AlertTriangle className="size-4 shrink-0 text-amber-500" />
<AlertTitle className="text-xs sm:text-sm">
Sync stopped, install the plugin to migrate
</AlertTitle>
<AlertDescription className="text-[11px] sm:text-xs leading-relaxed">
<Alert variant="warning">
<AlertTriangle />
<AlertTitle>Sync stopped, install the plugin to migrate</AlertTitle>
<AlertDescription>
This Obsidian connector used the legacy server-path scanner, which has been removed. The
notes already indexed remain searchable, but they no longer reflect changes made in your
vault.
@ -124,10 +122,10 @@ const PluginStats: FC<{ config: Record<string, unknown> }> = ({ config }) => {
return (
<div className="space-y-4">
<Alert className="border-emerald-500/30 bg-emerald-500/10">
<Info className="size-4 shrink-0 text-emerald-500" />
<AlertTitle className="text-xs sm:text-sm">Plugin connected</AlertTitle>
<AlertDescription className="text-[11px] sm:text-xs">
<Alert>
<Info />
<AlertTitle>Plugin connected</AlertTitle>
<AlertDescription>
Your notes stay synced automatically. To stop syncing, disable or uninstall the plugin in
Obsidian, or delete this connector.
</AlertDescription>
@ -152,11 +150,11 @@ const PluginStats: FC<{ config: Record<string, unknown> }> = ({ config }) => {
const UnknownConnectorState: FC = () => (
<Alert>
<Info className="size-4 shrink-0" />
<AlertTitle className="text-xs sm:text-sm">Unrecognized config</AlertTitle>
<AlertDescription className="text-[11px] sm:text-xs">
This connector has neither plugin metadata nor a legacy marker. It may predate migration you
can safely delete it and re-install the SurfSense Obsidian plugin to resume syncing.
<Info />
<AlertTitle>Unrecognized config</AlertTitle>
<AlertDescription>
This connector is missing plugin metadata and may predate the Obsidian plugin migration. You
can safely delete it and reinstall the SurfSense Obsidian plugin to resume syncing.
</AlertDescription>
</Alert>
);

View file

@ -14,6 +14,7 @@ import {
import type { FC } from "react";
import { useCallback, useEffect, useState } from "react";
import { DriveFolderTree, type SelectedFolder } from "@/components/connectors/drive-folder-tree";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
Select,
@ -178,14 +179,16 @@ export const OneDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfigCh
>
<FolderClosed className="size-3.5 shrink-0 text-muted-foreground" />
<span className="flex-1 truncate">{folder.name}</span>
<button
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveFolder(folder.id)}
className="shrink-0 p-0.5 hover:bg-muted-foreground/20 rounded transition-colors"
className="size-5 shrink-0 rounded p-0 hover:bg-accent hover:text-accent-foreground"
aria-label={`Remove ${folder.name}`}
>
<X className="size-3.5" />
</button>
</Button>
</div>
))}
{selectedFiles.map((file) => (
@ -196,14 +199,16 @@ export const OneDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfigCh
>
{getFileIconFromName(file.name)}
<span className="flex-1 truncate">{file.name}</span>
<button
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => handleRemoveFile(file.id)}
className="shrink-0 p-0.5 hover:bg-muted-foreground/20 rounded transition-colors"
className="size-5 shrink-0 rounded p-0 hover:bg-accent hover:text-accent-foreground"
aria-label={`Remove ${file.name}`}
>
<X className="size-3.5" />
</button>
</Button>
</div>
))}
</div>
@ -218,10 +223,11 @@ export const OneDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfigCh
{isEditMode ? (
<div className="space-y-2">
<button
<Button
type="button"
variant="ghost"
onClick={() => setIsFolderTreeOpen((prev) => !prev)}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground transition-colors w-fit"
className="h-auto w-fit gap-2 px-0 py-0 text-xs font-normal text-muted-foreground hover:bg-transparent hover:text-accent-foreground sm:text-sm"
>
Change Selection
{isFolderTreeOpen ? (
@ -229,7 +235,7 @@ export const OneDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfigCh
) : (
<ChevronRight className="size-4" />
)}
</button>
</Button>
{isFolderTreeOpen && (
<DriveFolderTree
fetchItems={fetchItems}

View file

@ -2,6 +2,7 @@
import { AlertCircle, CheckCircle2, Hash, Info, Lock, RefreshCw } from "lucide-react";
import { type FC, useCallback, useEffect, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { connectorsApiService, type SlackChannel } from "@/lib/apis/connectors-api.service";
@ -74,20 +75,20 @@ export const SlackConfig: FC<SlackConfigProps> = ({ connector }) => {
return (
<div className="space-y-6">
{/* 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" />
</div>
<div className="text-xs sm:text-sm">
<p className="font-medium text-xs sm:text-sm">Add Bot to Channels</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
<Alert>
<Info />
<AlertTitle>Add Bot to Channels</AlertTitle>
<AlertDescription>
<p>
Before indexing, add the SurfSense bot to each channel you want to index. The bot can
only access messages from channels it's been added to. Type{" "}
<code className="bg-muted px-1 py-0.5 rounded text-[9px]">/invite @SurfSense</code> in
any channel to add it.
<code className="rounded bg-popover px-1 py-0.5 text-[9px] text-popover-foreground">
/invite @SurfSense
</code>{" "}
in any channel to add it.
</p>
</div>
</div>
</AlertDescription>
</Alert>
{/* Channels Section */}
<div className="space-y-3">
@ -104,7 +105,7 @@ export const SlackConfig: FC<SlackConfigProps> = ({ connector }) => {
size="sm"
onClick={fetchChannels}
disabled={isLoading}
className="h-7 px-2.5 text-[11px] bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 border-slate-400/20 dark:border-white/20"
className="h-7 px-2.5 text-[11px] bg-slate-400/10 dark:bg-white/10 hover:bg-accent hover:text-accent-foreground border-slate-400/20 dark:border-white/20"
>
<RefreshCw className={cn("mr-1.5 size-3", isLoading && "animate-spin")} />
Refresh
@ -178,7 +179,7 @@ interface ChannelPillProps {
const ChannelPill: FC<ChannelPillProps> = ({ channel }) => {
return (
<div className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 transition-colors">
<div className="inline-flex items-center gap-1 px-2 py-1 rounded-md text-[11px] font-medium bg-slate-400/10 dark:bg-white/10 hover:bg-accent hover:text-accent-foreground transition-colors">
{channel.is_private ? (
<Lock className="size-2.5 text-muted-foreground" />
) : (

View file

@ -2,6 +2,7 @@
import { Info } from "lucide-react";
import type { FC } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import type { ConnectorConfigProps } from "../index";
export interface TeamsConfigProps extends ConnectorConfigProps {
@ -11,19 +12,17 @@ export interface TeamsConfigProps extends ConnectorConfigProps {
export const TeamsConfig: FC<TeamsConfigProps> = () => {
return (
<div className="space-y-6">
<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">Microsoft Teams Access</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
<Alert>
<Info />
<AlertTitle>Microsoft Teams Access</AlertTitle>
<AlertDescription>
<p>
Your agent can search and read messages from Teams channels you have access to, and send
messages on your behalf. Make sure you&#39;re a member of the teams you want to interact
with.
</p>
</div>
</div>
</AlertDescription>
</Alert>
</div>
);
};

View file

@ -52,13 +52,13 @@ export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({ connector, onConfig
</div>
{/* Chat tip */}
<div className="flex items-start gap-3 rounded-lg border border-blue-200/50 bg-blue-50/50 dark:border-blue-500/20 dark:bg-blue-950/20 p-3 text-xs sm:text-sm">
<Info className="size-4 mt-0.5 shrink-0 text-blue-600 dark:text-blue-400" />
<p className="text-muted-foreground">
<Alert>
<Info />
<AlertDescription>
Want a quick answer from a webpage without indexing it? Just paste the URL directly into
the chat instead.
</p>
</div>
</AlertDescription>
</Alert>
{/* API Key Field */}
<div className="space-y-2">
@ -79,7 +79,7 @@ export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({ connector, onConfig
variant="ghost"
size="sm"
onClick={() => setShowApiKey((prev) => !prev)}
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 px-2 text-xs text-muted-foreground hover:text-foreground"
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 px-2 text-xs text-muted-foreground hover:text-accent-foreground"
>
{showApiKey ? "Hide" : "Show"}
</Button>
@ -116,9 +116,9 @@ export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({ connector, onConfig
</div>
{/* Info Alert */}
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3">
<Info className="size-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs">
<Alert>
<Info />
<AlertDescription>
Configuration is saved when you start indexing. You can update these settings anytime from
the connector management page.
</AlertDescription>

View file

@ -90,14 +90,15 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
{/* Header */}
<div className="flex-shrink-0 px-6 sm:px-12 pt-8 sm:pt-10">
<button
<Button
variant="ghost"
type="button"
onClick={onBack}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
className="mb-6 h-auto w-fit justify-start gap-2 px-0 py-0 text-xs text-muted-foreground hover:bg-transparent hover:text-accent-foreground sm:text-sm"
>
<ArrowLeft className="size-4" />
<ArrowLeft data-icon="inline-start" />
Back to connectors
</button>
</Button>
<div className="flex items-center gap-4 mb-6">
<div className="flex h-14 w-14 items-center justify-center rounded-xl border border-slate-400/30">
@ -133,7 +134,7 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
</div>
{/* Fixed Footer - Action buttons */}
<div className="flex-shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-muted border-t border-border">
<div className="flex-shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-popover">
<Button
variant="ghost"
onClick={onBack}

View file

@ -5,6 +5,7 @@ import { ArrowLeft, Info, RefreshCw } from "lucide-react";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { EnumConnectorName } from "@/contracts/enums/connector";
@ -206,14 +207,15 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
)}
>
{/* Back button */}
<button
<Button
variant="ghost"
type="button"
onClick={onBack}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
className="mb-6 h-auto w-fit justify-start gap-2 px-0 py-0 text-xs text-muted-foreground hover:bg-transparent hover:text-accent-foreground sm:text-sm"
>
<ArrowLeft className="size-4" />
<ArrowLeft data-icon="inline-start" />
Back to connectors
</button>
</Button>
{/* Connector header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 mb-6">
@ -239,7 +241,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
size="sm"
onClick={handleQuickIndex}
disabled={isQuickIndexing || isIndexing || isSaving || isDisconnecting}
className="text-xs sm:text-sm bg-slate-400/10 dark:bg-white/10 hover:bg-slate-400/20 dark:hover:bg-white/20 border-slate-400/20 dark:border-white/20 w-full sm:w-auto"
className="text-xs sm:text-sm bg-slate-400/10 dark:bg-white/10 hover:bg-accent hover:text-accent-foreground border-slate-400/20 dark:border-white/20 w-full sm:w-auto"
>
{isQuickIndexing || isIndexing ? (
<>
@ -349,41 +351,33 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
{/* Info box - hidden for live connectors */}
{connector.is_indexable && !isLive && (
<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 inbox for
updates.
</p>
</div>
</div>
<Alert>
<Info />
<AlertDescription>
You can continue using SurfSense while we sync your data. Check inbox for updates.
</AlertDescription>
</Alert>
)}
</div>
</div>
{/* Top fade shadow - appears when scrolled */}
{isScrolled && (
<div className="absolute top-0 left-0 right-0 h-6 bg-gradient-to-b from-muted/50 to-transparent pointer-events-none z-10" />
<div className="absolute top-0 left-0 right-0 h-6 bg-gradient-to-b from-popover to-transparent pointer-events-none z-10" />
)}
{/* Bottom fade shadow - appears when there's more content */}
{hasMoreContent && (
<div className="absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-t from-muted/50 to-transparent pointer-events-none z-10" />
<div className="absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-t from-popover to-transparent pointer-events-none z-10" />
)}
</div>
{/* Fixed Footer - Action buttons */}
<div className="flex-shrink-0 flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3 sm:gap-0 px-6 sm:px-12 py-6 sm:py-6 bg-muted border-t border-border">
<div className="flex-shrink-0 flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-3 sm:gap-0 px-6 sm:px-12 py-6 sm:py-6 bg-popover">
{showDisconnectConfirm ? (
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 flex-1 sm:flex-initial">
<span className="text-xs sm:text-sm text-muted-foreground sm:whitespace-nowrap">
{isLive
? "Your agent will lose access to this service."
: "This will remove all indexed data."}
? "Your agent will lose access to this service"
: "This will remove all indexed data"}
</span>
<div className="flex items-center gap-2 sm:gap-3">
<Button

View file

@ -2,6 +2,7 @@
import { ArrowLeft, Check, Info } from "lucide-react";
import { type FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { EnumConnectorName } from "@/contracts/enums/connector";
@ -128,14 +129,15 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
>
{/* Back button - only show if not from OAuth */}
{!isFromOAuth && (
<button
<Button
variant="ghost"
type="button"
onClick={onSkip}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
className="mb-6 h-auto w-fit justify-start gap-2 px-0 py-0 text-xs text-muted-foreground hover:bg-transparent hover:text-accent-foreground sm:text-sm"
>
<ArrowLeft className="size-4" />
<ArrowLeft data-icon="inline-start" />
Back to connectors
</button>
</Button>
)}
{/* Success header */}
@ -229,33 +231,27 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
{/* Info box - hidden for live connectors */}
{connector?.is_indexable && !isLive && (
<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 inbox for
updates.
</p>
</div>
</div>
<Alert>
<Info />
<AlertDescription>
You can continue using SurfSense while we sync your data. Check inbox for updates.
</AlertDescription>
</Alert>
)}
</div>
</div>
{/* Top fade shadow - appears when scrolled */}
{isScrolled && (
<div className="absolute top-0 left-0 right-0 h-6 bg-gradient-to-b from-muted/50 to-transparent pointer-events-none z-10" />
<div className="absolute top-0 left-0 right-0 h-6 bg-gradient-to-b from-popover to-transparent pointer-events-none z-10" />
)}
{/* Bottom fade shadow - appears when there's more content */}
{hasMoreContent && (
<div className="absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-t from-muted/50 to-transparent pointer-events-none z-10" />
<div className="absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-t from-popover to-transparent pointer-events-none z-10" />
)}
</div>
{/* Fixed Footer - Action buttons */}
<div className="flex-shrink-0 flex items-center justify-end px-6 sm:px-12 py-6 bg-muted">
<div className="flex-shrink-0 flex items-center justify-end px-6 sm:px-12 py-6 bg-popover">
{isLive ? (
<Button onClick={onSkip} className="text-xs sm:text-sm">
Done

View file

@ -185,7 +185,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
"relative flex items-center gap-4 p-4 rounded-xl transition-all",
isAnyIndexing
? "bg-primary/5 border-0"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10 border border-border"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-accent hover:text-accent-foreground border border-border"
)}
>
<div
@ -222,7 +222,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
<Button
variant="secondary"
size="sm"
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
className="h-8 text-[11px] px-3 font-medium bg-white text-slate-700 hover:bg-accent hover:text-accent-foreground border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground shrink-0"
onClick={handleManageClick}
>
Manage
@ -247,7 +247,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
"flex items-center gap-4 p-4 rounded-xl transition-all",
isIndexing
? "bg-primary/5 border-0"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10 border border-border"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-accent hover:text-accent-foreground border border-border"
)}
>
<div
@ -280,7 +280,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
<Button
variant="secondary"
size="sm"
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
className="h-8 text-[11px] px-3 font-medium bg-white text-slate-700 hover:bg-accent hover:text-accent-foreground border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground shrink-0"
onClick={onManage ? () => onManage(connector) : undefined}
>
Manage
@ -302,7 +302,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
{standaloneDocuments.map((doc) => (
<div
key={doc.type}
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full border border-border bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10 transition-all"
className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full border border-border bg-slate-400/5 dark:bg-white/5 hover:bg-accent hover:text-accent-foreground transition-all"
>
<div className="flex items-center justify-center">
{getConnectorIcon(doc.type, "size-3.5")}

View file

@ -24,6 +24,7 @@ interface ConnectorAccountsListViewProps {
indexingConnectorIds: Set<number>;
onBack: () => void;
onManage: (connector: SearchSourceConnector) => void;
onDisconnect?: (connector: SearchSourceConnector) => Promise<void> | void;
onAddAccount: () => void;
isConnecting?: boolean;
addButtonText?: string;
@ -36,12 +37,15 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
indexingConnectorIds,
onBack,
onManage,
onDisconnect,
onAddAccount,
isConnecting = false,
addButtonText,
}) => {
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
const [reauthingId, setReauthingId] = useState<number | null>(null);
const [confirmDisconnectId, setConfirmDisconnectId] = useState<number | null>(null);
const [disconnectingId, setDisconnectingId] = useState<number | null>(null);
// Get connector status
const { isConnectorEnabled, getConnectorStatusMessage } = useConnectorStatus();
@ -104,16 +108,17 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="px-6 sm:px-12 pt-8 sm:pt-10 pb-1 sm:pb-4 border-b border-border/50 bg-muted">
<div className="px-6 sm:px-12 pt-8 sm:pt-10 pb-1 sm:pb-4 bg-popover">
{/* Back button */}
<button
<Button
type="button"
variant="ghost"
onClick={onBack}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
className="mb-6 h-auto w-fit gap-2 px-0 py-0 text-xs font-normal text-muted-foreground hover:bg-transparent hover:text-accent-foreground sm:text-sm"
>
<ArrowLeft className="size-4" />
Back to connectors
</button>
</Button>
{/* Connector header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 mb-6">
@ -131,15 +136,16 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
</div>
</div>
{/* Add Account Button with dashed border */}
<button
<Button
type="button"
variant="ghost"
onClick={onAddAccount}
disabled={isConnecting || !isEnabled}
className={cn(
"flex items-center justify-center gap-1.5 h-8 px-3 rounded-md border-2 border-dashed text-xs sm:text-sm transition-all duration-200 shrink-0 w-full sm:w-auto",
"h-8 w-full shrink-0 gap-1.5 rounded-md border-2 border-dashed px-3 text-xs transition-all duration-200 sm:w-auto sm:text-sm",
!isEnabled
? "border-border/30 opacity-50 cursor-not-allowed"
: "border-slate-400/20 dark:border-white/20 hover:bg-primary/5",
: "border-slate-400/20 dark:border-white/20 hover:bg-accent hover:text-accent-foreground",
isConnecting && "opacity-50 cursor-not-allowed"
)}
>
@ -151,7 +157,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
)}
</div>
<span className="text-xs sm:text-sm font-medium">{buttonText}</span>
</button>
</Button>
</div>
</div>
@ -194,7 +200,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
"flex items-center gap-4 p-4 rounded-xl transition-all",
isIndexing
? "bg-primary/5 border-0"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10 border border-border"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-accent hover:text-accent-foreground border border-border"
)}
>
<div
@ -227,7 +233,7 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
{isAuthExpired ? (
<Button
size="sm"
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-amber-600 hover:bg-amber-700 text-white border-0 shadow-xs shrink-0"
className="h-8 text-[11px] px-3 font-medium bg-amber-600 hover:bg-amber-700 text-white border-0 shadow-xs shrink-0"
onClick={() => handleReauth(connector)}
disabled={reauthingId === connector.id}
>
@ -236,11 +242,55 @@ export const ConnectorAccountsListView: FC<ConnectorAccountsListViewProps> = ({
/>
Re-authenticate
</Button>
) : isLive && onDisconnect ? (
confirmDisconnectId === connector.id ? (
<div className="flex items-center gap-1.5 shrink-0">
<Button
variant="destructive"
size="sm"
className="h-8 text-[11px] px-3 font-medium shadow-xs"
onClick={async () => {
setDisconnectingId(connector.id);
setConfirmDisconnectId(null);
try {
await onDisconnect(connector);
} finally {
setDisconnectingId(null);
}
}}
disabled={disconnectingId === connector.id}
>
{disconnectingId === connector.id ? (
<RefreshCw className="size-3.5 animate-spin" />
) : (
"Confirm"
)}
</Button>
<Button
variant="ghost"
size="sm"
className="h-8 text-[11px] px-2"
onClick={() => setConfirmDisconnectId(null)}
disabled={disconnectingId === connector.id}
>
Cancel
</Button>
</div>
) : (
<Button
variant="destructive"
size="sm"
className="h-8 text-[11px] px-3 font-medium shrink-0"
onClick={() => setConfirmDisconnectId(connector.id)}
>
Disconnect
</Button>
)
) : (
<Button
variant="secondary"
size="sm"
className="h-8 text-[11px] px-3 rounded-lg font-medium bg-white text-slate-700 hover:bg-slate-50 border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground dark:hover:bg-secondary/80 shrink-0"
className="h-8 text-[11px] px-3 font-medium bg-white text-slate-700 hover:bg-accent hover:text-accent-foreground border-0 shadow-xs dark:bg-secondary dark:text-secondary-foreground shrink-0"
onClick={() => onManage(connector)}
>
Manage

View file

@ -7,6 +7,7 @@ import { useTranslations } from "next-intl";
import { type FC, useCallback, useState } from "react";
import { toast } from "sonner";
import { createDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Spinner } from "@/components/ui/spinner";
@ -216,14 +217,15 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId,
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
{/* Header */}
<div className="shrink-0 px-6 sm:px-12 pt-8 sm:pt-10">
<button
<Button
variant="ghost"
type="button"
onClick={onBack}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
className="mb-6 h-auto w-fit justify-start gap-2 px-0 py-0 text-xs text-muted-foreground hover:bg-transparent hover:text-accent-foreground sm:text-sm"
>
<ArrowLeft className="size-4" />
<ArrowLeft data-icon="inline-start" />
Back to connectors
</button>
</Button>
<div className="flex items-center gap-4 mb-6">
<div className="flex h-14 w-14 items-center justify-center rounded-xl border border-slate-400/30">
@ -259,7 +261,7 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId,
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",
"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-accent-foreground",
},
}}
activeTagIndex={activeTagIndex}
@ -278,10 +280,10 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId,
{error && <div className="text-sm text-red-500 mt-2">{error}</div>}
<div className="flex items-start gap-3 rounded-lg border border-blue-200/50 bg-blue-50/50 dark:border-blue-500/20 dark:bg-blue-950/20 p-4 text-sm">
<Info className="size-4 mt-0.5 shrink-0 text-blue-600 dark:text-blue-400" />
<p className="text-muted-foreground">{t("chat_tip")}</p>
</div>
<Alert>
<Info />
<AlertDescription>{t("chat_tip")}</AlertDescription>
</Alert>
<div className="bg-muted/50 rounded-lg p-4 text-sm">
<h4 className="font-medium mb-2">{t("tips_title")}</h4>
@ -323,7 +325,7 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId,
</div>
{/* Fixed Footer - Action buttons */}
<div className="shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-muted border-t border-border">
<div className="shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-popover">
<Button
variant="ghost"
onClick={onBack}