chore: ran frontend linting

This commit is contained in:
Anish Sarkar 2026-01-01 22:24:42 +05:30
parent f9a10c1e0d
commit 95878368c8
74 changed files with 2347 additions and 1697 deletions

View file

@ -137,19 +137,19 @@ export function DocumentsTableShell({
<div className="rounded-full bg-muted p-4">
<FileX className="h-8 w-8 text-muted-foreground" />
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold">{t("no_documents")}</h3>
<p className="text-sm text-muted-foreground">
Get started by uploading your first document.
</p>
</div>
<Button
onClick={() => router.push(`/dashboard/${searchSpaceId}/documents/upload`)}
className="mt-2"
>
<Plus className="mr-2 h-4 w-4" />
Upload Documents
</Button>
<div className="space-y-2">
<h3 className="text-lg font-semibold">{t("no_documents")}</h3>
<p className="text-sm text-muted-foreground">
Get started by uploading your first document.
</p>
</div>
<Button
onClick={() => router.push(`/dashboard/${searchSpaceId}/documents/upload`)}
className="mt-2"
>
<Plus className="mr-2 h-4 w-4" />
Upload Documents
</Button>
</motion.div>
</div>
) : (

View file

@ -517,4 +517,4 @@ export default function EditorPage() {
</AlertDialog>
</motion.div>
);
}
}

View file

@ -26,13 +26,13 @@ export default function DashboardLayout({
},
];
const customNavMain = [
{
title: "Chat",
url: `/dashboard/${search_space_id}/new-chat`,
icon: "MessageCircle",
items: [],
},
const customNavMain = [
{
title: "Chat",
url: `/dashboard/${search_space_id}/new-chat`,
icon: "MessageCircle",
items: [],
},
{
title: "Documents",
url: `/dashboard/${search_space_id}/documents`,

View file

@ -9,7 +9,10 @@ import { CheckIcon, CopyIcon, DownloadIcon, RefreshCwIcon } from "lucide-react";
import type { FC } from "react";
import { useContext } from "react";
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import { ThinkingStepsContext, ThinkingStepsDisplay } from "@/components/assistant-ui/thinking-steps";
import {
ThinkingStepsContext,
ThinkingStepsDisplay,
} from "@/components/assistant-ui/thinking-steps";
import { ToolFallback } from "@/components/assistant-ui/tool-fallback";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { BranchPicker } from "@/components/assistant-ui/branch-picker";
@ -115,4 +118,3 @@ const AssistantActionBar: FC = () => {
</ActionBarPrimitive.Root>
);
};

View file

@ -30,4 +30,3 @@ export const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({ className,
</BranchPickerPrimitive.Root>
);
};

View file

@ -74,7 +74,9 @@ const ConnectorIndicator: FC = () => {
"text-muted-foreground"
)}
aria-label={
hasConnectors ? `View ${activeConnectorsCount} active connectors` : "Add your first connector"
hasConnectors
? `View ${activeConnectorsCount} active connectors`
: "Add your first connector"
}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
@ -137,7 +139,9 @@ const ConnectorIndicator: FC = () => {
className="flex items-center gap-1.5 rounded-md bg-muted/80 px-2.5 py-1.5 text-xs border border-border/50"
>
{getConnectorIcon(docType, "size-3.5")}
<span className="truncate max-w-[100px]">{getDocumentTypeLabel(docType)}</span>
<span className="truncate max-w-[100px]">
{getDocumentTypeLabel(docType)}
</span>
<span className="flex items-center justify-center min-w-[18px] h-[18px] px-1 text-[10px] font-medium rounded-full bg-primary/10 text-primary">
{count > 999 ? "999+" : count}
</span>
@ -150,7 +154,9 @@ const ConnectorIndicator: FC = () => {
<button
type="button"
className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
onClick={() => {/* Connector popup should be opened via the connector indicator button */}}
onClick={() => {
/* Connector popup should be opened via the connector indicator button */
}}
>
<Plus className="size-3" />
Add more sources
@ -167,7 +173,9 @@ const ConnectorIndicator: FC = () => {
<button
type="button"
className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 transition-colors mt-1"
onClick={() => {/* Connector popup should be opened via the connector indicator button */}}
onClick={() => {
/* Connector popup should be opened via the connector indicator button */
}}
>
<Plus className="size-3" />
Add Connector
@ -283,4 +291,3 @@ export const ComposerAction: FC = () => {
</div>
);
};

View file

@ -8,10 +8,7 @@ import {
mentionedDocumentIdsAtom,
mentionedDocumentsAtom,
} from "@/atoms/chat/mentioned-documents.atom";
import {
ComposerAddAttachment,
ComposerAttachments,
} from "@/components/assistant-ui/attachment";
import { ComposerAddAttachment, ComposerAttachments } from "@/components/assistant-ui/attachment";
import { ComposerAction } from "@/components/assistant-ui/composer-action";
import {
InlineMentionEditor,
@ -237,4 +234,3 @@ export const Composer: FC = () => {
</ComposerPrimitive.Root>
);
};

View file

@ -10,14 +10,8 @@ import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-quer
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import {
Dialog,
DialogContent,
} from "@/components/ui/dialog";
import {
Tabs,
TabsContent,
} from "@/components/ui/tabs";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Tabs, TabsContent } from "@/components/ui/tabs";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { cn } from "@/lib/utils";
import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab";
@ -35,19 +29,15 @@ export const ConnectorIndicator: FC = () => {
const searchParams = useSearchParams();
const { data: documentTypeCounts, isLoading: documentTypesLoading } =
useAtomValue(documentTypeCountsAtom);
// Check if YouTube view is active
const isYouTubeView = searchParams.get("view") === "youtube";
// Track active indexing tasks
const { summary: logsSummary } = useLogsSummary(
searchSpaceId ? Number(searchSpaceId) : 0,
24,
{
enablePolling: true,
refetchInterval: 5000,
}
);
const { summary: logsSummary } = useLogsSummary(searchSpaceId ? Number(searchSpaceId) : 0, 24, {
enablePolling: true,
refetchInterval: 5000,
});
// Use the custom hook for dialog state management
const {
@ -202,10 +192,7 @@ export const ConnectorIndicator: FC = () => {
<DialogContent className="max-w-3xl w-[95vw] sm:w-full h-[90vh] sm:h-[85vh] flex flex-col p-0 gap-0 overflow-hidden border border-border bg-muted text-foreground [&>button]:right-6 sm:[&>button]:right-12 [&>button]:top-8 sm:[&>button]:top-10 [&>button]:opacity-80 hover:[&>button]:opacity-100 [&>button_svg]:size-5">
{/* YouTube Crawler View - shown when adding YouTube videos */}
{isYouTubeView && searchSpaceId ? (
<YouTubeCrawlerView
searchSpaceId={searchSpaceId}
onBack={handleBackFromYouTube}
/>
<YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} />
) : connectingConnectorType ? (
<ConnectorConnectView
connectorType={connectingConnectorType}
@ -234,17 +221,25 @@ export const ConnectorIndicator: FC = () => {
onSave={() => handleSaveConnector(() => refreshConnectors())}
onDisconnect={() => handleDisconnectConnector(() => refreshConnectors())}
onBack={handleBackFromEdit}
onQuickIndex={editingConnector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" ? () => handleQuickIndexConnector(editingConnector.id) : undefined}
onQuickIndex={
editingConnector.connector_type !== "GOOGLE_DRIVE_CONNECTOR"
? () => handleQuickIndexConnector(editingConnector.id)
: undefined
}
onConfigChange={setConnectorConfig}
onNameChange={setConnectorName}
/>
) : indexingConfig ? (
<IndexingConfigurationView
config={indexingConfig}
connector={indexingConnector ? {
...indexingConnector,
config: indexingConnectorConfig || indexingConnector.config,
} : undefined}
connector={
indexingConnector
? {
...indexingConnector,
config: indexingConnectorConfig || indexingConnector.config,
}
: undefined
}
startDate={startDate}
endDate={endDate}
periodicEnabled={periodicEnabled}
@ -259,7 +254,11 @@ export const ConnectorIndicator: FC = () => {
onSkip={handleSkipIndexing}
/>
) : (
<Tabs value={activeTab} onValueChange={handleTabChange} className="flex-1 flex flex-col min-h-0">
<Tabs
value={activeTab}
onValueChange={handleTabChange}
className="flex-1 flex flex-col min-h-0"
>
{/* Header */}
<ConnectorDialogHeader
activeTab={activeTab}
@ -274,23 +273,23 @@ export const ConnectorIndicator: FC = () => {
<div className="flex-1 min-h-0 relative overflow-hidden">
<div className="h-full overflow-y-auto" onScroll={handleScroll}>
<div className="px-6 sm:px-12 py-6 sm:py-8 pb-16 sm:pb-16">
<TabsContent value="all" className="m-0">
<AllConnectorsTab
searchQuery={searchQuery}
searchSpaceId={searchSpaceId}
connectedTypes={connectedTypes}
connectingId={connectingId}
allConnectors={allConnectors}
documentTypeCounts={documentTypeCounts}
indexingConnectorIds={indexingConnectorIds}
logsSummary={logsSummary}
onConnectOAuth={handleConnectOAuth}
onConnectNonOAuth={handleConnectNonOAuth}
onCreateWebcrawler={handleCreateWebcrawler}
onCreateYouTubeCrawler={handleCreateYouTubeCrawler}
onManage={handleStartEdit}
/>
</TabsContent>
<TabsContent value="all" className="m-0">
<AllConnectorsTab
searchQuery={searchQuery}
searchSpaceId={searchSpaceId}
connectedTypes={connectedTypes}
connectingId={connectingId}
allConnectors={allConnectors}
documentTypeCounts={documentTypeCounts}
indexingConnectorIds={indexingConnectorIds}
logsSummary={logsSummary}
onConnectOAuth={handleConnectOAuth}
onConnectNonOAuth={handleConnectNonOAuth}
onCreateWebcrawler={handleCreateWebcrawler}
onCreateYouTubeCrawler={handleCreateYouTubeCrawler}
onManage={handleStartEdit}
/>
</TabsContent>
<ActiveConnectorsTab
hasSources={hasSources}

View file

@ -72,11 +72,7 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
return (
<div className="flex items-center gap-2 w-full max-w-[200px]">
<span className="text-[11px] text-primary font-medium whitespace-nowrap">
{indexingCount !== null ? (
<>{indexingCount.toLocaleString()} indexed</>
) : (
"Syncing..."
)}
{indexingCount !== null ? <>{indexingCount.toLocaleString()} indexed</> : "Syncing..."}
</span>
{/* Indeterminate progress bar with animation */}
<div className="relative flex-1 h-1 overflow-hidden rounded-full bg-primary/20">
@ -117,9 +113,7 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
<div className="flex items-center gap-2">
<span className="text-[14px] font-semibold leading-tight">{title}</span>
</div>
<div className="text-[11px] text-muted-foreground mt-1">
{getStatusContent()}
</div>
<div className="text-[11px] text-muted-foreground mt-1">{getStatusContent()}</div>
{isConnected && documentCount !== undefined && (
<p className="text-[11px] text-muted-foreground mt-0.5">
{formatDocumentCount(documentCount)}
@ -131,7 +125,8 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
variant={isConnected ? "secondary" : "default"}
className={cn(
"h-8 text-[11px] px-3 rounded-lg flex-shrink-0 font-medium",
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",
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",
!isConnected && "shadow-xs"
)}
onClick={isConnected ? onManage : onConnect}
@ -154,4 +149,3 @@ export const ConnectorCard: FC<ConnectorCardProps> = ({
</div>
);
};

View file

@ -2,15 +2,8 @@
import { Search } from "lucide-react";
import type { FC } from "react";
import {
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
import { DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
interface ConnectorDialogHeaderProps {
@ -84,4 +77,3 @@ export const ConnectorDialogHeader: FC<ConnectorDialogHeaderProps> = ({
</div>
);
};

View file

@ -43,13 +43,16 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
<div className="rounded-xl bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6">
<h3 className="font-medium text-sm sm:text-base mb-4">Select Date Range</h3>
<p className="text-xs sm:text-sm text-muted-foreground mb-6">
Choose how far back you want to sync your data. You can always re-index later with different dates.
Choose how far back you want to sync your data. You can always re-index later with different
dates.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Start Date */}
<div className="space-y-2">
<Label htmlFor="start-date" className="text-xs sm:text-sm">Start Date</Label>
<Label htmlFor="start-date" className="text-xs sm:text-sm">
Start Date
</Label>
<Popover>
<PopoverTrigger asChild>
<Button
@ -77,7 +80,9 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
{/* End Date */}
<div className="space-y-2">
<Label htmlFor="end-date" className="text-xs sm:text-sm">End Date</Label>
<Label htmlFor="end-date" className="text-xs sm:text-sm">
End Date
</Label>
<Popover>
<PopoverTrigger asChild>
<Button
@ -137,4 +142,3 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
</div>
);
};

View file

@ -39,7 +39,9 @@ export const PeriodicSyncConfig: FC<PeriodicSyncConfigProps> = ({
{enabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select value={frequencyMinutes} onValueChange={onFrequencyChange}>
<SelectTrigger
id="frequency"
@ -48,12 +50,24 @@ export const PeriodicSyncConfig: FC<PeriodicSyncConfigProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -62,4 +76,3 @@ export const PeriodicSyncConfig: FC<PeriodicSyncConfigProps> = ({
</div>
);
};

View file

@ -32,10 +32,7 @@ const baiduSearchApiFormSchema = z.object({
type BaiduSearchApiFormValues = z.infer<typeof baiduSearchApiFormSchema>;
export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const form = useForm<BaiduSearchApiFormValues>({
resolver: zodResolver(baiduSearchApiFormSchema),
@ -77,7 +74,8 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
<div className="-ml-1">
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
You'll need a Baidu AppBuilder API key to use this connector. You can get one by signing up at{" "}
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"
@ -92,7 +90,11 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="baidu-search-api-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="baidu-search-api-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -100,11 +102,11 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Baidu Search Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Baidu Search Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -122,12 +124,12 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Baidu AppBuilder API Key</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Enter your Baidu API key"
<Input
type="password"
placeholder="Enter your Baidu API key"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -155,4 +157,3 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -53,10 +53,7 @@ const bookstackConnectorFormSchema = z.object({
type BookStackConnectorFormValues = z.infer<typeof bookstackConnectorFormSchema>;
export const BookStackConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const BookStackConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -110,14 +107,19 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<div className="-ml-1">
<AlertTitle className="text-xs sm:text-sm">API Token Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
You'll need a BookStack API Token to use this connector. You can create one from your BookStack instance settings.
You'll need a BookStack API Token to use this connector. You can create one from your
BookStack instance settings.
</AlertDescription>
</div>
</Alert>
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="bookstack-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="bookstack-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -125,11 +127,11 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My BookStack Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My BookStack Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -147,16 +149,17 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">BookStack Base URL</FormLabel>
<FormControl>
<Input
<Input
type="url"
placeholder="https://your-bookstack-instance.com"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
placeholder="https://your-bookstack-instance.com"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
The base URL of your BookStack instance (e.g., https://your-bookstack-instance.com).
The base URL of your BookStack instance (e.g.,
https://your-bookstack-instance.com).
</FormDescription>
<FormMessage />
</FormItem>
@ -170,11 +173,11 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Token ID</FormLabel>
<FormControl>
<Input
placeholder="Your Token ID"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="Your Token ID"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -192,12 +195,12 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Token Secret</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Your Token Secret"
<Input
type="password"
placeholder="Your Token Secret"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -211,7 +214,7 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -229,14 +232,24 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -244,12 +257,24 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -264,7 +289,9 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
{/* What you get section */}
{getConnectorBenefits(EnumConnectorName.BOOKSTACK_CONNECTOR) && (
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 px-3 sm:px-6 py-4 space-y-2">
<h4 className="text-xs sm:text-sm font-medium">What you get with BookStack integration:</h4>
<h4 className="text-xs sm:text-sm font-medium">
What you get with BookStack integration:
</h4>
<ul className="list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
{getConnectorBenefits(EnumConnectorName.BOOKSTACK_CONNECTOR)?.map((benefit) => (
<li key={benefit}>{benefit}</li>
@ -274,7 +301,11 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -283,14 +314,17 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<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 BookStack connector uses the BookStack REST API to fetch all pages from your BookStack instance that your account has access to.
The BookStack connector uses the BookStack REST API to fetch all pages from your
BookStack instance that your account has access to.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves pages that have been updated since the last indexing attempt.
For follow up indexing runs, the connector retrieves pages that have been updated
since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your search results within minutes.
Indexing is configured to run periodically, so updates should appear in your
search results within minutes.
</li>
</ul>
</div>
@ -302,13 +336,16 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">API Token Required</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You need to create an API token from your BookStack instance. The token requires "Access System API" permission.
You need to create an API token from your BookStack instance. The token requires
"Access System API" permission.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Create an API Token</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Create an API Token
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Log in to your BookStack instance</li>
<li>Click on your profile icon Edit Profile</li>
@ -320,15 +357,19 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Grant necessary access</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Grant necessary access
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
Your user account must have "Access System API" permission. The connector will only index content your account can view.
Your user account must have "Access System API" permission. The connector will
only index content your account can view.
</p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Rate Limiting</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
BookStack API has a rate limit of 180 requests per minute. The connector automatically handles rate limiting to ensure reliable indexing.
BookStack API has a rate limit of 180 requests per minute. The connector
automatically handles rate limiting to ensure reliable indexing.
</AlertDescription>
</Alert>
</div>
@ -341,13 +382,16 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
Navigate to the Connector Dashboard and select the <strong>BookStack</strong> Connector.
Navigate to the Connector Dashboard and select the <strong>BookStack</strong>{" "}
Connector.
</li>
<li>
Enter your <strong>BookStack Instance URL</strong> (e.g., https://docs.example.com)
Enter your <strong>BookStack Instance URL</strong> (e.g.,
https://docs.example.com)
</li>
<li>
Enter your <strong>Token ID</strong> and <strong>Token Secret</strong> from your BookStack API token.
Enter your <strong>Token ID</strong> and <strong>Token Secret</strong> from your
BookStack API token.
</li>
<li>
Click <strong>Connect</strong> to establish the connection.
@ -376,4 +420,3 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -29,10 +29,7 @@ const circlebackFormSchema = z.object({
type CirclebackFormValues = z.infer<typeof circlebackFormSchema>;
export const CirclebackConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const CirclebackConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const form = useForm<CirclebackFormValues>({
resolver: zodResolver(circlebackFormSchema),
@ -71,14 +68,19 @@ export const CirclebackConnectForm: FC<ConnectFormProps> = ({
<div className="-ml-1">
<AlertTitle className="text-xs sm:text-sm">Webhook-Based Integration</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
Circleback uses webhooks to automatically send meeting data. After connecting, you'll receive a webhook URL to configure in your Circleback settings.
Circleback uses webhooks to automatically send meeting data. After connecting, you'll
receive a webhook URL to configure in your Circleback settings.
</AlertDescription>
</div>
</Alert>
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="circleback-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="circleback-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -86,11 +88,11 @@ export const CirclebackConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Circleback Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Circleback Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -118,4 +120,3 @@ export const CirclebackConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -49,10 +49,7 @@ const clickupConnectorFormSchema = z.object({
type ClickUpConnectorFormValues = z.infer<typeof clickupConnectorFormSchema>;
export const ClickUpConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const ClickUpConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -117,7 +114,11 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="clickup-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="clickup-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -125,11 +126,11 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My ClickUp Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My ClickUp Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -147,12 +148,12 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">ClickUp API Token</FormLabel>
<FormControl>
<Input
type="password"
placeholder="pk_..."
<Input
type="password"
placeholder="pk_..."
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -166,7 +167,7 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -184,14 +185,24 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -199,12 +210,24 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -229,7 +252,11 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -238,14 +265,17 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<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 ClickUp connector uses the ClickUp API to fetch all tasks and projects that your API token has access to within your workspace.
The ClickUp connector uses the ClickUp API to fetch all tasks and projects that your
API token has access to within your workspace.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves tasks that have been updated since the last indexing attempt.
For follow up indexing runs, the connector retrieves tasks that have been updated
since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your search results within minutes.
Indexing is configured to run periodically, so updates should appear in your
search results within minutes.
</li>
</ul>
</div>
@ -257,19 +287,23 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">API Token Required</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You need a ClickUp personal API token to use this connector. The token will be used to read your ClickUp data.
You need a ClickUp personal API token to use this connector. The token will be
used to read your ClickUp data.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Get Your API Token</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Get Your API Token
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Log in to your ClickUp account</li>
<li>Click your avatar in the upper-right corner and select "Settings"</li>
<li>In the sidebar, click "Apps"</li>
<li>
Under "API Token", click <strong>Generate</strong> or <strong>Regenerate</strong>
Under "API Token", click <strong>Generate</strong> or{" "}
<strong>Regenerate</strong>
</li>
<li>Copy the generated token (it typically starts with "pk_")</li>
<li>
@ -288,15 +322,20 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Grant necessary access</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Grant necessary access
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
The API Token will have access to all tasks and projects that your user account can see. Make sure your account has appropriate permissions for the workspaces you want to index.
The API Token will have access to all tasks and projects that your user
account can see. Make sure your account has appropriate permissions for the
workspaces you want to index.
</p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
Only tasks, comments, and basic metadata will be indexed. ClickUp attachments and linked files are not indexed by this connector.
Only tasks, comments, and basic metadata will be indexed. ClickUp
attachments and linked files are not indexed by this connector.
</AlertDescription>
</Alert>
</div>
@ -309,7 +348,8 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
Navigate to the Connector Dashboard and select the <strong>ClickUp</strong> Connector.
Navigate to the Connector Dashboard and select the <strong>ClickUp</strong>{" "}
Connector.
</li>
<li>
Place your <strong>API Token</strong> in the form field.
@ -341,4 +381,3 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -51,10 +51,7 @@ const confluenceConnectorFormSchema = z.object({
type ConfluenceConnectorFormValues = z.infer<typeof confluenceConnectorFormSchema>;
export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const ConfluenceConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -123,7 +120,11 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="confluence-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="confluence-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -131,11 +132,11 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Confluence Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Confluence Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -153,16 +154,17 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Confluence Base URL</FormLabel>
<FormControl>
<Input
<Input
type="url"
placeholder="https://your-domain.atlassian.net"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
placeholder="https://your-domain.atlassian.net"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
The base URL of your Confluence instance (e.g., https://your-domain.atlassian.net).
The base URL of your Confluence instance (e.g.,
https://your-domain.atlassian.net).
</FormDescription>
<FormMessage />
</FormItem>
@ -176,13 +178,13 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Email Address</FormLabel>
<FormControl>
<Input
<Input
type="email"
placeholder="your-email@example.com"
placeholder="your-email@example.com"
autoComplete="email"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -200,12 +202,12 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">API Token</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Your API Token"
<Input
type="password"
placeholder="Your API Token"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -219,7 +221,7 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -237,14 +239,24 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -252,12 +264,24 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -272,7 +296,9 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
{/* What you get section */}
{getConnectorBenefits(EnumConnectorName.CONFLUENCE_CONNECTOR) && (
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 px-3 sm:px-6 py-4 space-y-2">
<h4 className="text-xs sm:text-sm font-medium">What you get with Confluence integration:</h4>
<h4 className="text-xs sm:text-sm font-medium">
What you get with Confluence integration:
</h4>
<ul className="list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
{getConnectorBenefits(EnumConnectorName.CONFLUENCE_CONNECTOR)?.map((benefit) => (
<li key={benefit}>{benefit}</li>
@ -282,7 +308,11 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -291,14 +321,17 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<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 Confluence connector uses the Confluence REST API to fetch all pages and comments that your account has access to within your Confluence instance.
The Confluence connector uses the Confluence REST API to fetch all pages and
comments that your account has access to within your Confluence instance.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves pages and comments that have been updated since the last indexing attempt.
For follow up indexing runs, the connector retrieves pages and comments that have
been updated since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your search results within minutes.
Indexing is configured to run periodically, so updates should appear in your
search results within minutes.
</li>
</ul>
</div>
@ -308,15 +341,20 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 mb-4">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Read-Only Access is Sufficient</AlertTitle>
<AlertTitle className="text-[10px] sm:text-xs">
Read-Only Access is Sufficient
</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You only need read access for this connector to work. The API Token will only be used to read your Confluence data.
You only need read access for this connector to work. The API Token will only be
used to read your Confluence data.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Create an API Token</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Create an API Token
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Log in to your Atlassian account</li>
<li>
@ -343,15 +381,20 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Grant necessary access</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Grant necessary access
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
The API Token will have access to all spaces and pages that your user account can see. Make sure your account has appropriate permissions for the spaces you want to index.
The API Token will have access to all spaces and pages that your user account
can see. Make sure your account has appropriate permissions for the spaces you
want to index.
</p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
Only pages, comments, and basic metadata will be indexed. Confluence attachments and linked files are not indexed by this connector.
Only pages, comments, and basic metadata will be indexed. Confluence
attachments and linked files are not indexed by this connector.
</AlertDescription>
</Alert>
</div>
@ -364,10 +407,12 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
Navigate to the Connector Dashboard and select the <strong>Confluence</strong> Connector.
Navigate to the Connector Dashboard and select the <strong>Confluence</strong>{" "}
Connector.
</li>
<li>
Enter your <strong>Confluence Instance URL</strong> (e.g., https://yourcompany.atlassian.net)
Enter your <strong>Confluence Instance URL</strong> (e.g.,
https://yourcompany.atlassian.net)
</li>
<li>
Enter your <strong>Email Address</strong> associated with your Atlassian account
@ -402,4 +447,3 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -42,19 +42,14 @@ 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.",
}),
bot_token: z.string().min(10, {
message: "Discord Bot Token is required and must be valid.",
}),
});
type DiscordConnectorFormValues = z.infer<typeof discordConnectorFormSchema>;
export const DiscordConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const DiscordConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -119,7 +114,11 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="discord-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="discord-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -127,11 +126,11 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Discord Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Discord Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -149,12 +148,12 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Discord Bot Token</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Your Bot Token"
<Input
type="password"
placeholder="Your Bot Token"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -168,7 +167,7 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -186,14 +185,24 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -201,12 +210,24 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -231,7 +252,11 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -240,13 +265,13 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
<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 Discord connector uses the Discord API to fetch messages from all accessible channels
that the bot token has access to within a server.
The Discord connector uses the Discord API to fetch messages from all accessible
channels that the bot token has access to within a server.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves messages that
have been updated since the last indexing attempt.
For follow up indexing runs, the connector retrieves messages that have been
updated since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your
@ -262,16 +287,19 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Bot Token Required</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You need to create a Discord application and bot to get a bot token.
The bot needs read access to channels and messages.
You need to create a Discord application and bot to get a bot token. The bot
needs read access to channels and messages.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Create a Discord Application</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Create a Discord Application
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Go to{" "}
<li>
Go to{" "}
<a
href="https://discord.com/developers/applications"
target="_blank"
@ -281,30 +309,56 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
https://discord.com/developers/applications
</a>
</li>
<li>Click <strong>New Application</strong></li>
<li>Enter an application name and click <strong>Create</strong></li>
<li>
Click <strong>New Application</strong>
</li>
<li>
Enter an application name and click <strong>Create</strong>
</li>
</ol>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Create a Bot</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Create a Bot
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Navigate to <strong>Bot</strong> in the sidebar</li>
<li>Click <strong>Add Bot</strong> and confirm</li>
<li>Under <strong>Privileged Gateway Intents</strong>, enable:
<li>
Navigate to <strong>Bot</strong> in the sidebar
</li>
<li>
Click <strong>Add Bot</strong> and confirm
</li>
<li>
Under <strong>Privileged Gateway Intents</strong>, enable:
<ul className="list-disc pl-5 mt-1 space-y-1">
<li><code className="bg-muted px-1 py-0.5 rounded">MESSAGE CONTENT INTENT</code> - Required to read message content</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">
MESSAGE CONTENT INTENT
</code>{" "}
- Required to read message content
</li>
</ul>
</li>
</ol>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 3: Get Bot Token and Invite Bot</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 3: Get Bot Token and Invite Bot
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Under <strong>Token</strong>, click <strong>Reset Token</strong> and copy the token</li>
<li>Navigate to <strong>OAuth2 URL Generator</strong></li>
<li>Select <strong>bot</strong> scope and <strong>Read Messages</strong> permission</li>
<li>
Under <strong>Token</strong>, click <strong>Reset Token</strong> and copy
the token
</li>
<li>
Navigate to <strong>OAuth2 URL Generator</strong>
</li>
<li>
Select <strong>bot</strong> scope and <strong>Read Messages</strong>{" "}
permission
</li>
<li>Copy the generated URL and open it in your browser</li>
<li>Select your server and authorize the bot</li>
</ol>
@ -351,4 +405,3 @@ export const DiscordConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -73,10 +73,7 @@ const elasticsearchConnectorFormSchema = z
type ElasticsearchConnectorFormValues = z.infer<typeof elasticsearchConnectorFormSchema>;
export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const authBasicId = useId();
const authApiKeyId = useId();
@ -187,7 +184,11 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="elasticsearch-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="elasticsearch-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -195,11 +196,11 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Elasticsearch Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Elasticsearch Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -231,7 +232,8 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Enter the complete Elasticsearch endpoint URL. We'll automatically extract the hostname, port, and SSL settings.
Enter the complete Elasticsearch endpoint URL. We'll automatically extract the
hostname, port, and SSL settings.
</FormDescription>
<FormMessage />
</FormItem>
@ -241,7 +243,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{/* Show parsed URL details */}
{form.watch("endpoint_url") && (
<div className="rounded-lg border border-border bg-muted/50 p-3">
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Parsed Connection Details:</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Parsed Connection Details:
</h4>
<div className="text-[10px] sm:text-xs text-muted-foreground space-y-1">
{(() => {
try {
@ -305,7 +309,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<div className="h-2.5 w-2.5 rounded-full bg-current" />
</RadioGroup.Indicator>
</RadioGroup.Item>
<Label htmlFor={authApiKeyId} className="text-xs sm:text-sm">API Key</Label>
<Label htmlFor={authApiKeyId} className="text-xs sm:text-sm">
API Key
</Label>
</div>
<div className="flex items-center space-x-2">
@ -318,7 +324,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<div className="h-2.5 w-2.5 rounded-full bg-current" />
</RadioGroup.Indicator>
</RadioGroup.Item>
<Label htmlFor={authBasicId} className="text-xs sm:text-sm">Username & Password</Label>
<Label htmlFor={authBasicId} className="text-xs sm:text-sm">
Username & Password
</Label>
</div>
</RadioGroup.Root>
</FormControl>
@ -337,12 +345,12 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Username</FormLabel>
<FormControl>
<Input
placeholder="elastic"
autoComplete="username"
<Input
placeholder="elastic"
autoComplete="username"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormMessage />
@ -392,7 +400,8 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Enter your Elasticsearch API key (base64 encoded). This will be stored securely.
Enter your Elasticsearch API key (base64 encoded). This will be stored
securely.
</FormDescription>
<FormMessage />
</FormItem>
@ -409,11 +418,11 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Index Selection</FormLabel>
<FormControl>
<Input
placeholder="logs-*, documents-*, app-logs"
<Input
placeholder="logs-*, documents-*, app-logs"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -454,7 +463,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{/* Advanced Configuration */}
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="advanced">
<AccordionTrigger className="text-xs sm:text-sm">Advanced Configuration</AccordionTrigger>
<AccordionTrigger className="text-xs sm:text-sm">
Advanced Configuration
</AccordionTrigger>
<AccordionContent className="space-y-4">
{/* Default Search Query */}
<FormField
@ -467,15 +478,16 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<span className="text-muted-foreground">(Optional)</span>
</FormLabel>
<FormControl>
<Input
placeholder="*"
<Input
placeholder="*"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Default Elasticsearch query to use for searches. Use "*" to match all documents.
Default Elasticsearch query to use for searches. Use "*" to match all
documents.
</FormDescription>
<FormMessage />
</FormItem>
@ -489,19 +501,19 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
render={({ field }) => (
<FormItem>
<FormLabel className="text-xs sm:text-sm">
Search Fields{" "}
<span className="text-muted-foreground">(Optional)</span>
Search Fields <span className="text-muted-foreground">(Optional)</span>
</FormLabel>
<FormControl>
<Input
placeholder="title, content, description"
<Input
placeholder="title, content, description"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Comma-separated list of specific fields to search in (e.g., "title, content, description"). Leave empty to search all fields.
Comma-separated list of specific fields to search in (e.g., "title,
content, description"). Leave empty to search all fields.
</FormDescription>
<FormMessage />
</FormItem>
@ -542,15 +554,14 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{...field}
onChange={(e) =>
field.onChange(
e.target.value === ""
? undefined
: parseInt(e.target.value, 10)
e.target.value === "" ? undefined : parseInt(e.target.value, 10)
)
}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Maximum number of documents to retrieve per search (1-10,000). Leave empty to use Elasticsearch's default limit.
Maximum number of documents to retrieve per search (1-10,000). Leave empty
to use Elasticsearch's default limit.
</FormDescription>
<FormMessage />
</FormItem>
@ -563,7 +574,7 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -581,14 +592,24 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -596,12 +617,24 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -616,7 +649,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{/* What you get section */}
{getConnectorBenefits(EnumConnectorName.ELASTICSEARCH_CONNECTOR) && (
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 px-3 sm:px-6 py-4 space-y-2">
<h4 className="text-xs sm:text-sm font-medium">What you get with Elasticsearch integration:</h4>
<h4 className="text-xs sm:text-sm font-medium">
What you get with Elasticsearch integration:
</h4>
<ul className="list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
{getConnectorBenefits(EnumConnectorName.ELASTICSEARCH_CONNECTOR)?.map((benefit) => (
<li key={benefit}>{benefit}</li>
@ -626,7 +661,11 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -635,7 +674,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<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.
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>
@ -644,43 +685,73 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<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>
<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:
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>
<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>
<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:
<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.
<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>
<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>
<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>
@ -694,19 +765,30 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<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.
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:
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>
<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.
@ -716,7 +798,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<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.
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>
@ -731,28 +815,38 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<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.
<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.
<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.
<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>
<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.
<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.
<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.
<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>
@ -761,13 +855,16 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<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.
<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.
<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.
<strong>Field Not Found:</strong> Ensure the search fields you specify
actually exist in your Elasticsearch documents.
</li>
</ul>
</div>
@ -776,7 +873,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<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.
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>
@ -788,4 +887,3 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -57,10 +57,7 @@ const githubConnectorFormSchema = z.object({
type GithubConnectorFormValues = z.infer<typeof githubConnectorFormSchema>;
export const GithubConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const GithubConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -92,7 +89,7 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
isSubmittingRef.current = true;
try {
const repoList = stringToArray(values.repo_full_names);
await onSubmit({
name: values.name,
connector_type: EnumConnectorName.GITHUB_CONNECTOR,
@ -122,7 +119,8 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<div className="-ml-1">
<AlertTitle className="text-xs sm:text-sm">Personal Access Token Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
You'll need a GitHub Personal Access Token to use this connector. You can create one from{" "}
You'll need a GitHub Personal Access Token to use this connector. You can create one
from{" "}
<a
href="https://github.com/settings/tokens"
target="_blank"
@ -137,7 +135,11 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="github-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="github-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -145,11 +147,11 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My GitHub Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My GitHub Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -167,16 +169,17 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">GitHub Personal Access Token</FormLabel>
<FormControl>
<Input
type="password"
placeholder="ghp_..."
<Input
type="password"
placeholder="ghp_..."
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Your GitHub PAT will be encrypted and stored securely. It typically starts with "ghp_" or "github_pat_".
Your GitHub PAT will be encrypted and stored securely. It typically starts with
"ghp_" or "github_pat_".
</FormDescription>
<FormMessage />
</FormItem>
@ -190,15 +193,16 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Repository Names</FormLabel>
<FormControl>
<Input
placeholder="owner/repo1, owner/repo2"
<Input
placeholder="owner/repo1, owner/repo2"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Comma-separated list of repository full names (e.g., "owner/repo1, owner/repo2").
Comma-separated list of repository full names (e.g., "owner/repo1,
owner/repo2").
</FormDescription>
<FormMessage />
</FormItem>
@ -222,7 +226,7 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -240,14 +244,24 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -255,12 +269,24 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -285,7 +311,11 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -294,7 +324,10 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<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 GitHub connector uses a Personal Access Token (PAT) to authenticate with the GitHub API. You provide a comma-separated list of repository full names (e.g., "owner/repo1, owner/repo2") that you want to index. The connector indexes relevant files (code, markdown, text) from the selected repositories.
The GitHub connector uses a Personal Access Token (PAT) to authenticate with the
GitHub API. You provide a comma-separated list of repository full names (e.g.,
"owner/repo1, owner/repo2") that you want to index. The connector indexes relevant
files (code, markdown, text) from the selected repositories.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
@ -303,7 +336,8 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<li>Large files (over 1MB) are skipped during indexing.</li>
<li>Only specified repositories are indexed.</li>
<li>
Indexing runs periodically (check connector settings for frequency) to keep content up-to-date.
Indexing runs periodically (check connector settings for frequency) to keep
content up-to-date.
</li>
</ul>
</div>
@ -313,15 +347,20 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 mb-4">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Personal Access Token Required</AlertTitle>
<AlertTitle className="text-[10px] sm:text-xs">
Personal Access Token Required
</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You'll need a GitHub PAT with the appropriate scopes (e.g., 'repo') to fetch repositories. The PAT will be stored securely to enable indexing.
You'll need a GitHub PAT with the appropriate scopes (e.g., 'repo') to fetch
repositories. The PAT will be stored securely to enable indexing.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Generate GitHub PAT</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Generate GitHub PAT
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>
Go to your GitHub{" "}
@ -336,39 +375,46 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
</li>
<li>
Click on <strong>Personal access tokens</strong>, then choose{" "}
<strong>Tokens (classic)</strong> or <strong>Fine-grained tokens</strong> (recommended if available).
<strong>Tokens (classic)</strong> or <strong>Fine-grained tokens</strong>{" "}
(recommended if available).
</li>
<li>
Click <strong>Generate new token</strong> (and choose the appropriate type).
</li>
<li>Give your token a descriptive name (e.g., "SurfSense Connector").</li>
<li>Set an expiration date for the token (recommended for security).</li>
<li>
Give your token a descriptive name (e.g., "SurfSense Connector").
</li>
<li>
Set an expiration date for the token (recommended for security).
</li>
<li>
Under <strong>Select scopes</strong> (for classic tokens) or <strong>Repository access</strong> (for fine-grained), grant the necessary permissions. At minimum, the <strong>`repo`</strong> scope (or equivalent read access to repositories for fine-grained tokens) is required to read repository content.
Under <strong>Select scopes</strong> (for classic tokens) or{" "}
<strong>Repository access</strong> (for fine-grained), grant the necessary
permissions. At minimum, the <strong>`repo`</strong> scope (or equivalent
read access to repositories for fine-grained tokens) is required to read
repository content.
</li>
<li>
Click <strong>Generate token</strong>.
</li>
<li>
<strong>Important:</strong> Copy your new PAT immediately. You won't be able to see it again after leaving the page.
<strong>Important:</strong> Copy your new PAT immediately. You won't be able
to see it again after leaving the page.
</li>
</ol>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Specify repositories</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Specify repositories
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
Enter a comma-separated list of repository full names in the format "owner/repo1, owner/repo2". The connector will index files from only the specified repositories.
Enter a comma-separated list of repository full names in the format
"owner/repo1, owner/repo2". The connector will index files from only the
specified repositories.
</p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Repository Access</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
Make sure your PAT has access to all repositories you want to index. Private repositories require appropriate permissions.
Make sure your PAT has access to all repositories you want to index. Private
repositories require appropriate permissions.
</AlertDescription>
</Alert>
</div>
@ -381,13 +427,15 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
Navigate to the Connector Dashboard and select the <strong>GitHub</strong> Connector.
Navigate to the Connector Dashboard and select the <strong>GitHub</strong>{" "}
Connector.
</li>
<li>
Enter your <strong>GitHub Personal Access Token</strong> in the form field.
</li>
<li>
Enter a comma-separated list of <strong>Repository Names</strong> (e.g., "owner/repo1, owner/repo2").
Enter a comma-separated list of <strong>Repository Names</strong> (e.g.,
"owner/repo1, owner/repo2").
</li>
<li>
Click <strong>Connect</strong> to establish the connection.
@ -416,4 +464,3 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -51,10 +51,7 @@ const jiraConnectorFormSchema = z.object({
type JiraConnectorFormValues = z.infer<typeof jiraConnectorFormSchema>;
export const JiraConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const JiraConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -123,7 +120,11 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="jira-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="jira-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -131,11 +132,11 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Jira Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Jira Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -153,12 +154,12 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Jira Base URL</FormLabel>
<FormControl>
<Input
<Input
type="url"
placeholder="https://your-domain.atlassian.net"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
placeholder="https://your-domain.atlassian.net"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -176,13 +177,13 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Email Address</FormLabel>
<FormControl>
<Input
<Input
type="email"
placeholder="your-email@example.com"
placeholder="your-email@example.com"
autoComplete="email"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -200,12 +201,12 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">API Token</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Your API Token"
<Input
type="password"
placeholder="Your API Token"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -219,7 +220,7 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -237,14 +238,24 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -252,12 +263,24 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -282,7 +305,11 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -291,14 +318,17 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<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 Jira connector uses the Jira REST API with Basic Authentication to fetch all issues and comments that your account has access to within your Jira instance.
The Jira connector uses the Jira REST API with Basic Authentication to fetch all
issues and comments that your account has access to within your Jira instance.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves issues and comments that have been updated since the last indexing attempt.
For follow up indexing runs, the connector retrieves issues and comments that have
been updated since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your search results within minutes.
Indexing is configured to run periodically, so updates should appear in your
search results within minutes.
</li>
</ul>
</div>
@ -308,15 +338,20 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 mb-4">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Read-Only Access is Sufficient</AlertTitle>
<AlertTitle className="text-[10px] sm:text-xs">
Read-Only Access is Sufficient
</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You only need read access for this connector to work. The API Token will only be used to read your Jira data.
You only need read access for this connector to work. The API Token will only be
used to read your Jira data.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Create an API Token</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Create an API Token
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Log in to your Atlassian account</li>
<li>
@ -343,15 +378,20 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Grant necessary access</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Grant necessary access
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
The API Token will have access to all projects and issues that your user account can see. Make sure your account has appropriate permissions for the projects you want to index.
The API Token will have access to all projects and issues that your user
account can see. Make sure your account has appropriate permissions for the
projects you want to index.
</p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
Only issues, comments, and basic metadata will be indexed. Jira attachments and linked files are not indexed by this connector.
Only issues, comments, and basic metadata will be indexed. Jira attachments
and linked files are not indexed by this connector.
</AlertDescription>
</Alert>
</div>
@ -364,10 +404,12 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
Navigate to the Connector Dashboard and select the <strong>Jira</strong> Connector.
Navigate to the Connector Dashboard and select the <strong>Jira</strong>{" "}
Connector.
</li>
<li>
Enter your <strong>Jira Instance URL</strong> (e.g., https://yourcompany.atlassian.net)
Enter your <strong>Jira Instance URL</strong> (e.g.,
https://yourcompany.atlassian.net)
</li>
<li>
Enter your <strong>Email Address</strong> associated with your Atlassian account
@ -404,4 +446,3 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -54,10 +54,7 @@ const linearConnectorFormSchema = z.object({
type LinearConnectorFormValues = z.infer<typeof linearConnectorFormSchema>;
export const LinearConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const LinearConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -122,7 +119,11 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="linear-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="linear-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -130,11 +131,11 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Linear Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Linear Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -152,16 +153,17 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Linear API Key</FormLabel>
<FormControl>
<Input
type="password"
placeholder="lin_api_..."
<Input
type="password"
placeholder="lin_api_..."
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Your Linear API Key will be encrypted and stored securely. It typically starts with "lin_api_".
Your Linear API Key will be encrypted and stored securely. It typically starts
with "lin_api_".
</FormDescription>
<FormMessage />
</FormItem>
@ -171,7 +173,7 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -189,14 +191,24 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -204,12 +216,24 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -234,7 +258,11 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -243,13 +271,13 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
<div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3>
<p className="text-[10px] sm:text-xs text-muted-foreground">
The Linear connector uses the Linear GraphQL API to fetch all issues and
comments that the API key has access to within a workspace.
The Linear connector uses the Linear GraphQL API to fetch all issues and comments
that the API key has access to within a workspace.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves issues and comments that
have been updated since the last indexing attempt.
For follow up indexing runs, the connector retrieves issues and comments that have
been updated since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your
@ -263,16 +291,20 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 mb-4">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Read-Only Access is Sufficient</AlertTitle>
<AlertTitle className="text-[10px] sm:text-xs">
Read-Only Access is Sufficient
</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You only need a read-only API key for this connector to work. This limits
the permissions to just reading your Linear data.
You only need a read-only API key for this connector to work. This limits the
permissions to just reading your Linear data.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Create an API key</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Create an API key
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Log in to your Linear account</li>
<li>
@ -297,25 +329,27 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
Click <strong>Create</strong> to generate the API key.
</li>
<li>
Copy the generated API key that starts with 'lin_api_' as it will only
be shown once.
Copy the generated API key that starts with 'lin_api_' as it will only be
shown once.
</li>
</ol>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Grant necessary access</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Grant necessary access
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
The API key will have access to all issues and comments that your user
account can see. If you're creating the key as an admin, it will have
access to all issues in the workspace.
The API key will have access to all issues and comments that your user account
can see. If you're creating the key as an admin, it will have access to all
issues in the workspace.
</p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
Only issues and comments will be indexed. Linear attachments and
linked files are not indexed by this connector.
Only issues and comments will be indexed. Linear attachments and linked
files are not indexed by this connector.
</AlertDescription>
</Alert>
</div>
@ -361,4 +395,3 @@ export const LinearConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -32,10 +32,7 @@ const linkupApiFormSchema = z.object({
type LinkupApiFormValues = z.infer<typeof linkupApiFormSchema>;
export const LinkupApiConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const LinkupApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const form = useForm<LinkupApiFormValues>({
resolver: zodResolver(linkupApiFormSchema),
@ -92,7 +89,11 @@ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="linkup-api-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="linkup-api-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -100,11 +101,11 @@ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Linkup API Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Linkup API Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -122,12 +123,12 @@ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Linkup API Key</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Enter your Linkup API key"
<Input
type="password"
placeholder="Enter your Linkup API key"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -155,4 +156,3 @@ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -49,10 +49,7 @@ const lumaConnectorFormSchema = z.object({
type LumaConnectorFormValues = z.infer<typeof lumaConnectorFormSchema>;
export const LumaConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const LumaConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -117,7 +114,11 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="luma-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="luma-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -125,11 +126,11 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Luma Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Luma Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -147,12 +148,12 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Luma API Key</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Your API Key"
<Input
type="password"
placeholder="Your API Key"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -166,7 +167,7 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -184,14 +185,24 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -199,12 +210,24 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -229,7 +252,11 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -238,14 +265,17 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<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 Luma connector uses the Luma API to fetch all events that your API key has access to.
The Luma connector uses the Luma API to fetch all events that your API key has
access to.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves events that have been updated since the last indexing attempt.
For follow up indexing runs, the connector retrieves events that have been updated
since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your search results within minutes.
Indexing is configured to run periodically, so updates should appear in your
search results within minutes.
</li>
</ul>
</div>
@ -257,13 +287,16 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">API Key Required</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You need a Luma API key to use this connector. The key will be used to read your Luma events with read-only permissions.
You need a Luma API key to use this connector. The key will be used to read your
Luma events with read-only permissions.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Get Your API Key</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Get Your API Key
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Log into your Luma account</li>
<li>Navigate to your account settings</li>
@ -286,15 +319,20 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Grant necessary access</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Grant necessary access
</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
The API key will have access to all events that your user account can see. Make sure your account has appropriate permissions for the events you want to index.
The API key will have access to all events that your user account can see.
Make sure your account has appropriate permissions for the events you want to
index.
</p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
Only event details, descriptions, and attendee information will be indexed. Event attachments and linked files are not indexed by this connector.
Only event details, descriptions, and attendee information will be indexed.
Event attachments and linked files are not indexed by this connector.
</AlertDescription>
</Alert>
</div>
@ -307,7 +345,8 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li>
Navigate to the Connector Dashboard and select the <strong>Luma</strong> Connector.
Navigate to the Connector Dashboard and select the <strong>Luma</strong>{" "}
Connector.
</li>
<li>
Place your <strong>API Key</strong> in the form field.
@ -339,4 +378,3 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -42,19 +42,14 @@ 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.",
}),
integration_token: z.string().min(10, {
message: "Notion Integration Token is required and must be valid.",
}),
});
type NotionConnectorFormValues = z.infer<typeof notionConnectorFormSchema>;
export const NotionConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const NotionConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -119,7 +114,11 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="notion-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="notion-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -127,11 +126,11 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Notion Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Notion Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -149,16 +148,17 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Notion Integration Token</FormLabel>
<FormControl>
<Input
type="password"
placeholder="ntn_..."
<Input
type="password"
placeholder="ntn_..."
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Your Notion Integration Token will be encrypted and stored securely. It typically starts with "ntn_".
Your Notion Integration Token will be encrypted and stored securely. It
typically starts with "ntn_".
</FormDescription>
<FormMessage />
</FormItem>
@ -168,7 +168,7 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -186,14 +186,24 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -201,12 +211,24 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -231,7 +253,11 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -240,13 +266,13 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
<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 Notion connector uses the Notion API to fetch pages from all accessible workspaces
that the integration token has access to.
The Notion connector uses the Notion API to fetch pages from all accessible
workspaces that the integration token has access to.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves pages that
have been updated since the last indexing attempt.
For follow up indexing runs, the connector retrieves pages that have been updated
since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your
@ -260,7 +286,9 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 mb-4">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Integration Token Required</AlertTitle>
<AlertTitle className="text-[10px] sm:text-xs">
Integration Token Required
</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
You need to create a Notion integration and share pages with it to get access.
The integration needs read access to pages.
@ -269,9 +297,12 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Create a Notion Integration</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Create a Notion Integration
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Go to{" "}
<li>
Go to{" "}
<a
href="https://www.notion.so/my-integrations"
target="_blank"
@ -281,21 +312,35 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
https://www.notion.so/my-integrations
</a>
</li>
<li>Click <strong>+ New integration</strong></li>
<li>
Click <strong>+ New integration</strong>
</li>
<li>Enter a name for your integration (e.g., "Search Connector")</li>
<li>Select your workspace</li>
<li>Under <strong>Capabilities</strong>, enable <strong>Read content</strong></li>
<li>Click <strong>Submit</strong> to create the integration</li>
<li>Copy the <strong>Internal Integration Token</strong> (starts with "ntn_")</li>
<li>
Under <strong>Capabilities</strong>, enable <strong>Read content</strong>
</li>
<li>
Click <strong>Submit</strong> to create the integration
</li>
<li>
Copy the <strong>Internal Integration Token</strong> (starts with "ntn_")
</li>
</ol>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Share Pages with Integration</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Share Pages with Integration
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Open the Notion pages or databases you want to index</li>
<li>Click the <strong></strong> (three dots) menu in the top right</li>
<li>Select <strong>Add connections</strong> or <strong>Connections</strong></li>
<li>
Click the <strong></strong> (three dots) menu in the top right
</li>
<li>
Select <strong>Add connections</strong> or <strong>Connections</strong>
</li>
<li>Search for and select your integration</li>
<li>Repeat for all pages you want to index</li>
</ol>
@ -303,8 +348,8 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Important</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
The integration can only access pages that have been explicitly shared with it.
Make sure to share all pages you want to index.
The integration can only access pages that have been explicitly shared with
it. Make sure to share all pages you want to index.
</AlertDescription>
</Alert>
</div>
@ -350,4 +395,3 @@ export const NotionConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -52,10 +52,7 @@ const parseCommaSeparated = (value?: string | null) => {
return items.length > 0 ? items : undefined;
};
export const SearxngConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const SearxngConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const form = useForm<SearxngFormValues>({
resolver: zodResolver(searxngFormSchema),
@ -146,7 +143,11 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="searxng-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="searxng-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -154,11 +155,11 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My SearxNG Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My SearxNG Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -176,15 +177,16 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">SearxNG Host</FormLabel>
<FormControl>
<Input
placeholder="https://searxng.example.org"
<Input
placeholder="https://searxng.example.org"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Provide the full base URL to your SearxNG instance. Include the protocol (http/https).
Provide the full base URL to your SearxNG instance. Include the protocol
(http/https).
</FormDescription>
<FormMessage />
</FormItem>
@ -222,11 +224,11 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Engines (optional)</FormLabel>
<FormControl>
<Input
placeholder="google,bing,duckduckgo"
<Input
placeholder="google,bing,duckduckgo"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -244,11 +246,11 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Categories (optional)</FormLabel>
<FormControl>
<Input
placeholder="general,it,science"
<Input
placeholder="general,it,science"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -266,13 +268,15 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
name="language"
render={({ field }) => (
<FormItem>
<FormLabel className="text-xs sm:text-sm">Preferred Language (optional)</FormLabel>
<FormLabel className="text-xs sm:text-sm">
Preferred Language (optional)
</FormLabel>
<FormControl>
<Input
placeholder="en-US"
<Input
placeholder="en-US"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -288,17 +292,20 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
name="safesearch"
render={({ field }) => (
<FormItem>
<FormLabel className="text-xs sm:text-sm">SafeSearch Level (optional)</FormLabel>
<FormLabel className="text-xs sm:text-sm">
SafeSearch Level (optional)
</FormLabel>
<FormControl>
<Input
placeholder="0 (off), 1 (moderate), 2 (strict)"
<Input
placeholder="0 (off), 1 (moderate), 2 (strict)"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Set 0, 1, or 2 to adjust SafeSearch filtering. Leave blank to use the instance default.
Set 0, 1, or 2 to adjust SafeSearch filtering. Leave blank to use the instance
default.
</FormDescription>
<FormMessage />
</FormItem>
@ -318,7 +325,11 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
</FormDescription>
</div>
<FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} disabled={isSubmitting} />
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={isSubmitting}
/>
</FormControl>
</FormItem>
)}
@ -341,4 +352,3 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -42,19 +42,14 @@ 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.",
}),
bot_token: z.string().min(10, {
message: "Slack Bot Token is required and must be valid.",
}),
});
type SlackConnectorFormValues = z.infer<typeof slackConnectorFormSchema>;
export const SlackConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const SlackConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -104,7 +99,8 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
<div className="-ml-1">
<AlertTitle className="text-xs sm:text-sm">Bot User OAuth Token Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
You'll need a Slack Bot User OAuth Token to use this connector. You can create a Slack app and get the token from{" "}
You'll need a Slack Bot User OAuth Token to use this connector. You can create a Slack
app and get the token from{" "}
<a
href="https://api.slack.com/apps"
target="_blank"
@ -119,7 +115,11 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="slack-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="slack-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -127,11 +127,11 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Slack Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Slack Connector"
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -149,16 +149,17 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Slack Bot User OAuth Token</FormLabel>
<FormControl>
<Input
type="password"
placeholder="xoxb-..."
<Input
type="password"
placeholder="xoxb-..."
className="h-8 sm:h-10 px-2 sm:px-3 text-xs sm:text-sm border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
Your Bot User OAuth Token will be encrypted and stored securely. It typically starts with "xoxb-".
Your Bot User OAuth Token will be encrypted and stored securely. It typically
starts with "xoxb-".
</FormDescription>
<FormMessage />
</FormItem>
@ -168,7 +169,7 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20">
<h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */}
<DateRangeSelector
startDate={startDate}
@ -186,14 +187,24 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals
</p>
</div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} />
<Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div>
{periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2">
<Label htmlFor="frequency" className="text-xs sm:text-sm">Sync Frequency</Label>
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}>
<Label htmlFor="frequency" className="text-xs sm:text-sm">
Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger
id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm"
@ -201,12 +212,24 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
<SelectValue placeholder="Select frequency" />
</SelectTrigger>
<SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem>
<SelectItem value="15" className="text-xs sm:text-sm">
Every 15 minutes
</SelectItem>
<SelectItem value="60" className="text-xs sm:text-sm">
Every hour
</SelectItem>
<SelectItem value="360" className="text-xs sm:text-sm">
Every 6 hours
</SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">
Every 12 hours
</SelectItem>
<SelectItem value="1440" className="text-xs sm:text-sm">
Daily
</SelectItem>
<SelectItem value="10080" className="text-xs sm:text-sm">
Weekly
</SelectItem>
</SelectContent>
</Select>
</div>
@ -231,7 +254,11 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
)}
{/* Documentation Section */}
<Accordion type="single" collapsible className="w-full border border-border rounded-xl bg-slate-400/5 dark:bg-white/5">
<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
@ -240,13 +267,13 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
<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 Slack connector uses the Slack Web API to fetch messages from all accessible channels
that the bot token has access to within a workspace.
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.
</p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li>
For follow up indexing runs, the connector retrieves messages that
have been updated since the last indexing attempt.
For follow up indexing runs, the connector retrieves messages that have been
updated since the last indexing attempt.
</li>
<li>
Indexing is configured to run periodically, so updates should appear in your
@ -260,18 +287,23 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 mb-4">
<Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Bot User OAuth Token Required</AlertTitle>
<AlertTitle className="text-[10px] sm:text-xs">
Bot User OAuth Token Required
</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]">
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.
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.
</AlertDescription>
</Alert>
<div className="space-y-4 sm:space-y-6">
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 1: Create a Slack App</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 1: Create a Slack App
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Go to{" "}
<li>
Go to{" "}
<a
href="https://api.slack.com/apps"
target="_blank"
@ -281,36 +313,74 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
https://api.slack.com/apps
</a>
</li>
<li>Click <strong>Create New App</strong> and choose "From scratch"</li>
<li>
Click <strong>Create New App</strong> and choose "From scratch"
</li>
<li>Enter an app name and select your workspace</li>
<li>Click <strong>Create App</strong></li>
<li>
Click <strong>Create App</strong>
</li>
</ol>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 2: Configure Bot Scopes</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 2: Configure Bot Scopes
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Navigate to <strong>OAuth & Permissions</strong> in the sidebar</li>
<li>Under <strong>Bot Token Scopes</strong>, add the following scopes:
<li>
Navigate to <strong>OAuth & Permissions</strong> in the sidebar
</li>
<li>
Under <strong>Bot Token Scopes</strong>, add the following scopes:
<ul className="list-disc pl-5 mt-1 space-y-1">
<li><code className="bg-muted px-1 py-0.5 rounded">channels:read</code> - View basic information about public channels</li>
<li><code className="bg-muted px-1 py-0.5 rounded">channels:history</code> - View messages in public channels</li>
<li><code className="bg-muted px-1 py-0.5 rounded">groups:read</code> - View basic information about private channels</li>
<li><code className="bg-muted px-1 py-0.5 rounded">groups:history</code> - View messages in private channels</li>
<li><code className="bg-muted px-1 py-0.5 rounded">im:read</code> - View basic information about direct messages</li>
<li><code className="bg-muted px-1 py-0.5 rounded">im:history</code> - View messages in direct messages</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">channels:read</code> -
View basic information about public channels
</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">channels:history</code> -
View messages in public channels
</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">groups:read</code> - View
basic information about private channels
</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">groups:history</code> -
View messages in private channels
</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">im:read</code> - View
basic information about direct messages
</li>
<li>
<code className="bg-muted px-1 py-0.5 rounded">im:history</code> - View
messages in direct messages
</li>
</ul>
</li>
</ol>
</div>
<div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Step 3: Install App to Workspace</h4>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">
Step 3: Install App to Workspace
</h4>
<ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li>Go to <strong>Install App</strong> in the sidebar</li>
<li>Click <strong>Install to Workspace</strong></li>
<li>Review the permissions and click <strong>Allow</strong></li>
<li>Copy the <strong>Bot User OAuth Token</strong> from the "OAuth & Permissions" page (starts with "xoxb-")</li>
<li>
Go to <strong>Install App</strong> in the sidebar
</li>
<li>
Click <strong>Install to Workspace</strong>
</li>
<li>
Review the permissions and click <strong>Allow</strong>
</li>
<li>
Copy the <strong>Bot User OAuth Token</strong> from the "OAuth &
Permissions" page (starts with "xoxb-")
</li>
</ol>
</div>
</div>
@ -355,4 +425,3 @@ export const SlackConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -32,10 +32,7 @@ const tavilyApiFormSchema = z.object({
type TavilyApiFormValues = z.infer<typeof tavilyApiFormSchema>;
export const TavilyApiConnectForm: FC<ConnectFormProps> = ({
onSubmit,
isSubmitting,
}) => {
export const TavilyApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
const isSubmittingRef = useRef(false);
const form = useForm<TavilyApiFormValues>({
resolver: zodResolver(tavilyApiFormSchema),
@ -92,7 +89,11 @@ export const TavilyApiConnectForm: FC<ConnectFormProps> = ({
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
<Form {...form}>
<form id="tavily-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
<form
id="tavily-connect-form"
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4 sm:space-y-6"
>
<FormField
control={form.control}
name="name"
@ -100,11 +101,11 @@ export const TavilyApiConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl>
<Input
placeholder="My Tavily API Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
<Input
placeholder="My Tavily API Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -122,12 +123,12 @@ export const TavilyApiConnectForm: FC<ConnectFormProps> = ({
<FormItem>
<FormLabel className="text-xs sm:text-sm">Tavily API Key</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Enter your Tavily API key"
<Input
type="password"
placeholder="Enter your Tavily API key"
className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting}
{...field}
{...field}
/>
</FormControl>
<FormDescription className="text-[10px] sm:text-xs">
@ -155,4 +156,3 @@ export const TavilyApiConnectForm: FC<ConnectFormProps> = ({
</div>
);
};

View file

@ -112,4 +112,3 @@ export function getConnectorBenefits(connectorType: string): string[] | null {
return benefits[connectorType] || null;
}

View file

@ -41,9 +41,7 @@ export type ConnectFormComponent = FC<ConnectFormProps>;
/**
* Factory function to get the appropriate connect form component for a connector type
*/
export function getConnectFormComponent(
connectorType: string
): ConnectFormComponent | null {
export function getConnectFormComponent(connectorType: string): ConnectFormComponent | null {
switch (connectorType) {
case "TAVILY_API":
return TavilyApiConnectForm;
@ -82,4 +80,3 @@ export function getConnectFormComponent(
return null;
}
}

View file

@ -16,9 +16,7 @@ export const BaiduSearchApiConfig: FC<BaiduSearchApiConfigProps> = ({
onConfigChange,
onNameChange,
}) => {
const [apiKey, setApiKey] = useState<string>(
(connector.config?.BAIDU_API_KEY as string) || ""
);
const [apiKey, setApiKey] = useState<string>((connector.config?.BAIDU_API_KEY as string) || "");
const [name, setName] = useState<string>(connector.name || "");
// Update API key and name when connector changes
@ -89,4 +87,3 @@ export const BaiduSearchApiConfig: FC<BaiduSearchApiConfigProps> = ({
</div>
);
};

View file

@ -148,4 +148,3 @@ export const BookStackConfig: FC<BookStackConfigProps> = ({
</div>
);
};

View file

@ -27,10 +27,7 @@ const circlebackWebhookInfoSchema = z.object({
export type CirclebackWebhookInfo = z.infer<typeof circlebackWebhookInfoSchema>;
export const CirclebackConfig: FC<CirclebackConfigProps> = ({
connector,
onNameChange,
}) => {
export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameChange }) => {
const [name, setName] = useState<string>(connector.name || "");
const [webhookUrl, setWebhookUrl] = useState<string>("");
const [webhookInfo, setWebhookInfo] = useState<CirclebackWebhookInfo | null>(null);
@ -46,14 +43,14 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({
useEffect(() => {
const fetchWebhookInfo = async () => {
if (!connector.search_space_id) return;
const baseUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL;
if (!baseUrl) {
console.error("NEXT_PUBLIC_FASTAPI_BACKEND_URL is not configured");
setIsLoading(false);
return;
}
setIsLoading(true);
try {
const response = await authenticatedFetch(
@ -170,8 +167,9 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({
<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 !pl-0 mt-1">
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.
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.
</AlertDescription>
</Alert>
)}
@ -179,4 +177,3 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({
</div>
);
};

View file

@ -89,4 +89,3 @@ export const ClickUpConfig: FC<ClickUpConfigProps> = ({
</div>
);
};

View file

@ -19,9 +19,7 @@ export const ConfluenceConfig: FC<ConfluenceConfigProps> = ({
const [baseUrl, setBaseUrl] = useState<string>(
(connector.config?.CONFLUENCE_BASE_URL as string) || ""
);
const [email, setEmail] = useState<string>(
(connector.config?.CONFLUENCE_EMAIL as string) || ""
);
const [email, setEmail] = useState<string>((connector.config?.CONFLUENCE_EMAIL as string) || "");
const [apiToken, setApiToken] = useState<string>(
(connector.config?.CONFLUENCE_API_TOKEN as string) || ""
);
@ -149,4 +147,3 @@ export const ConfluenceConfig: FC<ConfluenceConfigProps> = ({
</div>
);
};

View file

@ -89,4 +89,3 @@ export const DiscordConfig: FC<DiscordConfigProps> = ({
</div>
);
};

View file

@ -271,7 +271,9 @@ export const ElasticsearchConfig: FC<ElasticsearchConfigProps> = ({
<div className="h-2.5 w-2.5 rounded-full bg-current" />
</RadioGroup.Indicator>
</RadioGroup.Item>
<Label htmlFor={authApiKeyId} className="text-xs sm:text-sm">API Key</Label>
<Label htmlFor={authApiKeyId} className="text-xs sm:text-sm">
API Key
</Label>
</div>
<div className="flex items-center space-x-2">
@ -284,7 +286,9 @@ export const ElasticsearchConfig: FC<ElasticsearchConfigProps> = ({
<div className="h-2.5 w-2.5 rounded-full bg-current" />
</RadioGroup.Indicator>
</RadioGroup.Item>
<Label htmlFor={authBasicId} className="text-xs sm:text-sm">Username & Password</Label>
<Label htmlFor={authBasicId} className="text-xs sm:text-sm">
Username & Password
</Label>
</div>
</RadioGroup.Root>
@ -435,4 +439,3 @@ export const ElasticsearchConfig: FC<ElasticsearchConfigProps> = ({
</div>
);
};

View file

@ -20,7 +20,10 @@ export const GithubConfig: FC<GithubConfigProps> = ({
const stringToArray = (arr: string[] | string | undefined): string[] => {
if (Array.isArray(arr)) return arr;
if (typeof arr === "string") {
return arr.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
return arr
.split(",")
.map((item) => item.trim())
.filter((item) => item.length > 0);
}
return [];
};
@ -147,4 +150,3 @@ export const GithubConfig: FC<GithubConfigProps> = ({
</div>
);
};

View file

@ -13,12 +13,10 @@ interface SelectedFolder {
name: string;
}
export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({
connector,
onConfigChange,
}) => {
export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfigChange }) => {
// Initialize with existing selected folders and files from connector config
const existingFolders = (connector.config?.selected_folders as SelectedFolder[] | undefined) || [];
const existingFolders =
(connector.config?.selected_folders as SelectedFolder[] | undefined) || [];
const existingFiles = (connector.config?.selected_files as SelectedFolder[] | undefined) || [];
const [selectedFolders, setSelectedFolders] = useState<SelectedFolder[]>(existingFolders);
const [selectedFiles, setSelectedFiles] = useState<SelectedFolder[]>(existingFiles);
@ -63,7 +61,8 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({
<div className="space-y-1 sm:space-y-2">
<h3 className="font-medium text-sm sm:text-base">Folder & File Selection</h3>
<p className="text-xs sm:text-sm text-muted-foreground">
Select specific folders and/or individual files to index. Only files directly in each folder will be processedsubfolders must be selected separately.
Select specific folders and/or individual files to index. Only files directly in each
folder will be processedsubfolders must be selected separately.
</p>
</div>
@ -71,17 +70,27 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({
<div className="p-2 sm:p-3 bg-muted rounded-lg text-xs sm:text-sm space-y-1 sm:space-y-2">
<p className="font-medium">
Selected {totalSelected} item{totalSelected > 1 ? "s" : ""}:
{selectedFolders.length > 0 && ` ${selectedFolders.length} folder${selectedFolders.length > 1 ? "s" : ""}`}
{selectedFiles.length > 0 && ` ${selectedFiles.length} file${selectedFiles.length > 1 ? "s" : ""}`}
{selectedFolders.length > 0 &&
` ${selectedFolders.length} folder${selectedFolders.length > 1 ? "s" : ""}`}
{selectedFiles.length > 0 &&
` ${selectedFiles.length} file${selectedFiles.length > 1 ? "s" : ""}`}
</p>
<div className="max-h-20 sm:max-h-24 overflow-y-auto space-y-1">
{selectedFolders.map((folder) => (
<p key={folder.id} className="text-xs sm:text-sm text-muted-foreground truncate" title={folder.name}>
<p
key={folder.id}
className="text-xs sm:text-sm text-muted-foreground truncate"
title={folder.name}
>
📁 {folder.name}
</p>
))}
{selectedFiles.map((file) => (
<p key={file.id} className="text-xs sm:text-sm text-muted-foreground truncate" title={file.name}>
<p
key={file.id}
className="text-xs sm:text-sm text-muted-foreground truncate"
title={file.name}
>
📄 {file.name}
</p>
))}
@ -122,10 +131,10 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3 flex items-center gap-2 [&>svg]:relative [&>svg]:left-0 [&>svg]:top-0 [&>svg+div]:translate-y-0">
<Info className="h-3 w-3 sm:h-4 sm:w-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
Folder and file selection is used when indexing. You can change this selection when you start indexing.
Folder and file selection is used when indexing. You can change this selection when you
start indexing.
</AlertDescription>
</Alert>
</div>
);
};

View file

@ -11,17 +11,9 @@ export interface JiraConfigProps extends ConnectorConfigProps {
onNameChange?: (name: string) => void;
}
export const JiraConfig: FC<JiraConfigProps> = ({
connector,
onConfigChange,
onNameChange,
}) => {
const [baseUrl, setBaseUrl] = useState<string>(
(connector.config?.JIRA_BASE_URL as string) || ""
);
const [email, setEmail] = useState<string>(
(connector.config?.JIRA_EMAIL as string) || ""
);
export const JiraConfig: FC<JiraConfigProps> = ({ connector, onConfigChange, onNameChange }) => {
const [baseUrl, setBaseUrl] = useState<string>((connector.config?.JIRA_BASE_URL as string) || "");
const [email, setEmail] = useState<string>((connector.config?.JIRA_EMAIL as string) || "");
const [apiToken, setApiToken] = useState<string>(
(connector.config?.JIRA_API_TOKEN as string) || ""
);
@ -149,4 +141,3 @@ export const JiraConfig: FC<JiraConfigProps> = ({
</div>
);
};

View file

@ -16,9 +16,7 @@ export const LinearConfig: FC<LinearConfigProps> = ({
onConfigChange,
onNameChange,
}) => {
const [apiKey, setApiKey] = useState<string>(
(connector.config?.LINEAR_API_KEY as string) || ""
);
const [apiKey, setApiKey] = useState<string>((connector.config?.LINEAR_API_KEY as string) || "");
const [name, setName] = useState<string>(connector.name || "");
// Update API key and name when connector changes
@ -89,4 +87,3 @@ export const LinearConfig: FC<LinearConfigProps> = ({
</div>
);
};

View file

@ -16,9 +16,7 @@ export const LinkupApiConfig: FC<LinkupApiConfigProps> = ({
onConfigChange,
onNameChange,
}) => {
const [apiKey, setApiKey] = useState<string>(
(connector.config?.LINKUP_API_KEY as string) || ""
);
const [apiKey, setApiKey] = useState<string>((connector.config?.LINKUP_API_KEY as string) || "");
const [name, setName] = useState<string>(connector.name || "");
// Update API key and name when connector changes
@ -89,4 +87,3 @@ export const LinkupApiConfig: FC<LinkupApiConfigProps> = ({
</div>
);
};

View file

@ -11,14 +11,8 @@ export interface LumaConfigProps extends ConnectorConfigProps {
onNameChange?: (name: string) => void;
}
export const LumaConfig: FC<LumaConfigProps> = ({
connector,
onConfigChange,
onNameChange,
}) => {
const [apiKey, setApiKey] = useState<string>(
(connector.config?.LUMA_API_KEY as string) || ""
);
export const LumaConfig: FC<LumaConfigProps> = ({ connector, onConfigChange, onNameChange }) => {
const [apiKey, setApiKey] = useState<string>((connector.config?.LUMA_API_KEY as string) || "");
const [name, setName] = useState<string>(connector.name || "");
// Update API key and name when connector changes
@ -89,4 +83,3 @@ export const LumaConfig: FC<LumaConfigProps> = ({
</div>
);
};

View file

@ -89,4 +89,3 @@ export const NotionConfig: FC<NotionConfigProps> = ({
</div>
);
};

View file

@ -34,15 +34,9 @@ export const SearxngConfig: FC<SearxngConfigProps> = ({
onConfigChange,
onNameChange,
}) => {
const [host, setHost] = useState<string>(
(connector.config?.SEARXNG_HOST as string) || ""
);
const [apiKey, setApiKey] = useState<string>(
(connector.config?.SEARXNG_API_KEY as string) || ""
);
const [engines, setEngines] = useState<string>(
arrayToString(connector.config?.SEARXNG_ENGINES)
);
const [host, setHost] = useState<string>((connector.config?.SEARXNG_HOST as string) || "");
const [apiKey, setApiKey] = useState<string>((connector.config?.SEARXNG_API_KEY as string) || "");
const [engines, setEngines] = useState<string>(arrayToString(connector.config?.SEARXNG_ENGINES));
const [categories, setCategories] = useState<string>(
arrayToString(connector.config?.SEARXNG_CATEGORIES)
);
@ -300,7 +294,8 @@ export const SearxngConfig: FC<SearxngConfigProps> = ({
className="border-slate-400/20 focus-visible:border-slate-400/40"
/>
<p className="text-[10px] sm:text-xs text-muted-foreground">
Set 0, 1, or 2 to adjust SafeSearch filtering. Leave blank to use the instance default.
Set 0, 1, or 2 to adjust SafeSearch filtering. Leave blank to use the instance
default.
</p>
</div>
</div>
@ -319,4 +314,3 @@ export const SearxngConfig: FC<SearxngConfigProps> = ({
</div>
);
};

View file

@ -11,11 +11,7 @@ export interface SlackConfigProps extends ConnectorConfigProps {
onNameChange?: (name: string) => void;
}
export const SlackConfig: FC<SlackConfigProps> = ({
connector,
onConfigChange,
onNameChange,
}) => {
export const SlackConfig: FC<SlackConfigProps> = ({ connector, onConfigChange, onNameChange }) => {
const [botToken, setBotToken] = useState<string>(
(connector.config?.SLACK_BOT_TOKEN as string) || ""
);
@ -89,4 +85,3 @@ export const SlackConfig: FC<SlackConfigProps> = ({
</div>
);
};

View file

@ -16,9 +16,7 @@ export const TavilyApiConfig: FC<TavilyApiConfigProps> = ({
onConfigChange,
onNameChange,
}) => {
const [apiKey, setApiKey] = useState<string>(
(connector.config?.TAVILY_API_KEY as string) || ""
);
const [apiKey, setApiKey] = useState<string>((connector.config?.TAVILY_API_KEY as string) || "");
const [name, setName] = useState<string>(connector.name || "");
// Update API key and name when connector changes
@ -89,4 +87,3 @@ export const TavilyApiConfig: FC<TavilyApiConfigProps> = ({
</div>
);
};

View file

@ -10,14 +10,11 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import type { ConnectorConfigProps } from "../index";
export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({
connector,
onConfigChange,
}) => {
export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({ connector, onConfigChange }) => {
// Initialize with existing config values
const existingApiKey = (connector.config?.FIRECRAWL_API_KEY as string | undefined) || "";
const existingUrls = (connector.config?.INITIAL_URLS as string | undefined) || "";
const [apiKey, setApiKey] = useState(existingApiKey);
const [initialUrls, setInitialUrls] = useState(existingUrls);
const [showApiKey, setShowApiKey] = useState(false);
@ -57,7 +54,8 @@ export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({
<div className="space-y-1 sm:space-y-2">
<h3 className="font-medium text-sm sm:text-base">Web Crawler Configuration</h3>
<p className="text-xs sm:text-sm text-muted-foreground">
Configure your web crawler settings. You can add a Firecrawl API key for enhanced crawling or use the free fallback option.
Configure your web crawler settings. You can add a Firecrawl API key for enhanced crawling
or use the free fallback option.
</p>
</div>
@ -120,10 +118,10 @@ export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3 flex items-center gap-2 [&>svg]:relative [&>svg]:left-0 [&>svg]:top-0 [&>svg+div]:translate-y-0">
<Info className="h-3 w-3 sm:h-4 sm:w-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
Configuration is saved when you start indexing. You can update these settings anytime from the connector management page.
Configuration is saved when you start indexing. You can update these settings anytime from
the connector management page.
</AlertDescription>
</Alert>
</div>
);
};

View file

@ -77,4 +77,3 @@ export function getConnectorConfigComponent(
return null;
}
}

View file

@ -126,17 +126,17 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
{/* 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">
<Button
variant="ghost"
onClick={onBack}
disabled={isSubmitting}
<Button
variant="ghost"
onClick={onBack}
disabled={isSubmitting}
className="text-xs sm:text-sm"
>
Cancel
</Button>
<Button
onClick={handleFormSubmit}
disabled={isSubmitting}
<Button
onClick={handleFormSubmit}
disabled={isSubmitting}
className="text-xs sm:text-sm min-w-[140px] disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none"
>
{isSubmitting ? (
@ -145,13 +145,10 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
Connecting...
</>
) : (
<>
Connect {getConnectorTypeDisplay(connectorType)}
</>
<>Connect {getConnectorTypeDisplay(connectorType)}</>
)}
</Button>
</div>
</div>
);
};

View file

@ -63,12 +63,13 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
const checkScrollState = useCallback(() => {
if (!scrollContainerRef.current) return;
const target = scrollContainerRef.current;
const scrolled = target.scrollTop > 0;
const hasMore = target.scrollHeight > target.clientHeight &&
const hasMore =
target.scrollHeight > target.clientHeight &&
target.scrollTop + target.clientHeight < target.scrollHeight - 10;
setIsScrolled(scrolled);
setHasMoreContent(hasMore);
}, []);
@ -83,11 +84,11 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
const resizeObserver = new ResizeObserver(() => {
checkScrollState();
});
if (scrollContainerRef.current) {
resizeObserver.observe(scrollContainerRef.current);
}
return () => {
resizeObserver.disconnect();
};
@ -109,10 +110,12 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
return (
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
{/* Fixed Header */}
<div className={cn(
"flex-shrink-0 px-6 sm:px-12 pt-8 sm:pt-10 transition-shadow duration-200 relative z-10",
isScrolled && "shadow-sm"
)}>
<div
className={cn(
"flex-shrink-0 px-6 sm:px-12 pt-8 sm:pt-10 transition-shadow duration-200 relative z-10",
isScrolled && "shadow-sm"
)}
>
{/* Back button */}
<button
type="button"
@ -130,44 +133,44 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
{getConnectorIcon(connector.connector_type, "size-7")}
</div>
<div className="flex-1 min-w-0">
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
{connector.name}
</h2>
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">{connector.name}</h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1">
Manage your connector settings and sync configuration
</p>
</div>
</div>
{/* Quick Index Button - only show for indexable connectors, but not for Google Drive (requires folder selection) */}
{connector.is_indexable && onQuickIndex && connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && (
<Button
variant="secondary"
size="sm"
onClick={onQuickIndex}
disabled={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"
>
{isIndexing ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
Indexing...
</>
) : (
<>
<RefreshCw className="mr-2 h-4 w-4" />
Quick Index
</>
)}
</Button>
)}
{connector.is_indexable &&
onQuickIndex &&
connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && (
<Button
variant="secondary"
size="sm"
onClick={onQuickIndex}
disabled={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"
>
{isIndexing ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
Indexing...
</>
) : (
<>
<RefreshCw className="mr-2 h-4 w-4" />
Quick Index
</>
)}
</Button>
)}
</div>
</div>
{/* Scrollable Content */}
<div className="flex-1 min-h-0 relative overflow-hidden">
<div
<div
ref={scrollContainerRef}
className="h-full overflow-y-auto px-6 sm:px-12"
className="h-full overflow-y-auto px-6 sm:px-12"
onScroll={handleScroll}
>
<div className="space-y-6 pb-6 pt-2">
@ -184,14 +187,15 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
{connector.is_indexable && (
<>
{/* Date range selector - not shown for Google Drive (uses folder selection) or Webcrawler (uses config) */}
{connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && connector.connector_type !== "WEBCRAWLER_CONNECTOR" && (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={onStartDateChange}
onEndDateChange={onEndDateChange}
/>
)}
{connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" &&
connector.connector_type !== "WEBCRAWLER_CONNECTOR" && (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={onStartDateChange}
onEndDateChange={onEndDateChange}
/>
)}
{/* Periodic sync - not shown for Google Drive */}
{connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && (
@ -212,9 +216,12 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
<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="font-medium text-xs sm:text-sm">
Re-indexing runs in the background
</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
You can continue using SurfSense while we sync your data. Check the Active tab to see progress.
You can continue using SurfSense while we sync your data. Check the Active tab
to see progress.
</p>
</div>
</div>
@ -235,7 +242,9 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
<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-4 sm:py-6 bg-muted border-t border-border">
{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">Are you sure?</span>
<span className="text-xs sm:text-sm text-muted-foreground sm:whitespace-nowrap">
Are you sure?
</span>
<div className="flex items-center gap-2 sm:gap-3">
<Button
variant="destructive"
@ -276,9 +285,9 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
Disconnect
</Button>
)}
<Button
onClick={onSave}
disabled={isSaving || isDisconnecting}
<Button
onClick={onSave}
disabled={isSaving || isDisconnecting}
className="text-xs sm:text-sm flex-1 sm:flex-initial"
>
{isSaving ? (
@ -294,4 +303,3 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
</div>
);
};

View file

@ -45,7 +45,7 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
}) => {
// Get connector-specific config component
const ConnectorConfigComponent = useMemo(
() => connector ? getConnectorConfigComponent(connector.connector_type) : null,
() => (connector ? getConnectorConfigComponent(connector.connector_type) : null),
[connector]
);
const [isScrolled, setIsScrolled] = useState(false);
@ -54,12 +54,13 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
const checkScrollState = useCallback(() => {
if (!scrollContainerRef.current) return;
const target = scrollContainerRef.current;
const scrolled = target.scrollTop > 0;
const hasMore = target.scrollHeight > target.clientHeight &&
const hasMore =
target.scrollHeight > target.clientHeight &&
target.scrollTop + target.clientHeight < target.scrollHeight - 10;
setIsScrolled(scrolled);
setHasMoreContent(hasMore);
}, []);
@ -74,11 +75,11 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
const resizeObserver = new ResizeObserver(() => {
checkScrollState();
});
if (scrollContainerRef.current) {
resizeObserver.observe(scrollContainerRef.current);
}
return () => {
resizeObserver.disconnect();
};
@ -87,10 +88,12 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
return (
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
{/* Fixed Header */}
<div className={cn(
"flex-shrink-0 px-6 sm:px-12 pt-8 sm:pt-10 transition-shadow duration-200 relative z-10",
isScrolled && "shadow-sm"
)}>
<div
className={cn(
"flex-shrink-0 px-6 sm:px-12 pt-8 sm:pt-10 transition-shadow duration-200 relative z-10",
isScrolled && "shadow-sm"
)}
>
{/* Back button */}
<button
type="button"
@ -119,32 +122,30 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
{/* Scrollable Content */}
<div className="flex-1 min-h-0 relative overflow-hidden">
<div
<div
ref={scrollContainerRef}
className="h-full overflow-y-auto px-6 sm:px-12"
className="h-full overflow-y-auto px-6 sm:px-12"
onScroll={handleScroll}
>
<div className="space-y-6 pb-6 pt-2">
{/* Connector-specific configuration */}
{ConnectorConfigComponent && connector && (
<ConnectorConfigComponent
connector={connector}
onConfigChange={onConfigChange}
/>
<ConnectorConfigComponent connector={connector} onConfigChange={onConfigChange} />
)}
{/* Date range selector and periodic sync - only shown for indexable connectors */}
{connector?.is_indexable && (
<>
{/* Date range selector - not shown for Google Drive (uses folder selection) or Webcrawler (uses config) */}
{config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && config.connectorType !== "WEBCRAWLER_CONNECTOR" && (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={onStartDateChange}
onEndDateChange={onEndDateChange}
/>
)}
{config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" &&
config.connectorType !== "WEBCRAWLER_CONNECTOR" && (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={onStartDateChange}
onEndDateChange={onEndDateChange}
/>
)}
{/* Periodic sync - not shown for Google Drive */}
{config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && (
@ -167,7 +168,8 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
<div className="text-xs sm:text-sm">
<p className="font-medium text-xs sm:text-sm">Indexing runs in the background</p>
<p className="text-muted-foreground mt-1 text-[10px] sm:text-sm">
You can continue using SurfSense while we sync your data. Check the Active tab to see progress.
You can continue using SurfSense while we sync your data. Check the Active tab
to see progress.
</p>
</div>
</div>
@ -186,10 +188,19 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
{/* Fixed Footer - Action buttons */}
<div className="flex-shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-muted">
<Button variant="ghost" onClick={onSkip} disabled={isStartingIndexing} className="text-xs sm:text-sm">
<Button
variant="ghost"
onClick={onSkip}
disabled={isStartingIndexing}
className="text-xs sm:text-sm"
>
Skip for now
</Button>
<Button onClick={onStartIndexing} disabled={isStartingIndexing} className="text-xs sm:text-sm">
<Button
onClick={onStartIndexing}
disabled={isStartingIndexing}
className="text-xs sm:text-sm"
>
{isStartingIndexing ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
@ -203,4 +214,3 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
</div>
);
};

View file

@ -150,4 +150,3 @@ export const OTHER_CONNECTORS = [
// Re-export IndexingConfigState from schemas for backward compatibility
export type { IndexingConfigState } from "./connector-popup.schemas";

View file

@ -80,7 +80,7 @@ export function parseConnectorPopupQueryParams(
params: URLSearchParams | Record<string, string | null>
): ConnectorPopupQueryParams {
const obj: Record<string, string | undefined> = {};
if (params instanceof URLSearchParams) {
params.forEach((value, key) => {
obj[key] = value || undefined;
@ -90,7 +90,7 @@ export function parseConnectorPopupQueryParams(
obj[key] = value || undefined;
});
}
return connectorPopupQueryParamsSchema.parse(obj);
}
@ -107,4 +107,3 @@ export function parseOAuthAuthResponse(data: unknown): OAuthAuthResponse {
export function validateIndexingConfigState(data: unknown): IndexingConfigState {
return indexingConfigStateSchema.parse(data);
}

View file

@ -35,4 +35,3 @@ export type {
// Hooks
export { useConnectorDialog } from "./hooks/use-connector-dialog";

View file

@ -11,9 +11,7 @@ import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import type { LogSummary, LogActiveTask } from "@/contracts/types/log.types";
import { cn } from "@/lib/utils";
import { getDocumentCountForConnector } from "../utils/connector-document-mapping";
import {
TabsContent,
} from "@/components/ui/tabs";
import { TabsContent } from "@/components/ui/tabs";
interface ActiveConnectorsTabProps {
hasSources: boolean;
@ -68,12 +66,10 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
// These are: EXTENSION (browser extension), FILE (uploaded files), NOTE (editor notes),
// YOUTUBE_VIDEO (YouTube videos), and CRAWLED_URL (web pages - shown separately even though it can come from WEBCRAWLER_CONNECTOR)
const standaloneDocumentTypes = ["EXTENSION", "FILE", "NOTE", "YOUTUBE_VIDEO", "CRAWLED_URL"];
// Filter to only show standalone document types that have documents (count > 0)
const standaloneDocuments = activeDocumentTypes
.filter(([docType, count]) =>
standaloneDocumentTypes.includes(docType) && count > 0
)
.filter(([docType, count]) => standaloneDocumentTypes.includes(docType) && count > 0)
.map(([docType, count]) => ({
type: docType,
count,
@ -88,78 +84,76 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
{connectors.length > 0 && (
<div className="space-y-4">
<div className="flex items-center gap-2">
<h3 className="text-sm font-semibold text-muted-foreground">
Active Connectors
</h3>
<h3 className="text-sm font-semibold text-muted-foreground">Active Connectors</h3>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{connectors.map((connector) => {
const isIndexing = indexingConnectorIds.has(connector.id);
const activeTask = logsSummary?.active_tasks?.find(
(task: LogActiveTask) => task.connector_id === connector.id
);
const documentCount = getDocumentCountForConnector(
connector.connector_type,
documentTypeCounts
);
const isIndexing = indexingConnectorIds.has(connector.id);
const activeTask = logsSummary?.active_tasks?.find(
(task: LogActiveTask) => task.connector_id === connector.id
);
const documentCount = getDocumentCountForConnector(
connector.connector_type,
documentTypeCounts
);
return (
<div
key={`connector-${connector.id}`}
className={cn(
"flex items-center gap-4 p-4 rounded-xl border border-border transition-all",
isIndexing
? "bg-primary/5 border-primary/20"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
)}
>
<div
className={cn(
"flex h-12 w-12 items-center justify-center rounded-lg border",
isIndexing
? "bg-primary/10 border-primary/20"
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
)}
>
{getConnectorIcon(connector.connector_type, "size-6")}
</div>
<div className="flex-1 min-w-0">
<p className="text-[14px] font-semibold leading-tight truncate">
{connector.name}
</p>
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
Indexing...
{activeTask?.message && (
<span className="text-muted-foreground truncate max-w-[150px]">
{activeTask.message}
</span>
return (
<div
key={`connector-${connector.id}`}
className={cn(
"flex items-center gap-4 p-4 rounded-xl border border-border transition-all",
isIndexing
? "bg-primary/5 border-primary/20"
: "bg-slate-400/5 dark:bg-white/5 hover:bg-slate-400/10 dark:hover:bg-white/10"
)}
>
<div
className={cn(
"flex h-12 w-12 items-center justify-center rounded-lg border",
isIndexing
? "bg-primary/10 border-primary/20"
: "bg-slate-400/5 dark:bg-white/5 border-slate-400/5 dark:border-white/5"
)}
</p>
) : (
<p className="text-[11px] text-muted-foreground mt-1">
{connector.last_indexed_at
? `Last indexed: ${format(new Date(connector.last_indexed_at), "MMM d, yyyy")}`
: "Never indexed"}
</p>
)}
<p className="text-[11px] text-muted-foreground mt-0.5">
{formatDocumentCount(documentCount)}
</p>
</div>
<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"
onClick={onManage ? () => onManage(connector) : undefined}
disabled={isIndexing}
>
{isIndexing ? "Syncing..." : "Manage"}
</Button>
</div>
);
})}
>
{getConnectorIcon(connector.connector_type, "size-6")}
</div>
<div className="flex-1 min-w-0">
<p className="text-[14px] font-semibold leading-tight truncate">
{connector.name}
</p>
{isIndexing ? (
<p className="text-[11px] text-primary mt-1 flex items-center gap-1.5">
<Loader2 className="size-3 animate-spin" />
Indexing...
{activeTask?.message && (
<span className="text-muted-foreground truncate max-w-[150px]">
{activeTask.message}
</span>
)}
</p>
) : (
<p className="text-[11px] text-muted-foreground mt-1">
{connector.last_indexed_at
? `Last indexed: ${format(new Date(connector.last_indexed_at), "MMM d, yyyy")}`
: "Never indexed"}
</p>
)}
<p className="text-[11px] text-muted-foreground mt-0.5">
{formatDocumentCount(documentCount)}
</p>
</div>
<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"
onClick={onManage ? () => onManage(connector) : undefined}
disabled={isIndexing}
>
{isIndexing ? "Syncing..." : "Manage"}
</Button>
</div>
);
})}
</div>
</div>
)}
@ -168,9 +162,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
{standaloneDocuments.length > 0 && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-muted-foreground">
Documents
</h3>
<h3 className="text-sm font-semibold text-muted-foreground">Documents</h3>
<Button
variant="ghost"
size="sm"
@ -190,9 +182,7 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
<div className="flex items-center justify-center">
{getConnectorIcon(doc.type, "size-3.5")}
</div>
<span className="text-[12px] font-medium">
{doc.label}
</span>
<span className="text-[12px] font-medium">{doc.label}</span>
<span className="text-[11px] text-muted-foreground">
{formatDocumentCount(doc.count)}
</span>
@ -223,4 +213,3 @@ export const ActiveConnectorsTab: FC<ActiveConnectorsTabProps> = ({
</TabsContent>
);
};

View file

@ -71,41 +71,49 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
{filteredOAuth.length > 0 && (
<section>
<div className="flex items-center gap-2 mb-4">
<h3 className="text-sm font-semibold text-muted-foreground">
Quick Connect
</h3>
<h3 className="text-sm font-semibold text-muted-foreground">Quick Connect</h3>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{filteredOAuth.map((connector) => {
const isConnected = connectedTypes.has(connector.connectorType);
const isConnecting = connectingId === connector.id;
// Find the actual connector object if connected
const actualConnector = isConnected && allConnectors
? allConnectors.find((c: SearchSourceConnector) => c.connector_type === connector.connectorType)
: undefined;
const documentCount = getDocumentCountForConnector(connector.connectorType, documentTypeCounts);
const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id);
const activeTask = actualConnector ? getActiveTaskForConnector(actualConnector.id) : undefined;
{filteredOAuth.map((connector) => {
const isConnected = connectedTypes.has(connector.connectorType);
const isConnecting = connectingId === connector.id;
// Find the actual connector object if connected
const actualConnector =
isConnected && allConnectors
? allConnectors.find(
(c: SearchSourceConnector) => c.connector_type === connector.connectorType
)
: undefined;
return (
<ConnectorCard
key={connector.id}
id={connector.id}
title={connector.title}
description={connector.description}
connectorType={connector.connectorType}
isConnected={isConnected}
isConnecting={isConnecting}
documentCount={documentCount}
lastIndexedAt={actualConnector?.last_indexed_at}
isIndexing={isIndexing}
activeTask={activeTask}
onConnect={() => onConnectOAuth(connector)}
onManage={actualConnector && onManage ? () => onManage(actualConnector) : undefined}
/>
);
})}
const documentCount = getDocumentCountForConnector(
connector.connectorType,
documentTypeCounts
);
const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id);
const activeTask = actualConnector
? getActiveTaskForConnector(actualConnector.id)
: undefined;
return (
<ConnectorCard
key={connector.id}
id={connector.id}
title={connector.title}
description={connector.description}
connectorType={connector.connectorType}
isConnected={isConnected}
isConnecting={isConnecting}
documentCount={documentCount}
lastIndexedAt={actualConnector?.last_indexed_at}
isIndexing={isIndexing}
activeTask={activeTask}
onConnect={() => onConnectOAuth(connector)}
onManage={
actualConnector && onManage ? () => onManage(actualConnector) : undefined
}
/>
);
})}
</div>
</section>
)}
@ -114,43 +122,47 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
{filteredCrawlers.length > 0 && (
<section>
<div className="flex items-center gap-2 mb-4">
<h3 className="text-sm font-semibold text-muted-foreground">
Content Sources
</h3>
<h3 className="text-sm font-semibold text-muted-foreground">Content Sources</h3>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{filteredCrawlers.map((crawler) => {
const isYouTube = crawler.id === "youtube-crawler";
const isWebcrawler = crawler.id === "webcrawler-connector";
// For crawlers that are actual connectors, check connection status
const isConnected = crawler.connectorType
const isConnected = crawler.connectorType
? connectedTypes.has(crawler.connectorType)
: false;
const isConnecting = connectingId === crawler.id;
// Find the actual connector object if connected
const actualConnector = isConnected && crawler.connectorType && allConnectors
? allConnectors.find((c: SearchSourceConnector) => c.connector_type === crawler.connectorType)
: undefined;
const documentCount = crawler.connectorType
// Find the actual connector object if connected
const actualConnector =
isConnected && crawler.connectorType && allConnectors
? allConnectors.find(
(c: SearchSourceConnector) => c.connector_type === crawler.connectorType
)
: undefined;
const documentCount = crawler.connectorType
? getDocumentCountForConnector(crawler.connectorType, documentTypeCounts)
: undefined;
const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id);
const activeTask = actualConnector ? getActiveTaskForConnector(actualConnector.id) : undefined;
const activeTask = actualConnector
? getActiveTaskForConnector(actualConnector.id)
: undefined;
const handleConnect = isYouTube && onCreateYouTubeCrawler
? onCreateYouTubeCrawler
: isWebcrawler && onCreateWebcrawler
? onCreateWebcrawler
: crawler.connectorType && onConnectNonOAuth
? () => {
if (crawler.connectorType) {
onConnectNonOAuth(crawler.connectorType);
}
}
: () => {}; // Fallback for non-connector crawlers
const handleConnect =
isYouTube && onCreateYouTubeCrawler
? onCreateYouTubeCrawler
: isWebcrawler && onCreateWebcrawler
? onCreateWebcrawler
: crawler.connectorType && onConnectNonOAuth
? () => {
if (crawler.connectorType) {
onConnectNonOAuth(crawler.connectorType);
}
}
: () => {}; // Fallback for non-connector crawlers
return (
<ConnectorCard
@ -166,7 +178,9 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
isIndexing={isIndexing}
activeTask={activeTask}
onConnect={handleConnect}
onManage={actualConnector && onManage ? () => onManage(actualConnector) : undefined}
onManage={
actualConnector && onManage ? () => onManage(actualConnector) : undefined
}
/>
);
})}
@ -178,68 +192,92 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
{filteredOther.length > 0 && (
<section>
<div className="flex items-center gap-2 mb-4">
<h3 className="text-sm font-semibold text-muted-foreground">
More Integrations
</h3>
<h3 className="text-sm font-semibold text-muted-foreground">More Integrations</h3>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{filteredOther.map((connector) => {
// Special handling for connectors that can be created in popup
const isTavily = connector.id === "tavily-api";
const isSearxng = connector.id === "searxng";
const isLinkup = connector.id === "linkup-api";
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 isConfluence = connector.id === "confluence-connector";
const isBookStack = connector.id === "bookstack-connector";
const isGithub = connector.id === "github-connector";
const isJira = connector.id === "jira-connector";
const isClickUp = connector.id === "clickup-connector";
const isLuma = connector.id === "luma-connector";
const isCircleback = connector.id === "circleback-connector";
const isConnected = connectedTypes.has(connector.connectorType);
const isConnecting = connectingId === connector.id;
// Find the actual connector object if connected
const actualConnector = isConnected && allConnectors
? allConnectors.find((c: SearchSourceConnector) => c.connector_type === connector.connectorType)
: undefined;
// Special handling for connectors that can be created in popup
const isTavily = connector.id === "tavily-api";
const isSearxng = connector.id === "searxng";
const isLinkup = connector.id === "linkup-api";
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 isConfluence = connector.id === "confluence-connector";
const isBookStack = connector.id === "bookstack-connector";
const isGithub = connector.id === "github-connector";
const isJira = connector.id === "jira-connector";
const isClickUp = connector.id === "clickup-connector";
const isLuma = connector.id === "luma-connector";
const isCircleback = connector.id === "circleback-connector";
const documentCount = getDocumentCountForConnector(connector.connectorType, documentTypeCounts);
const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id);
const activeTask = actualConnector ? getActiveTaskForConnector(actualConnector.id) : undefined;
const isConnected = connectedTypes.has(connector.connectorType);
const isConnecting = connectingId === connector.id;
const handleConnect = (isTavily || isSearxng || isLinkup || isBaidu || isLinear || isElasticsearch || isSlack || isDiscord || isNotion || isConfluence || isBookStack || isGithub || isJira || isClickUp || isLuma || isCircleback) && onConnectNonOAuth
? () => onConnectNonOAuth(connector.connectorType)
: () => {}; // Fallback - connector popup should handle all connector types
// Find the actual connector object if connected
const actualConnector =
isConnected && allConnectors
? allConnectors.find(
(c: SearchSourceConnector) => c.connector_type === connector.connectorType
)
: undefined;
return (
<ConnectorCard
key={connector.id}
id={connector.id}
title={connector.title}
description={connector.description}
connectorType={connector.connectorType}
isConnected={isConnected}
isConnecting={isConnecting}
documentCount={documentCount}
lastIndexedAt={actualConnector?.last_indexed_at}
isIndexing={isIndexing}
activeTask={activeTask}
onConnect={handleConnect}
onManage={actualConnector && onManage ? () => onManage(actualConnector) : undefined}
/>
);
})}
const documentCount = getDocumentCountForConnector(
connector.connectorType,
documentTypeCounts
);
const isIndexing = actualConnector && indexingConnectorIds?.has(actualConnector.id);
const activeTask = actualConnector
? getActiveTaskForConnector(actualConnector.id)
: undefined;
const handleConnect =
(isTavily ||
isSearxng ||
isLinkup ||
isBaidu ||
isLinear ||
isElasticsearch ||
isSlack ||
isDiscord ||
isNotion ||
isConfluence ||
isBookStack ||
isGithub ||
isJira ||
isClickUp ||
isLuma ||
isCircleback) &&
onConnectNonOAuth
? () => onConnectNonOAuth(connector.connectorType)
: () => {}; // Fallback - connector popup should handle all connector types
return (
<ConnectorCard
key={connector.id}
id={connector.id}
title={connector.title}
description={connector.description}
connectorType={connector.connectorType}
isConnected={isConnected}
isConnecting={isConnecting}
documentCount={documentCount}
lastIndexedAt={actualConnector?.last_indexed_at}
isIndexing={isIndexing}
activeTask={activeTask}
onConnect={handleConnect}
onManage={
actualConnector && onManage ? () => onManage(actualConnector) : undefined
}
/>
);
})}
</div>
</section>
)}
</div>
);
};

View file

@ -2,7 +2,7 @@
/**
* Maps SearchSourceConnectorType to DocumentType for fetching document counts
*
*
* Note: Some connectors don't have a direct 1:1 mapping to document types:
* - Search API connectors (TAVILY_API, SEARXNG_API, etc.) don't index documents
* - WEBCRAWLER_CONNECTOR maps to CRAWLED_URL document type
@ -35,9 +35,7 @@ export const CONNECTOR_TO_DOCUMENT_TYPE: Record<string, string> = {
* Get the document type for a given connector type
* Returns undefined if the connector doesn't index documents (e.g., search APIs)
*/
export function getDocumentTypeForConnector(
connectorType: string
): string | undefined {
export function getDocumentTypeForConnector(connectorType: string): string | undefined {
return CONNECTOR_TO_DOCUMENT_TYPE[connectorType];
}
@ -62,4 +60,3 @@ export function getDocumentCountForConnector(
export function isIndexableConnectorType(connectorType: string): boolean {
return connectorType in CONNECTOR_TO_DOCUMENT_TYPE;
}

View file

@ -22,10 +22,7 @@ interface YouTubeCrawlerViewProps {
onBack: () => void;
}
export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({
searchSpaceId,
onBack,
}) => {
export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId, onBack }) => {
const t = useTranslations("add_youtube");
const router = useRouter();
const [videoTags, setVideoTags] = useState<TagType[]>([]);
@ -133,12 +130,8 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({
{getConnectorIcon(EnumConnectorName.YOUTUBE_CONNECTOR, "h-7 w-7")}
</div>
<div>
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
{t("title")}
</h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1">
{t("subtitle")}
</p>
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">{t("title")}</h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1">{t("subtitle")}</p>
</div>
</div>
</div>
@ -159,7 +152,8 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({
styleClasses={{
inlineTagsContainer:
"border border-slate-400/20 rounded-lg bg-muted/50 shadow-sm shadow-black/5 transition-shadow focus-within:border-slate-400/40 focus-within:outline-none focus-within:ring-[3px] focus-within:ring-ring/20 p-1 gap-1",
input: "w-full min-w-[80px] focus-visible:outline-none shadow-none px-2 h-7 text-foreground/90 placeholder:text-muted-foreground bg-transparent",
input:
"w-full min-w-[80px] focus-visible:outline-none shadow-none px-2 h-7 text-foreground/90 placeholder:text-muted-foreground bg-transparent",
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:
@ -172,11 +166,7 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({
<p className="text-xs text-muted-foreground mt-1">{t("hint")}</p>
</div>
{error && (
<div className="text-sm text-red-500 mt-2">
{error}
</div>
)}
{error && <div className="text-sm text-red-500 mt-2">{error}</div>}
<div className="bg-muted/50 rounded-lg p-4 text-sm">
<h4 className="font-medium mb-2">{t("tips_title")}</h4>
@ -244,4 +234,3 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({
</div>
);
};

View file

@ -24,4 +24,3 @@ export const EditComposer: FC = () => {
</MessagePrimitive.Root>
);
};

View file

@ -204,4 +204,3 @@ export const ThinkingStepsScrollHandler: FC = () => {
return null; // This component doesn't render anything
};

View file

@ -16,4 +16,3 @@ export const ThreadScrollToBottom: FC = () => {
</ThreadPrimitive.ScrollToBottom>
);
};

View file

@ -69,4 +69,3 @@ export const ThreadWelcome: FC = () => {
</div>
);
};

View file

@ -26,15 +26,7 @@ import {
SquareIcon,
} from "lucide-react";
import { useParams } from "next/navigation";
import {
type FC,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { type FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import {
mentionedDocumentIdsAtom,

View file

@ -70,4 +70,3 @@ const UserActionBar: FC = () => {
</ActionBarPrimitive.Root>
);
};

View file

@ -223,9 +223,17 @@ export function GoogleDriveFolderTree({
const childFiles = children?.filter((c) => !c.isFolder) || [];
const indentSize = 0.75; // Smaller indent for mobile
return (
<div key={item.id} className="w-full sm:ml-[calc(var(--level)*1.25rem)]" style={{ marginLeft: `${level * indentSize}rem`, '--level': level } as React.CSSProperties & { '--level'?: number }}>
<div
key={item.id}
className="w-full sm:ml-[calc(var(--level)*1.25rem)]"
style={
{ marginLeft: `${level * indentSize}rem`, "--level": level } as React.CSSProperties & {
"--level"?: number;
}
}
>
<div
className={cn(
"flex items-center group gap-1 sm:gap-2 h-auto py-1 sm:py-2 px-1 sm:px-2 rounded-md",
@ -302,7 +310,9 @@ export function GoogleDriveFolderTree({
{childFiles.map((child) => renderItem(child, level + 1))}
{children.length === 0 && (
<div className="text-[10px] sm:text-xs text-muted-foreground py-1 sm:py-2 pl-1 sm:pl-2">Empty folder</div>
<div className="text-[10px] sm:text-xs text-muted-foreground py-1 sm:py-2 pl-1 sm:pl-2">
Empty folder
</div>
)}
</div>
)}

View file

@ -155,7 +155,6 @@ export function DashboardBreadcrumb() {
return breadcrumbs;
}
// Handle other sub-sections
let subSectionLabel = subSection.charAt(0).toUpperCase() + subSection.slice(1);
const subSectionLabels: Record<string, string> = {

View file

@ -448,10 +448,7 @@ export const AppSidebar = memo(function AppSidebar({
<SidebarContent className="gap-1">
<NavMain items={processedNavMain} />
<NavChats
chats={processedRecentChats}
searchSpaceId={searchSpaceId}
/>
<NavChats chats={processedRecentChats} searchSpaceId={searchSpaceId} />
<NavNotes
notes={processedRecentNotes}

View file

@ -62,11 +62,7 @@ const actionIconMap: Record<string, LucideIcon> = {
RefreshCw,
};
export function NavChats({
chats,
defaultOpen = true,
searchSpaceId,
}: NavChatsProps) {
export function NavChats({ chats, defaultOpen = true, searchSpaceId }: NavChatsProps) {
const t = useTranslations("sidebar");
const router = useRouter();
const pathname = usePathname();

View file

@ -63,12 +63,7 @@ const actionIconMap: Record<string, LucideIcon> = {
MoreHorizontal,
};
export function NavNotes({
notes,
onAddNote,
defaultOpen = true,
searchSpaceId,
}: NavNotesProps) {
export function NavNotes({ notes, onAddNote, defaultOpen = true, searchSpaceId }: NavNotesProps) {
const t = useTranslations("sidebar");
const router = useRouter();
const pathname = usePathname();

View file

@ -1,7 +1,4 @@
import {
IconLinkPlus,
IconUsersGroup,
} from "@tabler/icons-react";
import { IconLinkPlus, IconUsersGroup } from "@tabler/icons-react";
import {
File,
FileText,

View file

@ -166,12 +166,14 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
}
}
}, [
connectorId,
connectors,
connectorsLoading,
router,
searchSpaceId,
connector, editForm.reset, patForm.reset
connectorId,
connectors,
connectorsLoading,
router,
searchSpaceId,
connector,
editForm.reset,
patForm.reset,
// Note: editForm and patForm are intentionally excluded from dependencies
// to prevent infinite loops. They are stable form objects from react-hook-form.
]);
@ -298,11 +300,15 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
}
const candidateConfig: Record<string, unknown> = { SEARXNG_HOST: host };
const originalHost = typeof originalConfig.SEARXNG_HOST === "string" ? originalConfig.SEARXNG_HOST : "";
const originalHost =
typeof originalConfig.SEARXNG_HOST === "string" ? originalConfig.SEARXNG_HOST : "";
let hasChanges = host !== originalHost.trim();
const apiKey = (formData.SEARXNG_API_KEY || "").trim();
const originalApiKey = typeof originalConfig.SEARXNG_API_KEY === "string" ? originalConfig.SEARXNG_API_KEY : "";
const originalApiKey =
typeof originalConfig.SEARXNG_API_KEY === "string"
? originalConfig.SEARXNG_API_KEY
: "";
const originalApiKeyTrimmed = originalApiKey.trim();
if (apiKey !== originalApiKeyTrimmed) {
candidateConfig.SEARXNG_API_KEY = apiKey || null;
@ -324,7 +330,10 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
}
const language = (formData.SEARXNG_LANGUAGE || "").trim();
const originalLanguage = typeof originalConfig.SEARXNG_LANGUAGE === "string" ? originalConfig.SEARXNG_LANGUAGE : "";
const originalLanguage =
typeof originalConfig.SEARXNG_LANGUAGE === "string"
? originalConfig.SEARXNG_LANGUAGE
: "";
const originalLanguageTrimmed = originalLanguage.trim();
if (language !== originalLanguageTrimmed) {
candidateConfig.SEARXNG_LANGUAGE = language || null;
@ -534,13 +543,13 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
}
try {
const updatedConnector = await updateConnector({
const updatedConnector = (await updateConnector({
id: connectorId,
data: {
...updatePayload,
connector_type: connector.connector_type as EnumConnectorName,
},
}) as UpdateConnectorResponse;
})) as UpdateConnectorResponse;
toast.success("Connector updated!");
// Use the response from the API which has the full merged config
const newlySavedConfig = updatedConnector.config || originalConfig;

View file

@ -26,4 +26,3 @@ export function useGoogleDriveFolders({
retry: 2,
});
}