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

View file

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

View file

@ -26,13 +26,13 @@ export default function DashboardLayout({
}, },
]; ];
const customNavMain = [ const customNavMain = [
{ {
title: "Chat", title: "Chat",
url: `/dashboard/${search_space_id}/new-chat`, url: `/dashboard/${search_space_id}/new-chat`,
icon: "MessageCircle", icon: "MessageCircle",
items: [], items: [],
}, },
{ {
title: "Documents", title: "Documents",
url: `/dashboard/${search_space_id}/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 type { FC } from "react";
import { useContext } from "react"; import { useContext } from "react";
import { MarkdownText } from "@/components/assistant-ui/markdown-text"; 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 { ToolFallback } from "@/components/assistant-ui/tool-fallback";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { BranchPicker } from "@/components/assistant-ui/branch-picker"; import { BranchPicker } from "@/components/assistant-ui/branch-picker";
@ -115,4 +118,3 @@ const AssistantActionBar: FC = () => {
</ActionBarPrimitive.Root> </ActionBarPrimitive.Root>
); );
}; };

View file

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

View file

@ -74,7 +74,9 @@ const ConnectorIndicator: FC = () => {
"text-muted-foreground" "text-muted-foreground"
)} )}
aria-label={ aria-label={
hasConnectors ? `View ${activeConnectorsCount} active connectors` : "Add your first connector" hasConnectors
? `View ${activeConnectorsCount} active connectors`
: "Add your first connector"
} }
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} 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" 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")} {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"> <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} {count > 999 ? "999+" : count}
</span> </span>
@ -150,7 +154,9 @@ const ConnectorIndicator: FC = () => {
<button <button
type="button" type="button"
className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors" 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" /> <Plus className="size-3" />
Add more sources Add more sources
@ -167,7 +173,9 @@ const ConnectorIndicator: FC = () => {
<button <button
type="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" 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" /> <Plus className="size-3" />
Add Connector Add Connector
@ -283,4 +291,3 @@ export const ComposerAction: FC = () => {
</div> </div>
); );
}; };

View file

@ -8,10 +8,7 @@ import {
mentionedDocumentIdsAtom, mentionedDocumentIdsAtom,
mentionedDocumentsAtom, mentionedDocumentsAtom,
} from "@/atoms/chat/mentioned-documents.atom"; } from "@/atoms/chat/mentioned-documents.atom";
import { import { ComposerAddAttachment, ComposerAttachments } from "@/components/assistant-ui/attachment";
ComposerAddAttachment,
ComposerAttachments,
} from "@/components/assistant-ui/attachment";
import { ComposerAction } from "@/components/assistant-ui/composer-action"; import { ComposerAction } from "@/components/assistant-ui/composer-action";
import { import {
InlineMentionEditor, InlineMentionEditor,
@ -237,4 +234,3 @@ export const Composer: FC = () => {
</ComposerPrimitive.Root> </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 { useQuery, useQueryClient } from "@tanstack/react-query";
import { cacheKeys } from "@/lib/query-client/cache-keys"; import { cacheKeys } from "@/lib/query-client/cache-keys";
import { connectorsApiService } from "@/lib/apis/connectors-api.service"; import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import { import { Dialog, DialogContent } from "@/components/ui/dialog";
Dialog, import { Tabs, TabsContent } from "@/components/ui/tabs";
DialogContent,
} from "@/components/ui/dialog";
import {
Tabs,
TabsContent,
} from "@/components/ui/tabs";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab"; import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab";
@ -35,19 +29,15 @@ export const ConnectorIndicator: FC = () => {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const { data: documentTypeCounts, isLoading: documentTypesLoading } = const { data: documentTypeCounts, isLoading: documentTypesLoading } =
useAtomValue(documentTypeCountsAtom); useAtomValue(documentTypeCountsAtom);
// Check if YouTube view is active // Check if YouTube view is active
const isYouTubeView = searchParams.get("view") === "youtube"; const isYouTubeView = searchParams.get("view") === "youtube";
// Track active indexing tasks // Track active indexing tasks
const { summary: logsSummary } = useLogsSummary( const { summary: logsSummary } = useLogsSummary(searchSpaceId ? Number(searchSpaceId) : 0, 24, {
searchSpaceId ? Number(searchSpaceId) : 0, enablePolling: true,
24, refetchInterval: 5000,
{ });
enablePolling: true,
refetchInterval: 5000,
}
);
// Use the custom hook for dialog state management // Use the custom hook for dialog state management
const { 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"> <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 */} {/* YouTube Crawler View - shown when adding YouTube videos */}
{isYouTubeView && searchSpaceId ? ( {isYouTubeView && searchSpaceId ? (
<YouTubeCrawlerView <YouTubeCrawlerView searchSpaceId={searchSpaceId} onBack={handleBackFromYouTube} />
searchSpaceId={searchSpaceId}
onBack={handleBackFromYouTube}
/>
) : connectingConnectorType ? ( ) : connectingConnectorType ? (
<ConnectorConnectView <ConnectorConnectView
connectorType={connectingConnectorType} connectorType={connectingConnectorType}
@ -234,17 +221,25 @@ export const ConnectorIndicator: FC = () => {
onSave={() => handleSaveConnector(() => refreshConnectors())} onSave={() => handleSaveConnector(() => refreshConnectors())}
onDisconnect={() => handleDisconnectConnector(() => refreshConnectors())} onDisconnect={() => handleDisconnectConnector(() => refreshConnectors())}
onBack={handleBackFromEdit} 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} onConfigChange={setConnectorConfig}
onNameChange={setConnectorName} onNameChange={setConnectorName}
/> />
) : indexingConfig ? ( ) : indexingConfig ? (
<IndexingConfigurationView <IndexingConfigurationView
config={indexingConfig} config={indexingConfig}
connector={indexingConnector ? { connector={
...indexingConnector, indexingConnector
config: indexingConnectorConfig || indexingConnector.config, ? {
} : undefined} ...indexingConnector,
config: indexingConnectorConfig || indexingConnector.config,
}
: undefined
}
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
periodicEnabled={periodicEnabled} periodicEnabled={periodicEnabled}
@ -259,7 +254,11 @@ export const ConnectorIndicator: FC = () => {
onSkip={handleSkipIndexing} 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 */} {/* Header */}
<ConnectorDialogHeader <ConnectorDialogHeader
activeTab={activeTab} activeTab={activeTab}
@ -274,23 +273,23 @@ export const ConnectorIndicator: FC = () => {
<div className="flex-1 min-h-0 relative overflow-hidden"> <div className="flex-1 min-h-0 relative overflow-hidden">
<div className="h-full overflow-y-auto" onScroll={handleScroll}> <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"> <div className="px-6 sm:px-12 py-6 sm:py-8 pb-16 sm:pb-16">
<TabsContent value="all" className="m-0"> <TabsContent value="all" className="m-0">
<AllConnectorsTab <AllConnectorsTab
searchQuery={searchQuery} searchQuery={searchQuery}
searchSpaceId={searchSpaceId} searchSpaceId={searchSpaceId}
connectedTypes={connectedTypes} connectedTypes={connectedTypes}
connectingId={connectingId} connectingId={connectingId}
allConnectors={allConnectors} allConnectors={allConnectors}
documentTypeCounts={documentTypeCounts} documentTypeCounts={documentTypeCounts}
indexingConnectorIds={indexingConnectorIds} indexingConnectorIds={indexingConnectorIds}
logsSummary={logsSummary} logsSummary={logsSummary}
onConnectOAuth={handleConnectOAuth} onConnectOAuth={handleConnectOAuth}
onConnectNonOAuth={handleConnectNonOAuth} onConnectNonOAuth={handleConnectNonOAuth}
onCreateWebcrawler={handleCreateWebcrawler} onCreateWebcrawler={handleCreateWebcrawler}
onCreateYouTubeCrawler={handleCreateYouTubeCrawler} onCreateYouTubeCrawler={handleCreateYouTubeCrawler}
onManage={handleStartEdit} onManage={handleStartEdit}
/> />
</TabsContent> </TabsContent>
<ActiveConnectorsTab <ActiveConnectorsTab
hasSources={hasSources} hasSources={hasSources}

View file

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

View file

@ -2,15 +2,8 @@
import { Search } from "lucide-react"; import { Search } from "lucide-react";
import type { FC } from "react"; import type { FC } from "react";
import { import { DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
DialogDescription, import { TabsList, TabsTrigger } from "@/components/ui/tabs";
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
TabsList,
TabsTrigger,
} from "@/components/ui/tabs";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface ConnectorDialogHeaderProps { interface ConnectorDialogHeaderProps {
@ -84,4 +77,3 @@ export const ConnectorDialogHeader: FC<ConnectorDialogHeaderProps> = ({
</div> </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"> <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> <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"> <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> </p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Start Date */} {/* Start Date */}
<div className="space-y-2"> <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> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
@ -77,7 +80,9 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
{/* End Date */} {/* End Date */}
<div className="space-y-2"> <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> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
@ -137,4 +142,3 @@ export const DateRangeSelector: FC<DateRangeSelectorProps> = ({
</div> </div>
); );
}; };

View file

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

View file

@ -32,10 +32,7 @@ const baiduSearchApiFormSchema = z.object({
type BaiduSearchApiFormValues = z.infer<typeof baiduSearchApiFormSchema>; type BaiduSearchApiFormValues = z.infer<typeof baiduSearchApiFormSchema>;
export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const form = useForm<BaiduSearchApiFormValues>({ const form = useForm<BaiduSearchApiFormValues>({
resolver: zodResolver(baiduSearchApiFormSchema), resolver: zodResolver(baiduSearchApiFormSchema),
@ -77,7 +74,8 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
<div className="-ml-1"> <div className="-ml-1">
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle> <AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0"> <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 <a
href="https://qianfan.cloud.baidu.com/" href="https://qianfan.cloud.baidu.com/"
target="_blank" 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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -100,11 +102,11 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My Baidu Search Connector" placeholder="My Baidu Search Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -122,12 +124,12 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Baidu AppBuilder API Key</FormLabel> <FormLabel className="text-xs sm:text-sm">Baidu AppBuilder API Key</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="password" type="password"
placeholder="Enter your Baidu API key" placeholder="Enter your Baidu API key"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -155,4 +157,3 @@ export const BaiduSearchApiConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

@ -53,10 +53,7 @@ const bookstackConnectorFormSchema = z.object({
type BookStackConnectorFormValues = z.infer<typeof bookstackConnectorFormSchema>; type BookStackConnectorFormValues = z.infer<typeof bookstackConnectorFormSchema>;
export const BookStackConnectForm: FC<ConnectFormProps> = ({ export const BookStackConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined); const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined); const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -110,14 +107,19 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<div className="-ml-1"> <div className="-ml-1">
<AlertTitle className="text-xs sm:text-sm">API Token Required</AlertTitle> <AlertTitle className="text-xs sm:text-sm">API Token Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0"> <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> </AlertDescription>
</div> </div>
</Alert> </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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -125,11 +127,11 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My BookStack Connector" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -147,16 +149,17 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">BookStack Base URL</FormLabel> <FormLabel className="text-xs sm:text-sm">BookStack Base URL</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="url" type="url"
placeholder="https://your-bookstack-instance.com" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -170,11 +173,11 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Token ID</FormLabel> <FormLabel className="text-xs sm:text-sm">Token ID</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="Your Token ID" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -192,12 +195,12 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Token Secret</FormLabel> <FormLabel className="text-xs sm:text-sm">Token Secret</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="password" type="password"
placeholder="Your Token Secret" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -211,7 +214,7 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */} {/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20"> <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> <h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */} {/* Date Range Selector */}
<DateRangeSelector <DateRangeSelector
startDate={startDate} startDate={startDate}
@ -229,14 +232,24 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals Automatically re-index at regular intervals
</p> </p>
</div> </div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} /> <Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div> </div>
{periodicEnabled && ( {periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3"> <div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2"> <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">
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}> Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger <SelectTrigger
id="frequency" id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm" 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" /> <SelectValue placeholder="Select frequency" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="z-[100]"> <SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem> <SelectItem value="15" className="text-xs sm:text-sm">
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem> Every 15 minutes
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem> </SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem> <SelectItem value="60" className="text-xs sm:text-sm">
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem> Every hour
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem> </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> </SelectContent>
</Select> </Select>
</div> </div>
@ -264,7 +289,9 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
{/* What you get section */} {/* What you get section */}
{getConnectorBenefits(EnumConnectorName.BOOKSTACK_CONNECTOR) && ( {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"> <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"> <ul className="list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
{getConnectorBenefits(EnumConnectorName.BOOKSTACK_CONNECTOR)?.map((benefit) => ( {getConnectorBenefits(EnumConnectorName.BOOKSTACK_CONNECTOR)?.map((benefit) => (
<li key={benefit}>{benefit}</li> <li key={benefit}>{benefit}</li>
@ -274,7 +301,11 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
)} )}
{/* Documentation Section */} {/* 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"> <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"> <AccordionTrigger className="text-sm sm:text-base font-medium px-3 sm:px-6 no-underline hover:no-underline">
Documentation Documentation
@ -283,14 +314,17 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<div> <div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3> <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"> <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> </p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1"> <ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li> <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>
<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> </li>
</ul> </ul>
</div> </div>
@ -302,13 +336,16 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<Info className="h-3 w-3 sm:h-4 sm:w-4" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">API Token Required</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">API Token Required</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
<div className="space-y-4 sm:space-y-6"> <div className="space-y-4 sm:space-y-6">
<div> <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"> <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>Log in to your BookStack instance</li>
<li>Click on your profile icon Edit Profile</li> <li>Click on your profile icon Edit Profile</li>
@ -320,15 +357,19 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
</div> </div>
<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"> <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> </p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20"> <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" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Rate Limiting</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">Rate Limiting</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
</div> </div>
@ -341,13 +382,16 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3> <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"> <ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li> <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>
<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>
<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>
<li> <li>
Click <strong>Connect</strong> to establish the connection. Click <strong>Connect</strong> to establish the connection.
@ -376,4 +420,3 @@ export const BookStackConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

@ -29,10 +29,7 @@ const circlebackFormSchema = z.object({
type CirclebackFormValues = z.infer<typeof circlebackFormSchema>; type CirclebackFormValues = z.infer<typeof circlebackFormSchema>;
export const CirclebackConnectForm: FC<ConnectFormProps> = ({ export const CirclebackConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const form = useForm<CirclebackFormValues>({ const form = useForm<CirclebackFormValues>({
resolver: zodResolver(circlebackFormSchema), resolver: zodResolver(circlebackFormSchema),
@ -71,14 +68,19 @@ export const CirclebackConnectForm: FC<ConnectFormProps> = ({
<div className="-ml-1"> <div className="-ml-1">
<AlertTitle className="text-xs sm:text-sm">Webhook-Based Integration</AlertTitle> <AlertTitle className="text-xs sm:text-sm">Webhook-Based Integration</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0"> <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> </AlertDescription>
</div> </div>
</Alert> </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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -86,11 +88,11 @@ export const CirclebackConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My Circleback Connector" placeholder="My Circleback Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -118,4 +120,3 @@ export const CirclebackConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

@ -49,10 +49,7 @@ const clickupConnectorFormSchema = z.object({
type ClickUpConnectorFormValues = z.infer<typeof clickupConnectorFormSchema>; type ClickUpConnectorFormValues = z.infer<typeof clickupConnectorFormSchema>;
export const ClickUpConnectForm: FC<ConnectFormProps> = ({ export const ClickUpConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined); const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = 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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -125,11 +126,11 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My ClickUp Connector" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -147,12 +148,12 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">ClickUp API Token</FormLabel> <FormLabel className="text-xs sm:text-sm">ClickUp API Token</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="password" type="password"
placeholder="pk_..." 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -166,7 +167,7 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */} {/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20"> <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> <h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */} {/* Date Range Selector */}
<DateRangeSelector <DateRangeSelector
startDate={startDate} startDate={startDate}
@ -184,14 +185,24 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals Automatically re-index at regular intervals
</p> </p>
</div> </div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} /> <Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div> </div>
{periodicEnabled && ( {periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3"> <div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2"> <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">
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}> Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger <SelectTrigger
id="frequency" id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm" 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" /> <SelectValue placeholder="Select frequency" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="z-[100]"> <SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem> <SelectItem value="15" className="text-xs sm:text-sm">
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem> Every 15 minutes
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem> </SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem> <SelectItem value="60" className="text-xs sm:text-sm">
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem> Every hour
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem> </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> </SelectContent>
</Select> </Select>
</div> </div>
@ -229,7 +252,11 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
)} )}
{/* Documentation Section */} {/* 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"> <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"> <AccordionTrigger className="text-sm sm:text-base font-medium px-3 sm:px-6 no-underline hover:no-underline">
Documentation Documentation
@ -238,14 +265,17 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<div> <div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3> <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"> <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> </p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1"> <ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li> <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>
<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> </li>
</ul> </ul>
</div> </div>
@ -257,19 +287,23 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<Info className="h-3 w-3 sm:h-4 sm:w-4" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">API Token Required</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">API Token Required</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
<div className="space-y-4 sm:space-y-6"> <div className="space-y-4 sm:space-y-6">
<div> <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"> <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>Log in to your ClickUp account</li>
<li>Click your avatar in the upper-right corner and select "Settings"</li> <li>Click your avatar in the upper-right corner and select "Settings"</li>
<li>In the sidebar, click "Apps"</li> <li>In the sidebar, click "Apps"</li>
<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>
<li>Copy the generated token (it typically starts with "pk_")</li> <li>Copy the generated token (it typically starts with "pk_")</li>
<li> <li>
@ -288,15 +322,20 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
</div> </div>
<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"> <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> </p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20"> <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" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
</div> </div>
@ -309,7 +348,8 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3> <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"> <ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li> <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>
<li> <li>
Place your <strong>API Token</strong> in the form field. Place your <strong>API Token</strong> in the form field.
@ -341,4 +381,3 @@ export const ClickUpConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

@ -51,10 +51,7 @@ const confluenceConnectorFormSchema = z.object({
type ConfluenceConnectorFormValues = z.infer<typeof confluenceConnectorFormSchema>; type ConfluenceConnectorFormValues = z.infer<typeof confluenceConnectorFormSchema>;
export const ConfluenceConnectForm: FC<ConnectFormProps> = ({ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined); const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = 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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -131,11 +132,11 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My Confluence Connector" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -153,16 +154,17 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Confluence Base URL</FormLabel> <FormLabel className="text-xs sm:text-sm">Confluence Base URL</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="url" type="url"
placeholder="https://your-domain.atlassian.net" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -176,13 +178,13 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Email Address</FormLabel> <FormLabel className="text-xs sm:text-sm">Email Address</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="email" type="email"
placeholder="your-email@example.com" placeholder="your-email@example.com"
autoComplete="email" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -200,12 +202,12 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">API Token</FormLabel> <FormLabel className="text-xs sm:text-sm">API Token</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="password" type="password"
placeholder="Your API Token" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -219,7 +221,7 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */} {/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20"> <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> <h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */} {/* Date Range Selector */}
<DateRangeSelector <DateRangeSelector
startDate={startDate} startDate={startDate}
@ -237,14 +239,24 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals Automatically re-index at regular intervals
</p> </p>
</div> </div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} /> <Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div> </div>
{periodicEnabled && ( {periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3"> <div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2"> <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">
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}> Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger <SelectTrigger
id="frequency" id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm" 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" /> <SelectValue placeholder="Select frequency" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="z-[100]"> <SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem> <SelectItem value="15" className="text-xs sm:text-sm">
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem> Every 15 minutes
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem> </SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem> <SelectItem value="60" className="text-xs sm:text-sm">
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem> Every hour
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem> </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> </SelectContent>
</Select> </Select>
</div> </div>
@ -272,7 +296,9 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
{/* What you get section */} {/* What you get section */}
{getConnectorBenefits(EnumConnectorName.CONFLUENCE_CONNECTOR) && ( {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"> <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"> <ul className="list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
{getConnectorBenefits(EnumConnectorName.CONFLUENCE_CONNECTOR)?.map((benefit) => ( {getConnectorBenefits(EnumConnectorName.CONFLUENCE_CONNECTOR)?.map((benefit) => (
<li key={benefit}>{benefit}</li> <li key={benefit}>{benefit}</li>
@ -282,7 +308,11 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
)} )}
{/* Documentation Section */} {/* 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"> <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"> <AccordionTrigger className="text-sm sm:text-base font-medium px-3 sm:px-6 no-underline hover:no-underline">
Documentation Documentation
@ -291,14 +321,17 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<div> <div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3> <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"> <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> </p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1"> <ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li> <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>
<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> </li>
</ul> </ul>
</div> </div>
@ -308,15 +341,20 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3> <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"> <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" /> <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]"> <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> </AlertDescription>
</Alert> </Alert>
<div className="space-y-4 sm:space-y-6"> <div className="space-y-4 sm:space-y-6">
<div> <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"> <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>Log in to your Atlassian account</li>
<li> <li>
@ -343,15 +381,20 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
</div> </div>
<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"> <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> </p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20"> <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" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
</div> </div>
@ -364,10 +407,12 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3> <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"> <ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li> <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>
<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>
<li> <li>
Enter your <strong>Email Address</strong> associated with your Atlassian account Enter your <strong>Email Address</strong> associated with your Atlassian account
@ -402,4 +447,3 @@ export const ConfluenceConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

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

View file

@ -73,10 +73,7 @@ const elasticsearchConnectorFormSchema = z
type ElasticsearchConnectorFormValues = z.infer<typeof elasticsearchConnectorFormSchema>; type ElasticsearchConnectorFormValues = z.infer<typeof elasticsearchConnectorFormSchema>;
export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const authBasicId = useId(); const authBasicId = useId();
const authApiKeyId = 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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -195,11 +196,11 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My Elasticsearch Connector" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -231,7 +232,8 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -241,7 +243,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{/* Show parsed URL details */} {/* Show parsed URL details */}
{form.watch("endpoint_url") && ( {form.watch("endpoint_url") && (
<div className="rounded-lg border border-border bg-muted/50 p-3"> <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"> <div className="text-[10px] sm:text-xs text-muted-foreground space-y-1">
{(() => { {(() => {
try { try {
@ -305,7 +309,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<div className="h-2.5 w-2.5 rounded-full bg-current" /> <div className="h-2.5 w-2.5 rounded-full bg-current" />
</RadioGroup.Indicator> </RadioGroup.Indicator>
</RadioGroup.Item> </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>
<div className="flex items-center space-x-2"> <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" /> <div className="h-2.5 w-2.5 rounded-full bg-current" />
</RadioGroup.Indicator> </RadioGroup.Indicator>
</RadioGroup.Item> </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> </div>
</RadioGroup.Root> </RadioGroup.Root>
</FormControl> </FormControl>
@ -337,12 +345,12 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Username</FormLabel> <FormLabel className="text-xs sm:text-sm">Username</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="elastic" placeholder="elastic"
autoComplete="username" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -392,7 +400,8 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -409,11 +418,11 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Index Selection</FormLabel> <FormLabel className="text-xs sm:text-sm">Index Selection</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="logs-*, documents-*, app-logs" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -454,7 +463,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{/* Advanced Configuration */} {/* Advanced Configuration */}
<Accordion type="single" collapsible className="w-full"> <Accordion type="single" collapsible className="w-full">
<AccordionItem value="advanced"> <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"> <AccordionContent className="space-y-4">
{/* Default Search Query */} {/* Default Search Query */}
<FormField <FormField
@ -467,15 +478,16 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<span className="text-muted-foreground">(Optional)</span> <span className="text-muted-foreground">(Optional)</span>
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="*" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -489,19 +501,19 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm"> <FormLabel className="text-xs sm:text-sm">
Search Fields{" "} Search Fields <span className="text-muted-foreground">(Optional)</span>
<span className="text-muted-foreground">(Optional)</span>
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="title, content, description" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -542,15 +554,14 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{...field} {...field}
onChange={(e) => onChange={(e) =>
field.onChange( field.onChange(
e.target.value === "" e.target.value === "" ? undefined : parseInt(e.target.value, 10)
? undefined
: parseInt(e.target.value, 10)
) )
} }
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -563,7 +574,7 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */} {/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20"> <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> <h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */} {/* Date Range Selector */}
<DateRangeSelector <DateRangeSelector
startDate={startDate} startDate={startDate}
@ -581,14 +592,24 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals Automatically re-index at regular intervals
</p> </p>
</div> </div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} /> <Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div> </div>
{periodicEnabled && ( {periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3"> <div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2"> <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">
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}> Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger <SelectTrigger
id="frequency" id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm" 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" /> <SelectValue placeholder="Select frequency" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="z-[100]"> <SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem> <SelectItem value="15" className="text-xs sm:text-sm">
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem> Every 15 minutes
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem> </SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem> <SelectItem value="60" className="text-xs sm:text-sm">
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem> Every hour
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem> </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> </SelectContent>
</Select> </Select>
</div> </div>
@ -616,7 +649,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
{/* What you get section */} {/* What you get section */}
{getConnectorBenefits(EnumConnectorName.ELASTICSEARCH_CONNECTOR) && ( {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"> <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"> <ul className="list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
{getConnectorBenefits(EnumConnectorName.ELASTICSEARCH_CONNECTOR)?.map((benefit) => ( {getConnectorBenefits(EnumConnectorName.ELASTICSEARCH_CONNECTOR)?.map((benefit) => (
<li key={benefit}>{benefit}</li> <li key={benefit}>{benefit}</li>
@ -626,7 +661,11 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
)} )}
{/* Documentation Section */} {/* 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"> <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"> <AccordionTrigger className="text-sm sm:text-base font-medium px-3 sm:px-6 no-underline hover:no-underline">
Documentation Documentation
@ -635,7 +674,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<div> <div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3> <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"> <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> </p>
</div> </div>
@ -644,43 +685,73 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Connection Setup</h3> <h3 className="text-sm sm:text-base font-semibold mb-2">Connection Setup</h3>
<div className="space-y-4 sm:space-y-6"> <div className="space-y-4 sm:space-y-6">
<div> <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"> <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> </p>
<ul className="list-disc pl-5 space-y-1 text-[10px] sm:text-xs text-muted-foreground mb-4"> <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>
<li>Self-hosted: <code className="bg-muted px-1 py-0.5 rounded">https://elasticsearch.example.com:9200</code></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> </ul>
</div> </div>
<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"> <p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
Elasticsearch requires authentication. You can use either: Elasticsearch requires authentication. You can use either:
</p> </p>
<ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4"> <ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li> <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"> <pre className="bg-muted p-2 rounded mt-1 text-[9px] overflow-x-auto">
<code>POST /_security/api_key</code> <code>POST /_security/api_key</code>
</pre> </pre>
</li> </li>
<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> </li>
</ul> </ul>
</div> </div>
<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"> <p className="text-[10px] sm:text-xs text-muted-foreground mb-3">
Specify which indices to search. You can: Specify which indices to search. You can:
</p> </p>
<ul className="list-disc pl-5 space-y-1 text-[10px] sm:text-xs text-muted-foreground"> <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>
<li>List specific indices: <code className="bg-muted px-1 py-0.5 rounded">logs-2024, documents-2024</code></li> Use wildcards: <code className="bg-muted px-1 py-0.5 rounded">logs-*</code>{" "}
<li>Leave empty to search all accessible indices (not recommended for performance)</li> 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> </ul>
</div> </div>
</div> </div>
@ -694,19 +765,30 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<div> <div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Search Query</h4> <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"> <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> </p>
</div> </div>
<div> <div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Search Fields</h4> <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"> <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> </p>
<ul className="list-disc pl-5 space-y-1 text-[10px] sm:text-xs text-muted-foreground"> <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>
<li><code className="bg-muted px-1 py-0.5 rounded">content</code> - Main content</li> <code className="bg-muted px-1 py-0.5 rounded">title</code> - Document
<li><code className="bg-muted px-1 py-0.5 rounded">description</code> - Descriptions</li> 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> </ul>
<p className="text-[10px] sm:text-xs text-muted-foreground mt-2"> <p className="text-[10px] sm:text-xs text-muted-foreground mt-2">
Leave empty to search all fields in your documents. Leave empty to search all fields in your documents.
@ -716,7 +798,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<div> <div>
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Maximum Documents</h4> <h4 className="text-[10px] sm:text-xs font-medium mb-2">Maximum Documents</h4>
<p className="text-[10px] sm:text-xs text-muted-foreground"> <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> </p>
</div> </div>
</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> <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"> <ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li> <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>
<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>
<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> </li>
</ul> </ul>
</div> </div>
<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"> <ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li> <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>
<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>
<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> </li>
</ul> </ul>
</div> </div>
@ -761,13 +855,16 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<h4 className="text-[10px] sm:text-xs font-medium mb-2">Search Issues</h4> <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"> <ul className="list-disc pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li> <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>
<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>
<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> </li>
</ul> </ul>
</div> </div>
@ -776,7 +873,9 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
<Info className="h-3 w-3 sm:h-4 sm:w-4" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Need More Help?</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">Need More Help?</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
</div> </div>
@ -788,4 +887,3 @@ export const ElasticsearchConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

@ -57,10 +57,7 @@ const githubConnectorFormSchema = z.object({
type GithubConnectorFormValues = z.infer<typeof githubConnectorFormSchema>; type GithubConnectorFormValues = z.infer<typeof githubConnectorFormSchema>;
export const GithubConnectForm: FC<ConnectFormProps> = ({ export const GithubConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined); const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = useState<Date | undefined>(undefined); const [endDate, setEndDate] = useState<Date | undefined>(undefined);
@ -92,7 +89,7 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
isSubmittingRef.current = true; isSubmittingRef.current = true;
try { try {
const repoList = stringToArray(values.repo_full_names); const repoList = stringToArray(values.repo_full_names);
await onSubmit({ await onSubmit({
name: values.name, name: values.name,
connector_type: EnumConnectorName.GITHUB_CONNECTOR, connector_type: EnumConnectorName.GITHUB_CONNECTOR,
@ -122,7 +119,8 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<div className="-ml-1"> <div className="-ml-1">
<AlertTitle className="text-xs sm:text-sm">Personal Access Token Required</AlertTitle> <AlertTitle className="text-xs sm:text-sm">Personal Access Token Required</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0"> <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 <a
href="https://github.com/settings/tokens" href="https://github.com/settings/tokens"
target="_blank" 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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -145,11 +147,11 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My GitHub Connector" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -167,16 +169,17 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">GitHub Personal Access Token</FormLabel> <FormLabel className="text-xs sm:text-sm">GitHub Personal Access Token</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="password" type="password"
placeholder="ghp_..." 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -190,15 +193,16 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Repository Names</FormLabel> <FormLabel className="text-xs sm:text-sm">Repository Names</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="owner/repo1, owner/repo2" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -222,7 +226,7 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */} {/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20"> <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> <h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */} {/* Date Range Selector */}
<DateRangeSelector <DateRangeSelector
startDate={startDate} startDate={startDate}
@ -240,14 +244,24 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals Automatically re-index at regular intervals
</p> </p>
</div> </div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} /> <Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div> </div>
{periodicEnabled && ( {periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3"> <div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2"> <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">
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}> Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger <SelectTrigger
id="frequency" id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm" 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" /> <SelectValue placeholder="Select frequency" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="z-[100]"> <SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem> <SelectItem value="15" className="text-xs sm:text-sm">
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem> Every 15 minutes
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem> </SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem> <SelectItem value="60" className="text-xs sm:text-sm">
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem> Every hour
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem> </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> </SelectContent>
</Select> </Select>
</div> </div>
@ -285,7 +311,11 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
)} )}
{/* Documentation Section */} {/* 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"> <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"> <AccordionTrigger className="text-sm sm:text-base font-medium px-3 sm:px-6 no-underline hover:no-underline">
Documentation Documentation
@ -294,7 +324,10 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<div> <div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3> <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"> <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> </p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1"> <ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li> <li>
@ -303,7 +336,8 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<li>Large files (over 1MB) are skipped during indexing.</li> <li>Large files (over 1MB) are skipped during indexing.</li>
<li>Only specified repositories are indexed.</li> <li>Only specified repositories are indexed.</li>
<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> </li>
</ul> </ul>
</div> </div>
@ -313,15 +347,20 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3> <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"> <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" /> <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]"> <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> </AlertDescription>
</Alert> </Alert>
<div className="space-y-4 sm:space-y-6"> <div className="space-y-4 sm:space-y-6">
<div> <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"> <ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground">
<li> <li>
Go to your GitHub{" "} Go to your GitHub{" "}
@ -336,39 +375,46 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
</li> </li>
<li> <li>
Click on <strong>Personal access tokens</strong>, then choose{" "} 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>
<li> <li>
Click <strong>Generate new token</strong> (and choose the appropriate type). Click <strong>Generate new token</strong> (and choose the appropriate type).
</li> </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> <li>
Give your token a descriptive name (e.g., "SurfSense Connector"). Under <strong>Select scopes</strong> (for classic tokens) or{" "}
</li> <strong>Repository access</strong> (for fine-grained), grant the necessary
<li> permissions. At minimum, the <strong>`repo`</strong> scope (or equivalent
Set an expiration date for the token (recommended for security). read access to repositories for fine-grained tokens) is required to read
</li> repository content.
<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.
</li> </li>
<li> <li>
Click <strong>Generate token</strong>. Click <strong>Generate token</strong>.
</li> </li>
<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> </li>
</ol> </ol>
</div> </div>
<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"> <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> </p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20"> <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" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Repository Access</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">Repository Access</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
</div> </div>
@ -381,13 +427,15 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3> <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"> <ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li> <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>
<li> <li>
Enter your <strong>GitHub Personal Access Token</strong> in the form field. Enter your <strong>GitHub Personal Access Token</strong> in the form field.
</li> </li>
<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>
<li> <li>
Click <strong>Connect</strong> to establish the connection. Click <strong>Connect</strong> to establish the connection.
@ -416,4 +464,3 @@ export const GithubConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

@ -51,10 +51,7 @@ const jiraConnectorFormSchema = z.object({
type JiraConnectorFormValues = z.infer<typeof jiraConnectorFormSchema>; type JiraConnectorFormValues = z.infer<typeof jiraConnectorFormSchema>;
export const JiraConnectForm: FC<ConnectFormProps> = ({ export const JiraConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined); const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = 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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -131,11 +132,11 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My Jira Connector" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -153,12 +154,12 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Jira Base URL</FormLabel> <FormLabel className="text-xs sm:text-sm">Jira Base URL</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="url" type="url"
placeholder="https://your-domain.atlassian.net" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -176,13 +177,13 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Email Address</FormLabel> <FormLabel className="text-xs sm:text-sm">Email Address</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="email" type="email"
placeholder="your-email@example.com" placeholder="your-email@example.com"
autoComplete="email" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -200,12 +201,12 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">API Token</FormLabel> <FormLabel className="text-xs sm:text-sm">API Token</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="password" type="password"
placeholder="Your API Token" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -219,7 +220,7 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */} {/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20"> <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> <h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */} {/* Date Range Selector */}
<DateRangeSelector <DateRangeSelector
startDate={startDate} startDate={startDate}
@ -237,14 +238,24 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals Automatically re-index at regular intervals
</p> </p>
</div> </div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} /> <Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div> </div>
{periodicEnabled && ( {periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3"> <div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2"> <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">
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}> Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger <SelectTrigger
id="frequency" id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm" 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" /> <SelectValue placeholder="Select frequency" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="z-[100]"> <SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem> <SelectItem value="15" className="text-xs sm:text-sm">
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem> Every 15 minutes
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem> </SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem> <SelectItem value="60" className="text-xs sm:text-sm">
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem> Every hour
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem> </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> </SelectContent>
</Select> </Select>
</div> </div>
@ -282,7 +305,11 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
)} )}
{/* Documentation Section */} {/* 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"> <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"> <AccordionTrigger className="text-sm sm:text-base font-medium px-3 sm:px-6 no-underline hover:no-underline">
Documentation Documentation
@ -291,14 +318,17 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<div> <div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3> <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"> <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> </p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1"> <ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li> <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>
<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> </li>
</ul> </ul>
</div> </div>
@ -308,15 +338,20 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Authorization</h3> <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"> <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" /> <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]"> <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> </AlertDescription>
</Alert> </Alert>
<div className="space-y-4 sm:space-y-6"> <div className="space-y-4 sm:space-y-6">
<div> <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"> <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>Log in to your Atlassian account</li>
<li> <li>
@ -343,15 +378,20 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
</div> </div>
<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"> <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> </p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20"> <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" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
</div> </div>
@ -364,10 +404,12 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3> <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"> <ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li> <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>
<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>
<li> <li>
Enter your <strong>Email Address</strong> associated with your Atlassian account Enter your <strong>Email Address</strong> associated with your Atlassian account
@ -404,4 +446,3 @@ export const JiraConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

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

View file

@ -32,10 +32,7 @@ const linkupApiFormSchema = z.object({
type LinkupApiFormValues = z.infer<typeof linkupApiFormSchema>; type LinkupApiFormValues = z.infer<typeof linkupApiFormSchema>;
export const LinkupApiConnectForm: FC<ConnectFormProps> = ({ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const form = useForm<LinkupApiFormValues>({ const form = useForm<LinkupApiFormValues>({
resolver: zodResolver(linkupApiFormSchema), 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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -100,11 +101,11 @@ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My Linkup API Connector" placeholder="My Linkup API Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -122,12 +123,12 @@ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Linkup API Key</FormLabel> <FormLabel className="text-xs sm:text-sm">Linkup API Key</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="password" type="password"
placeholder="Enter your Linkup API key" placeholder="Enter your Linkup API key"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -155,4 +156,3 @@ export const LinkupApiConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

@ -49,10 +49,7 @@ const lumaConnectorFormSchema = z.object({
type LumaConnectorFormValues = z.infer<typeof lumaConnectorFormSchema>; type LumaConnectorFormValues = z.infer<typeof lumaConnectorFormSchema>;
export const LumaConnectForm: FC<ConnectFormProps> = ({ export const LumaConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const [startDate, setStartDate] = useState<Date | undefined>(undefined); const [startDate, setStartDate] = useState<Date | undefined>(undefined);
const [endDate, setEndDate] = 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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -125,11 +126,11 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My Luma Connector" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -147,12 +148,12 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Luma API Key</FormLabel> <FormLabel className="text-xs sm:text-sm">Luma API Key</FormLabel>
<FormControl> <FormControl>
<Input <Input
type="password" type="password"
placeholder="Your API Key" 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" 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} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -166,7 +167,7 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
{/* Indexing Configuration */} {/* Indexing Configuration */}
<div className="space-y-4 pt-4 border-t border-slate-400/20"> <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> <h3 className="text-sm sm:text-base font-medium">Indexing Configuration</h3>
{/* Date Range Selector */} {/* Date Range Selector */}
<DateRangeSelector <DateRangeSelector
startDate={startDate} startDate={startDate}
@ -184,14 +185,24 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
Automatically re-index at regular intervals Automatically re-index at regular intervals
</p> </p>
</div> </div>
<Switch checked={periodicEnabled} onCheckedChange={setPeriodicEnabled} disabled={isSubmitting} /> <Switch
checked={periodicEnabled}
onCheckedChange={setPeriodicEnabled}
disabled={isSubmitting}
/>
</div> </div>
{periodicEnabled && ( {periodicEnabled && (
<div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3"> <div className="mt-4 pt-4 border-t border-slate-400/20 space-y-3">
<div className="space-y-2"> <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">
<Select value={frequencyMinutes} onValueChange={setFrequencyMinutes} disabled={isSubmitting}> Sync Frequency
</Label>
<Select
value={frequencyMinutes}
onValueChange={setFrequencyMinutes}
disabled={isSubmitting}
>
<SelectTrigger <SelectTrigger
id="frequency" id="frequency"
className="w-full bg-slate-400/5 dark:bg-slate-400/5 border-slate-400/20 text-xs sm:text-sm" 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" /> <SelectValue placeholder="Select frequency" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="z-[100]"> <SelectContent className="z-[100]">
<SelectItem value="15" className="text-xs sm:text-sm">Every 15 minutes</SelectItem> <SelectItem value="15" className="text-xs sm:text-sm">
<SelectItem value="60" className="text-xs sm:text-sm">Every hour</SelectItem> Every 15 minutes
<SelectItem value="360" className="text-xs sm:text-sm">Every 6 hours</SelectItem> </SelectItem>
<SelectItem value="720" className="text-xs sm:text-sm">Every 12 hours</SelectItem> <SelectItem value="60" className="text-xs sm:text-sm">
<SelectItem value="1440" className="text-xs sm:text-sm">Daily</SelectItem> Every hour
<SelectItem value="10080" className="text-xs sm:text-sm">Weekly</SelectItem> </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> </SelectContent>
</Select> </Select>
</div> </div>
@ -229,7 +252,11 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
)} )}
{/* Documentation Section */} {/* 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"> <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"> <AccordionTrigger className="text-sm sm:text-base font-medium px-3 sm:px-6 no-underline hover:no-underline">
Documentation Documentation
@ -238,14 +265,17 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<div> <div>
<h3 className="text-sm sm:text-base font-semibold mb-2">How it works</h3> <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"> <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> </p>
<ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1"> <ul className="mt-2 list-disc pl-5 text-[10px] sm:text-xs text-muted-foreground space-y-1">
<li> <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>
<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> </li>
</ul> </ul>
</div> </div>
@ -257,13 +287,16 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<Info className="h-3 w-3 sm:h-4 sm:w-4" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">API Key Required</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">API Key Required</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
<div className="space-y-4 sm:space-y-6"> <div className="space-y-4 sm:space-y-6">
<div> <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"> <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>Log into your Luma account</li>
<li>Navigate to your account settings</li> <li>Navigate to your account settings</li>
@ -286,15 +319,20 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
</div> </div>
<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"> <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> </p>
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20"> <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" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle> <AlertTitle className="text-[10px] sm:text-xs">Data Privacy</AlertTitle>
<AlertDescription className="text-[9px] sm:text-[10px]"> <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> </AlertDescription>
</Alert> </Alert>
</div> </div>
@ -307,7 +345,8 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
<h3 className="text-sm sm:text-base font-semibold mb-2">Indexing</h3> <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"> <ol className="list-decimal pl-5 space-y-2 text-[10px] sm:text-xs text-muted-foreground mb-4">
<li> <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>
<li> <li>
Place your <strong>API Key</strong> in the form field. Place your <strong>API Key</strong> in the form field.
@ -339,4 +378,3 @@ export const LumaConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

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

View file

@ -52,10 +52,7 @@ const parseCommaSeparated = (value?: string | null) => {
return items.length > 0 ? items : undefined; return items.length > 0 ? items : undefined;
}; };
export const SearxngConnectForm: FC<ConnectFormProps> = ({ export const SearxngConnectForm: FC<ConnectFormProps> = ({ onSubmit, isSubmitting }) => {
onSubmit,
isSubmitting,
}) => {
const isSubmittingRef = useRef(false); const isSubmittingRef = useRef(false);
const form = useForm<SearxngFormValues>({ const form = useForm<SearxngFormValues>({
resolver: zodResolver(searxngFormSchema), 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"> <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 {...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 <FormField
control={form.control} control={form.control}
name="name" name="name"
@ -154,11 +155,11 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel> <FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="My SearxNG Connector" placeholder="My SearxNG Connector"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -176,15 +177,16 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">SearxNG Host</FormLabel> <FormLabel className="text-xs sm:text-sm">SearxNG Host</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="https://searxng.example.org" placeholder="https://searxng.example.org"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -222,11 +224,11 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Engines (optional)</FormLabel> <FormLabel className="text-xs sm:text-sm">Engines (optional)</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="google,bing,duckduckgo" placeholder="google,bing,duckduckgo"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -244,11 +246,11 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Categories (optional)</FormLabel> <FormLabel className="text-xs sm:text-sm">Categories (optional)</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="general,it,science" placeholder="general,it,science"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -266,13 +268,15 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
name="language" name="language"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">Preferred Language (optional)</FormLabel> <FormLabel className="text-xs sm:text-sm">
Preferred Language (optional)
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="en-US" placeholder="en-US"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <FormDescription className="text-[10px] sm:text-xs">
@ -288,17 +292,20 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
name="safesearch" name="safesearch"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel className="text-xs sm:text-sm">SafeSearch Level (optional)</FormLabel> <FormLabel className="text-xs sm:text-sm">
SafeSearch Level (optional)
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder="0 (off), 1 (moderate), 2 (strict)" placeholder="0 (off), 1 (moderate), 2 (strict)"
className="border-slate-400/20 focus-visible:border-slate-400/40" className="border-slate-400/20 focus-visible:border-slate-400/40"
disabled={isSubmitting} disabled={isSubmitting}
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription className="text-[10px] sm:text-xs"> <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> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -318,7 +325,11 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
</FormDescription> </FormDescription>
</div> </div>
<FormControl> <FormControl>
<Switch checked={field.value} onCheckedChange={field.onChange} disabled={isSubmitting} /> <Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={isSubmitting}
/>
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}
@ -341,4 +352,3 @@ export const SearxngConnectForm: FC<ConnectFormProps> = ({
</div> </div>
); );
}; };

View file

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

View file

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

View file

@ -112,4 +112,3 @@ export function getConnectorBenefits(connectorType: string): string[] | null {
return benefits[connectorType] || 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 * Factory function to get the appropriate connect form component for a connector type
*/ */
export function getConnectFormComponent( export function getConnectFormComponent(connectorType: string): ConnectFormComponent | null {
connectorType: string
): ConnectFormComponent | null {
switch (connectorType) { switch (connectorType) {
case "TAVILY_API": case "TAVILY_API":
return TavilyApiConnectForm; return TavilyApiConnectForm;
@ -82,4 +80,3 @@ export function getConnectFormComponent(
return null; return null;
} }
} }

View file

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

View file

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

View file

@ -27,10 +27,7 @@ const circlebackWebhookInfoSchema = z.object({
export type CirclebackWebhookInfo = z.infer<typeof circlebackWebhookInfoSchema>; export type CirclebackWebhookInfo = z.infer<typeof circlebackWebhookInfoSchema>;
export const CirclebackConfig: FC<CirclebackConfigProps> = ({ export const CirclebackConfig: FC<CirclebackConfigProps> = ({ connector, onNameChange }) => {
connector,
onNameChange,
}) => {
const [name, setName] = useState<string>(connector.name || ""); const [name, setName] = useState<string>(connector.name || "");
const [webhookUrl, setWebhookUrl] = useState<string>(""); const [webhookUrl, setWebhookUrl] = useState<string>("");
const [webhookInfo, setWebhookInfo] = useState<CirclebackWebhookInfo | null>(null); const [webhookInfo, setWebhookInfo] = useState<CirclebackWebhookInfo | null>(null);
@ -46,14 +43,14 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({
useEffect(() => { useEffect(() => {
const fetchWebhookInfo = async () => { const fetchWebhookInfo = async () => {
if (!connector.search_space_id) return; if (!connector.search_space_id) return;
const baseUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL; const baseUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL;
if (!baseUrl) { if (!baseUrl) {
console.error("NEXT_PUBLIC_FASTAPI_BACKEND_URL is not configured"); console.error("NEXT_PUBLIC_FASTAPI_BACKEND_URL is not configured");
setIsLoading(false); setIsLoading(false);
return; return;
} }
setIsLoading(true); setIsLoading(true);
try { try {
const response = await authenticatedFetch( 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" /> <Info className="h-3 w-3 sm:h-4 sm:w-4" />
<AlertTitle className="text-xs sm:text-sm">Configuration Instructions</AlertTitle> <AlertTitle className="text-xs sm:text-sm">Configuration Instructions</AlertTitle>
<AlertDescription className="text-[10px] sm:text-xs !pl-0 mt-1"> <AlertDescription className="text-[10px] sm:text-xs !pl-0 mt-1">
Configure this URL in Circleback Settings Automations Create automation Send webhook request. Configure this URL in Circleback Settings Automations Create automation Send
The webhook will automatically send meeting notes, transcripts, and action items to this search space. webhook request. The webhook will automatically send meeting notes, transcripts, and
action items to this search space.
</AlertDescription> </AlertDescription>
</Alert> </Alert>
)} )}
@ -179,4 +177,3 @@ export const CirclebackConfig: FC<CirclebackConfigProps> = ({
</div> </div>
); );
}; };

View file

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

View file

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

View file

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

View file

@ -271,7 +271,9 @@ export const ElasticsearchConfig: FC<ElasticsearchConfigProps> = ({
<div className="h-2.5 w-2.5 rounded-full bg-current" /> <div className="h-2.5 w-2.5 rounded-full bg-current" />
</RadioGroup.Indicator> </RadioGroup.Indicator>
</RadioGroup.Item> </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>
<div className="flex items-center space-x-2"> <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" /> <div className="h-2.5 w-2.5 rounded-full bg-current" />
</RadioGroup.Indicator> </RadioGroup.Indicator>
</RadioGroup.Item> </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> </div>
</RadioGroup.Root> </RadioGroup.Root>
@ -435,4 +439,3 @@ export const ElasticsearchConfig: FC<ElasticsearchConfigProps> = ({
</div> </div>
); );
}; };

View file

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

View file

@ -13,12 +13,10 @@ interface SelectedFolder {
name: string; name: string;
} }
export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({ connector, onConfigChange }) => {
connector,
onConfigChange,
}) => {
// Initialize with existing selected folders and files from connector config // 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 existingFiles = (connector.config?.selected_files as SelectedFolder[] | undefined) || [];
const [selectedFolders, setSelectedFolders] = useState<SelectedFolder[]>(existingFolders); const [selectedFolders, setSelectedFolders] = useState<SelectedFolder[]>(existingFolders);
const [selectedFiles, setSelectedFiles] = useState<SelectedFolder[]>(existingFiles); const [selectedFiles, setSelectedFiles] = useState<SelectedFolder[]>(existingFiles);
@ -63,7 +61,8 @@ export const GoogleDriveConfig: FC<ConnectorConfigProps> = ({
<div className="space-y-1 sm:space-y-2"> <div className="space-y-1 sm:space-y-2">
<h3 className="font-medium text-sm sm:text-base">Folder & File Selection</h3> <h3 className="font-medium text-sm sm:text-base">Folder & File Selection</h3>
<p className="text-xs sm:text-sm text-muted-foreground"> <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> </p>
</div> </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"> <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"> <p className="font-medium">
Selected {totalSelected} item{totalSelected > 1 ? "s" : ""}: Selected {totalSelected} item{totalSelected > 1 ? "s" : ""}:
{selectedFolders.length > 0 && ` ${selectedFolders.length} folder${selectedFolders.length > 1 ? "s" : ""}`} {selectedFolders.length > 0 &&
{selectedFiles.length > 0 && ` ${selectedFiles.length} file${selectedFiles.length > 1 ? "s" : ""}`} ` ${selectedFolders.length} folder${selectedFolders.length > 1 ? "s" : ""}`}
{selectedFiles.length > 0 &&
` ${selectedFiles.length} file${selectedFiles.length > 1 ? "s" : ""}`}
</p> </p>
<div className="max-h-20 sm:max-h-24 overflow-y-auto space-y-1"> <div className="max-h-20 sm:max-h-24 overflow-y-auto space-y-1">
{selectedFolders.map((folder) => ( {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} 📁 {folder.name}
</p> </p>
))} ))}
{selectedFiles.map((file) => ( {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} 📄 {file.name}
</p> </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"> <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" /> <Info className="h-3 w-3 sm:h-4 sm:w-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs !pl-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> </AlertDescription>
</Alert> </Alert>
</div> </div>
); );
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,15 +34,9 @@ export const SearxngConfig: FC<SearxngConfigProps> = ({
onConfigChange, onConfigChange,
onNameChange, onNameChange,
}) => { }) => {
const [host, setHost] = useState<string>( const [host, setHost] = useState<string>((connector.config?.SEARXNG_HOST as 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 [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>( const [categories, setCategories] = useState<string>(
arrayToString(connector.config?.SEARXNG_CATEGORIES) 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" className="border-slate-400/20 focus-visible:border-slate-400/40"
/> />
<p className="text-[10px] sm:text-xs text-muted-foreground"> <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> </p>
</div> </div>
</div> </div>
@ -319,4 +314,3 @@ export const SearxngConfig: FC<SearxngConfigProps> = ({
</div> </div>
); );
}; };

View file

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

View file

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

View file

@ -10,14 +10,11 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import type { ConnectorConfigProps } from "../index"; import type { ConnectorConfigProps } from "../index";
export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({ export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({ connector, onConfigChange }) => {
connector,
onConfigChange,
}) => {
// Initialize with existing config values // Initialize with existing config values
const existingApiKey = (connector.config?.FIRECRAWL_API_KEY as string | undefined) || ""; const existingApiKey = (connector.config?.FIRECRAWL_API_KEY as string | undefined) || "";
const existingUrls = (connector.config?.INITIAL_URLS as string | undefined) || ""; const existingUrls = (connector.config?.INITIAL_URLS as string | undefined) || "";
const [apiKey, setApiKey] = useState(existingApiKey); const [apiKey, setApiKey] = useState(existingApiKey);
const [initialUrls, setInitialUrls] = useState(existingUrls); const [initialUrls, setInitialUrls] = useState(existingUrls);
const [showApiKey, setShowApiKey] = useState(false); const [showApiKey, setShowApiKey] = useState(false);
@ -57,7 +54,8 @@ export const WebcrawlerConfig: FC<ConnectorConfigProps> = ({
<div className="space-y-1 sm:space-y-2"> <div className="space-y-1 sm:space-y-2">
<h3 className="font-medium text-sm sm:text-base">Web Crawler Configuration</h3> <h3 className="font-medium text-sm sm:text-base">Web Crawler Configuration</h3>
<p className="text-xs sm:text-sm text-muted-foreground"> <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> </p>
</div> </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"> <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" /> <Info className="h-3 w-3 sm:h-4 sm:w-4 shrink-0" />
<AlertDescription className="text-[10px] sm:text-xs !pl-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> </AlertDescription>
</Alert> </Alert>
</div> </div>
); );
}; };

View file

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

View file

@ -126,17 +126,17 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
{/* Fixed Footer - Action buttons */} {/* Fixed Footer - Action buttons */}
<div className="flex-shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-muted border-t border-border"> <div className="flex-shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-muted border-t border-border">
<Button <Button
variant="ghost" variant="ghost"
onClick={onBack} onClick={onBack}
disabled={isSubmitting} disabled={isSubmitting}
className="text-xs sm:text-sm" className="text-xs sm:text-sm"
> >
Cancel Cancel
</Button> </Button>
<Button <Button
onClick={handleFormSubmit} onClick={handleFormSubmit}
disabled={isSubmitting} disabled={isSubmitting}
className="text-xs sm:text-sm min-w-[140px] disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none" className="text-xs sm:text-sm min-w-[140px] disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none"
> >
{isSubmitting ? ( {isSubmitting ? (
@ -145,13 +145,10 @@ export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
Connecting... Connecting...
</> </>
) : ( ) : (
<> <>Connect {getConnectorTypeDisplay(connectorType)}</>
Connect {getConnectorTypeDisplay(connectorType)}
</>
)} )}
</Button> </Button>
</div> </div>
</div> </div>
); );
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,10 +22,7 @@ interface YouTubeCrawlerViewProps {
onBack: () => void; onBack: () => void;
} }
export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({ searchSpaceId, onBack }) => {
searchSpaceId,
onBack,
}) => {
const t = useTranslations("add_youtube"); const t = useTranslations("add_youtube");
const router = useRouter(); const router = useRouter();
const [videoTags, setVideoTags] = useState<TagType[]>([]); const [videoTags, setVideoTags] = useState<TagType[]>([]);
@ -133,12 +130,8 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({
{getConnectorIcon(EnumConnectorName.YOUTUBE_CONNECTOR, "h-7 w-7")} {getConnectorIcon(EnumConnectorName.YOUTUBE_CONNECTOR, "h-7 w-7")}
</div> </div>
<div> <div>
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight"> <h2 className="text-xl sm:text-2xl font-semibold tracking-tight">{t("title")}</h2>
{t("title")} <p className="text-xs sm:text-base text-muted-foreground mt-1">{t("subtitle")}</p>
</h2>
<p className="text-xs sm:text-base text-muted-foreground mt-1">
{t("subtitle")}
</p>
</div> </div>
</div> </div>
</div> </div>
@ -159,7 +152,8 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({
styleClasses={{ styleClasses={{
inlineTagsContainer: 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", "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: { tag: {
body: "h-7 relative bg-background border border-input hover:bg-background rounded-md font-medium text-xs ps-2 pe-7 flex", body: "h-7 relative bg-background border border-input hover:bg-background rounded-md font-medium text-xs ps-2 pe-7 flex",
closeButton: closeButton:
@ -172,11 +166,7 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({
<p className="text-xs text-muted-foreground mt-1">{t("hint")}</p> <p className="text-xs text-muted-foreground mt-1">{t("hint")}</p>
</div> </div>
{error && ( {error && <div className="text-sm text-red-500 mt-2">{error}</div>}
<div className="text-sm text-red-500 mt-2">
{error}
</div>
)}
<div className="bg-muted/50 rounded-lg p-4 text-sm"> <div className="bg-muted/50 rounded-lg p-4 text-sm">
<h4 className="font-medium mb-2">{t("tips_title")}</h4> <h4 className="font-medium mb-2">{t("tips_title")}</h4>
@ -244,4 +234,3 @@ export const YouTubeCrawlerView: FC<YouTubeCrawlerViewProps> = ({
</div> </div>
); );
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -223,9 +223,17 @@ export function GoogleDriveFolderTree({
const childFiles = children?.filter((c) => !c.isFolder) || []; const childFiles = children?.filter((c) => !c.isFolder) || [];
const indentSize = 0.75; // Smaller indent for mobile const indentSize = 0.75; // Smaller indent for mobile
return ( 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 <div
className={cn( 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", "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))} {childFiles.map((child) => renderItem(child, level + 1))}
{children.length === 0 && ( {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> </div>
)} )}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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