mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-12 20:45:20 +02:00
feat: Add Tavily API connector support, including configuration forms and connection handling, enhance connector dialog with non-OAuth connection logic, and improve state management for connector creation.
This commit is contained in:
parent
5d1859db17
commit
880b3cc4bf
11 changed files with 711 additions and 86 deletions
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
import { useAtomValue } from "jotai";
|
||||
import { Cable, Loader2 } from "lucide-react";
|
||||
import { type FC, useMemo } from "react";
|
||||
import { type FC, useMemo, useEffect } from "react";
|
||||
import { documentTypeCountsAtom } from "@/atoms/documents/document-query.atoms";
|
||||
import { useLogsSummary } from "@/hooks/use-logs";
|
||||
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
|
||||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { cacheKeys } from "@/lib/query-client/cache-keys";
|
||||
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
|
@ -21,16 +23,13 @@ import { AllConnectorsTab } from "./connector-popup/tabs/all-connectors-tab";
|
|||
import { ActiveConnectorsTab } from "./connector-popup/tabs/active-connectors-tab";
|
||||
import { ConnectorDialogHeader } from "./connector-popup/components/connector-dialog-header";
|
||||
import { ConnectorEditView } from "./connector-popup/connector-configs/views/connector-edit-view";
|
||||
import { ConnectorConnectView } from "./connector-popup/connector-configs/views/connector-connect-view";
|
||||
import { IndexingConfigurationView } from "./connector-popup/connector-configs/views/indexing-configuration-view";
|
||||
import { useConnectorDialog } from "./connector-popup/hooks/use-connector-dialog";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
|
||||
export const ConnectorIndicator: FC = () => {
|
||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
||||
const { connectors, isLoading: connectorsLoading, refreshConnectors } = useSearchSourceConnectors(
|
||||
false,
|
||||
searchSpaceId ? Number(searchSpaceId) : undefined
|
||||
);
|
||||
const { data: documentTypeCounts, isLoading: documentTypesLoading } =
|
||||
useAtomValue(documentTypeCountsAtom);
|
||||
|
||||
|
|
@ -44,22 +43,6 @@ export const ConnectorIndicator: FC = () => {
|
|||
}
|
||||
);
|
||||
|
||||
// Get connector IDs that are currently being indexed
|
||||
const indexingConnectorIds = useMemo(() => {
|
||||
if (!logsSummary?.active_tasks) return new Set<number>();
|
||||
return new Set(
|
||||
logsSummary.active_tasks
|
||||
.filter((task) => task.source?.includes("connector_indexing"))
|
||||
.map((task) => {
|
||||
const match = task.source?.match(/connector[_-]?(\d+)/i);
|
||||
return match ? parseInt(match[1], 10) : null;
|
||||
})
|
||||
.filter((id): id is number => id !== null)
|
||||
);
|
||||
}, [logsSummary?.active_tasks]);
|
||||
|
||||
const isLoading = connectorsLoading || documentTypesLoading;
|
||||
|
||||
// Use the custom hook for dialog state management
|
||||
const {
|
||||
isOpen,
|
||||
|
|
@ -71,6 +54,8 @@ export const ConnectorIndicator: FC = () => {
|
|||
indexingConnector,
|
||||
indexingConnectorConfig,
|
||||
editingConnector,
|
||||
connectingConnectorType,
|
||||
isCreatingConnector,
|
||||
startDate,
|
||||
endDate,
|
||||
isStartingIndexing,
|
||||
|
|
@ -88,19 +73,81 @@ export const ConnectorIndicator: FC = () => {
|
|||
handleTabChange,
|
||||
handleScroll,
|
||||
handleConnectOAuth,
|
||||
handleConnectNonOAuth,
|
||||
handleCreateWebcrawler,
|
||||
handleCreateYouTube,
|
||||
handleSubmitConnectForm,
|
||||
handleStartIndexing,
|
||||
handleSkipIndexing,
|
||||
handleStartEdit,
|
||||
handleSaveConnector,
|
||||
handleDisconnectConnector,
|
||||
handleBackFromEdit,
|
||||
handleBackFromConnect,
|
||||
connectorConfig,
|
||||
setConnectorConfig,
|
||||
setIndexingConnectorConfig,
|
||||
setConnectorName,
|
||||
} = useConnectorDialog();
|
||||
|
||||
// Fetch connectors using React Query with conditional refetchInterval
|
||||
// This automatically refetches when mutations invalidate the cache (event-driven)
|
||||
// and also polls when dialog is open to catch external changes
|
||||
const {
|
||||
data: connectors = [],
|
||||
isLoading: connectorsLoading,
|
||||
refetch: refreshConnectors,
|
||||
} = useQuery({
|
||||
queryKey: cacheKeys.connectors.all(searchSpaceId || ""),
|
||||
queryFn: () =>
|
||||
connectorsApiService.getConnectors({
|
||||
queryParams: {
|
||||
search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined,
|
||||
},
|
||||
}),
|
||||
enabled: !!searchSpaceId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes (same as connectorsAtom)
|
||||
// Poll when dialog is open to catch external changes
|
||||
refetchInterval: isOpen ? 5000 : false, // 5 seconds when open, no polling when closed
|
||||
});
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Also refresh document type counts when dialog is open
|
||||
useEffect(() => {
|
||||
if (!isOpen || !searchSpaceId) return;
|
||||
|
||||
const POLL_INTERVAL = 5000; // 5 seconds, same as connectors
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
// Invalidate document type counts to refresh active document types
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: cacheKeys.documents.typeCounts(searchSpaceId),
|
||||
});
|
||||
}, POLL_INTERVAL);
|
||||
|
||||
// Cleanup interval on unmount or when dialog closes
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, [isOpen, searchSpaceId, queryClient]);
|
||||
|
||||
// Get connector IDs that are currently being indexed
|
||||
const indexingConnectorIds = useMemo(() => {
|
||||
if (!logsSummary?.active_tasks) return new Set<number>();
|
||||
return new Set(
|
||||
logsSummary.active_tasks
|
||||
.filter((task) => task.source?.includes("connector_indexing"))
|
||||
.map((task) => {
|
||||
const match = task.source?.match(/connector[_-]?(\d+)/i);
|
||||
return match ? parseInt(match[1], 10) : null;
|
||||
})
|
||||
.filter((id): id is number => id !== null)
|
||||
);
|
||||
}, [logsSummary?.active_tasks]);
|
||||
|
||||
const isLoading = connectorsLoading || documentTypesLoading;
|
||||
|
||||
// Get document types that have documents in the search space
|
||||
const activeDocumentTypes = documentTypeCounts
|
||||
? Object.entries(documentTypeCounts).filter(([, count]) => count > 0)
|
||||
|
|
@ -148,12 +195,20 @@ export const ConnectorIndicator: FC = () => {
|
|||
</TooltipIconButton>
|
||||
|
||||
<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">
|
||||
{/* Connector Edit View - shown when editing existing connector */}
|
||||
{editingConnector ? (
|
||||
{/* Connector Connect View - shown when connecting non-OAuth connectors */}
|
||||
{connectingConnectorType ? (
|
||||
<ConnectorConnectView
|
||||
connectorType={connectingConnectorType}
|
||||
onSubmit={handleSubmitConnectForm}
|
||||
onBack={handleBackFromConnect}
|
||||
isSubmitting={isCreatingConnector}
|
||||
/>
|
||||
) : editingConnector ? (
|
||||
<ConnectorEditView
|
||||
connector={{
|
||||
...editingConnector,
|
||||
config: connectorConfig || editingConnector.config,
|
||||
name: editingConnector.name,
|
||||
}}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
|
|
@ -165,10 +220,11 @@ export const ConnectorIndicator: FC = () => {
|
|||
onEndDateChange={setEndDate}
|
||||
onPeriodicEnabledChange={setPeriodicEnabled}
|
||||
onFrequencyChange={setFrequencyMinutes}
|
||||
onSave={() => handleSaveConnector(refreshConnectors)}
|
||||
onDisconnect={() => handleDisconnectConnector(refreshConnectors)}
|
||||
onSave={() => handleSaveConnector(() => refreshConnectors())}
|
||||
onDisconnect={() => handleDisconnectConnector(() => refreshConnectors())}
|
||||
onBack={handleBackFromEdit}
|
||||
onConfigChange={setConnectorConfig}
|
||||
onNameChange={setConnectorName}
|
||||
/>
|
||||
) : indexingConfig ? (
|
||||
<IndexingConfigurationView
|
||||
|
|
@ -187,7 +243,7 @@ export const ConnectorIndicator: FC = () => {
|
|||
onPeriodicEnabledChange={setPeriodicEnabled}
|
||||
onFrequencyChange={setFrequencyMinutes}
|
||||
onConfigChange={setIndexingConnectorConfig}
|
||||
onStartIndexing={() => handleStartIndexing(refreshConnectors)}
|
||||
onStartIndexing={() => handleStartIndexing(() => refreshConnectors())}
|
||||
onSkip={handleSkipIndexing}
|
||||
/>
|
||||
) : (
|
||||
|
|
@ -207,17 +263,18 @@ export const ConnectorIndicator: FC = () => {
|
|||
<div className="h-full overflow-y-auto" onScroll={handleScroll}>
|
||||
<div className="px-6 sm:px-12 py-6 sm:py-8 pb-16 sm:pb-16">
|
||||
<TabsContent value="all" className="m-0">
|
||||
<AllConnectorsTab
|
||||
searchQuery={searchQuery}
|
||||
searchSpaceId={searchSpaceId}
|
||||
connectedTypes={connectedTypes}
|
||||
connectingId={connectingId}
|
||||
allConnectors={allConnectors}
|
||||
onConnectOAuth={handleConnectOAuth}
|
||||
onCreateWebcrawler={handleCreateWebcrawler}
|
||||
onCreateYouTube={handleCreateYouTube}
|
||||
onManage={handleStartEdit}
|
||||
/>
|
||||
<AllConnectorsTab
|
||||
searchQuery={searchQuery}
|
||||
searchSpaceId={searchSpaceId}
|
||||
connectedTypes={connectedTypes}
|
||||
connectingId={connectingId}
|
||||
allConnectors={allConnectors}
|
||||
onConnectOAuth={handleConnectOAuth}
|
||||
onConnectNonOAuth={handleConnectNonOAuth}
|
||||
onCreateWebcrawler={handleCreateWebcrawler}
|
||||
onCreateYouTube={handleCreateYouTube}
|
||||
onManage={handleStartEdit}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<ActiveConnectorsTab
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
"use client";
|
||||
|
||||
import { KeyRound } from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import type { FC } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import type { ConnectorConfigProps } from "../index";
|
||||
|
||||
export interface TavilyApiConfigProps extends ConnectorConfigProps {
|
||||
onNameChange?: (name: string) => void;
|
||||
}
|
||||
|
||||
export const TavilyApiConfig: FC<TavilyApiConfigProps> = ({
|
||||
connector,
|
||||
onConfigChange,
|
||||
onNameChange,
|
||||
}) => {
|
||||
const [apiKey, setApiKey] = useState<string>(
|
||||
(connector.config?.TAVILY_API_KEY as string) || ""
|
||||
);
|
||||
const [name, setName] = useState<string>(connector.name || "");
|
||||
|
||||
// Update API key and name when connector changes
|
||||
useEffect(() => {
|
||||
const key = (connector.config?.TAVILY_API_KEY as string) || "";
|
||||
setApiKey(key);
|
||||
setName(connector.name || "");
|
||||
}, [connector.config, connector.name]);
|
||||
|
||||
const handleApiKeyChange = (value: string) => {
|
||||
setApiKey(value);
|
||||
if (onConfigChange) {
|
||||
onConfigChange({
|
||||
...connector.config,
|
||||
TAVILY_API_KEY: value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleNameChange = (value: string) => {
|
||||
setName(value);
|
||||
if (onNameChange) {
|
||||
onNameChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Connector Name */}
|
||||
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs sm:text-sm">Connector Name</Label>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => handleNameChange(e.target.value)}
|
||||
placeholder="My Tavily API Connector"
|
||||
className="border-slate-400/20 focus-visible:border-slate-400/40"
|
||||
/>
|
||||
<p className="text-[10px] sm:text-xs text-muted-foreground">
|
||||
A friendly name to identify this connector.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Configuration */}
|
||||
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
|
||||
<div className="space-y-1 sm:space-y-2">
|
||||
<h3 className="font-medium text-sm sm:text-base">Configuration</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="flex items-center gap-2 text-xs sm:text-sm">
|
||||
<KeyRound className="h-4 w-4" />
|
||||
Tavily API Key
|
||||
</Label>
|
||||
<Input
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onChange={(e) => handleApiKeyChange(e.target.value)}
|
||||
placeholder="Enter your Tavily API key"
|
||||
className="border-slate-400/20 focus-visible:border-slate-400/40"
|
||||
/>
|
||||
<p className="text-[10px] sm:text-xs text-muted-foreground">
|
||||
Update the Tavily API Key if needed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import type { FC } from "react";
|
||||
import { TavilyApiConnectForm } from "./tavily-api-connect-form";
|
||||
|
||||
export interface ConnectFormProps {
|
||||
onSubmit: (data: {
|
||||
name: string;
|
||||
connector_type: string;
|
||||
config: Record<string, unknown>;
|
||||
is_indexable: boolean;
|
||||
last_indexed_at: null;
|
||||
periodic_indexing_enabled: boolean;
|
||||
indexing_frequency_minutes: null;
|
||||
next_scheduled_at: null;
|
||||
}) => Promise<void>;
|
||||
onBack: () => void;
|
||||
isSubmitting: boolean;
|
||||
onFormSubmit?: () => void;
|
||||
}
|
||||
|
||||
export type ConnectFormComponent = FC<ConnectFormProps>;
|
||||
|
||||
/**
|
||||
* Factory function to get the appropriate connect form component for a connector type
|
||||
*/
|
||||
export function getConnectFormComponent(
|
||||
connectorType: string
|
||||
): ConnectFormComponent | null {
|
||||
switch (connectorType) {
|
||||
case "TAVILY_API":
|
||||
return TavilyApiConnectForm;
|
||||
// Add other connector types here as needed
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Info } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { useRef } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import type { ConnectFormProps } from "./index";
|
||||
|
||||
const tavilyApiFormSchema = z.object({
|
||||
name: z.string().min(3, {
|
||||
message: "Connector name must be at least 3 characters.",
|
||||
}),
|
||||
api_key: z.string().min(10, {
|
||||
message: "API key is required and must be valid.",
|
||||
}),
|
||||
});
|
||||
|
||||
type TavilyApiFormValues = z.infer<typeof tavilyApiFormSchema>;
|
||||
|
||||
export const TavilyApiConnectForm: FC<ConnectFormProps> = ({
|
||||
onSubmit,
|
||||
isSubmitting,
|
||||
}) => {
|
||||
const isSubmittingRef = useRef(false);
|
||||
const form = useForm<TavilyApiFormValues>({
|
||||
resolver: zodResolver(tavilyApiFormSchema),
|
||||
defaultValues: {
|
||||
name: "Tavily API Connector",
|
||||
api_key: "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = async (values: TavilyApiFormValues) => {
|
||||
// Prevent multiple submissions
|
||||
if (isSubmittingRef.current || isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmittingRef.current = true;
|
||||
try {
|
||||
await onSubmit({
|
||||
name: values.name,
|
||||
connector_type: EnumConnectorName.TAVILY_API,
|
||||
config: {
|
||||
TAVILY_API_KEY: values.api_key,
|
||||
},
|
||||
is_indexable: false,
|
||||
last_indexed_at: null,
|
||||
periodic_indexing_enabled: false,
|
||||
indexing_frequency_minutes: null,
|
||||
next_scheduled_at: null,
|
||||
});
|
||||
} finally {
|
||||
isSubmittingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 pb-6">
|
||||
<Alert className="bg-slate-400/5 dark:bg-white/5 border-slate-400/20 p-2 sm:p-3 flex items-center [&>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 ml-1" />
|
||||
<div className="-ml-1">
|
||||
<AlertTitle className="text-xs sm:text-sm">API Key Required</AlertTitle>
|
||||
<AlertDescription className="text-[10px] sm:text-xs !pl-0">
|
||||
You'll need a Tavily API key to use this connector. You can get one by signing up at{" "}
|
||||
<a
|
||||
href="https://tavily.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium underline underline-offset-4"
|
||||
>
|
||||
tavily.com
|
||||
</a>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<div className="rounded-xl border border-border bg-slate-400/5 dark:bg-white/5 p-3 sm:p-6 space-y-3 sm:space-y-4">
|
||||
<Form {...form}>
|
||||
<form id="tavily-connect-form" onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4 sm:space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-xs sm:text-sm">Connector Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My Tavily API Connector"
|
||||
className="border-slate-400/20 focus-visible:border-slate-400/40"
|
||||
disabled={isSubmitting}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription className="text-[10px] sm:text-xs">
|
||||
A friendly name to identify this connector.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="api_key"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-xs sm:text-sm">Tavily API Key</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter your Tavily API key"
|
||||
className="border-slate-400/20 focus-visible:border-slate-400/40"
|
||||
disabled={isSubmitting}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription className="text-[10px] sm:text-xs">
|
||||
Your API key will be encrypted and stored securely.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -3,12 +3,14 @@
|
|||
import type { FC } from "react";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { GoogleDriveConfig } from "./components/google-drive-config";
|
||||
import { TavilyApiConfig } from "./components/tavily-api-config";
|
||||
import { WebcrawlerConfig } from "./components/webcrawler-config";
|
||||
import { YouTubeConfig } from "./components/youtube-config";
|
||||
|
||||
export interface ConnectorConfigProps {
|
||||
connector: SearchSourceConnector;
|
||||
onConfigChange?: (config: Record<string, unknown>) => void;
|
||||
onNameChange?: (name: string) => void;
|
||||
}
|
||||
|
||||
export type ConnectorConfigComponent = FC<ConnectorConfigProps>;
|
||||
|
|
@ -22,6 +24,8 @@ export function getConnectorConfigComponent(
|
|||
switch (connectorType) {
|
||||
case "GOOGLE_DRIVE_CONNECTOR":
|
||||
return GoogleDriveConfig;
|
||||
case "TAVILY_API":
|
||||
return TavilyApiConfig;
|
||||
case "WEBCRAWLER_CONNECTOR":
|
||||
return WebcrawlerConfig;
|
||||
case "YOUTUBE_CONNECTOR":
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
"use client";
|
||||
|
||||
import { ArrowLeft, Loader2 } from "lucide-react";
|
||||
import { type FC, useMemo } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import { EnumConnectorName } from "@/contracts/enums/connector";
|
||||
import { getConnectorTypeDisplay } from "@/lib/connectors/utils";
|
||||
import { getConnectFormComponent } from "../connect-forms";
|
||||
|
||||
interface ConnectorConnectViewProps {
|
||||
connectorType: string;
|
||||
onSubmit: (data: {
|
||||
name: string;
|
||||
connector_type: string;
|
||||
config: Record<string, unknown>;
|
||||
is_indexable: boolean;
|
||||
last_indexed_at: null;
|
||||
periodic_indexing_enabled: boolean;
|
||||
indexing_frequency_minutes: null;
|
||||
next_scheduled_at: null;
|
||||
}) => Promise<void>;
|
||||
onBack: () => void;
|
||||
isSubmitting: boolean;
|
||||
}
|
||||
|
||||
export const ConnectorConnectView: FC<ConnectorConnectViewProps> = ({
|
||||
connectorType,
|
||||
onSubmit,
|
||||
onBack,
|
||||
isSubmitting,
|
||||
}) => {
|
||||
// Get connector-specific form component
|
||||
const ConnectFormComponent = useMemo(
|
||||
() => getConnectFormComponent(connectorType),
|
||||
[connectorType]
|
||||
);
|
||||
|
||||
const handleFormSubmit = () => {
|
||||
// Prevent multiple submissions
|
||||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
const form = document.getElementById("tavily-connect-form") as HTMLFormElement;
|
||||
if (form) {
|
||||
form.requestSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
if (!ConnectFormComponent) {
|
||||
return (
|
||||
<div className="flex-1 flex flex-col min-h-0 overflow-hidden p-6">
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Connector form not found for type: {connectorType}
|
||||
</p>
|
||||
<Button onClick={onBack} variant="ghost">
|
||||
Back
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex-shrink-0 px-6 sm:px-12 pt-8 sm:pt-10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBack}
|
||||
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground mb-6 w-fit"
|
||||
>
|
||||
<ArrowLeft className="size-4" />
|
||||
Back to connectors
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-xl border border-slate-400/30">
|
||||
{getConnectorIcon(connectorType as EnumConnectorName, "h-7 w-7")}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl sm:text-2xl font-semibold tracking-tight">
|
||||
Connect {connectorType === "TAVILY_API" ? "Tavily API" : connectorType}
|
||||
</h2>
|
||||
<p className="text-xs sm:text-base text-muted-foreground mt-1">
|
||||
Enter your connection details
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form Content - Scrollable */}
|
||||
<div className="flex-1 min-h-0 overflow-y-auto px-6 sm:px-12">
|
||||
<ConnectFormComponent
|
||||
onSubmit={onSubmit}
|
||||
onBack={onBack}
|
||||
isSubmitting={isSubmitting}
|
||||
onFormSubmit={handleFormSubmit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Fixed Footer - Action buttons */}
|
||||
<div className="flex-shrink-0 flex items-center justify-between px-6 sm:px-12 py-6 bg-muted border-t border-border">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onBack}
|
||||
disabled={isSubmitting}
|
||||
className="text-xs sm:text-sm"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleFormSubmit}
|
||||
disabled={isSubmitting}
|
||||
className="text-xs sm:text-sm min-w-[140px] disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Connecting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Connect {getConnectorTypeDisplay(connectorType)}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -26,6 +26,7 @@ interface ConnectorEditViewProps {
|
|||
onDisconnect: () => void;
|
||||
onBack: () => void;
|
||||
onConfigChange?: (config: Record<string, any>) => void;
|
||||
onNameChange?: (name: string) => void;
|
||||
}
|
||||
|
||||
export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
||||
|
|
@ -44,6 +45,7 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
onDisconnect,
|
||||
onBack,
|
||||
onConfigChange,
|
||||
onNameChange,
|
||||
}) => {
|
||||
// Get connector-specific config component
|
||||
const ConnectorConfigComponent = useMemo(
|
||||
|
|
@ -146,25 +148,31 @@ export const ConnectorEditView: FC<ConnectorEditViewProps> = ({
|
|||
<ConnectorConfigComponent
|
||||
connector={connector}
|
||||
onConfigChange={onConfigChange}
|
||||
onNameChange={onNameChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Date range selector - not shown for Google Drive (uses folder selection), Webcrawler (uses config), or YouTube (uses URL selection) */}
|
||||
{connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && connector.connector_type !== "WEBCRAWLER_CONNECTOR" && connector.connector_type !== "YOUTUBE_CONNECTOR" && (
|
||||
<DateRangeSelector
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onStartDateChange={onStartDateChange}
|
||||
onEndDateChange={onEndDateChange}
|
||||
/>
|
||||
)}
|
||||
{/* Date range selector and periodic sync - only shown for indexable connectors */}
|
||||
{connector.is_indexable && (
|
||||
<>
|
||||
{/* Date range selector - not shown for Google Drive (uses folder selection), Webcrawler (uses config), or YouTube (uses URL selection) */}
|
||||
{connector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && connector.connector_type !== "WEBCRAWLER_CONNECTOR" && connector.connector_type !== "YOUTUBE_CONNECTOR" && (
|
||||
<DateRangeSelector
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onStartDateChange={onStartDateChange}
|
||||
onEndDateChange={onEndDateChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
<PeriodicSyncConfig
|
||||
enabled={periodicEnabled}
|
||||
frequencyMinutes={frequencyMinutes}
|
||||
onEnabledChange={onPeriodicEnabledChange}
|
||||
onFrequencyChange={onFrequencyChange}
|
||||
/>
|
||||
<PeriodicSyncConfig
|
||||
enabled={periodicEnabled}
|
||||
frequencyMinutes={frequencyMinutes}
|
||||
onEnabledChange={onPeriodicEnabledChange}
|
||||
onFrequencyChange={onFrequencyChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Info box */}
|
||||
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3">
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
|
||||
import { type FC, useState, useCallback, useRef, useEffect, useMemo } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
|
||||
import type { SearchSourceConnector } from "@/contracts/types/connector.types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { IndexingConfigState } from "../../constants/connector-constants";
|
||||
|
|
@ -134,22 +133,27 @@ export const IndexingConfigurationView: FC<IndexingConfigurationViewProps> = ({
|
|||
/>
|
||||
)}
|
||||
|
||||
{/* Date range selector - not shown for Google Drive (uses folder selection), Webcrawler (uses config), or YouTube (uses URL selection) */}
|
||||
{config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && config.connectorType !== "WEBCRAWLER_CONNECTOR" && config.connectorType !== "YOUTUBE_CONNECTOR" && (
|
||||
<DateRangeSelector
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onStartDateChange={onStartDateChange}
|
||||
onEndDateChange={onEndDateChange}
|
||||
/>
|
||||
)}
|
||||
{/* Date range selector and periodic sync - only shown for indexable connectors */}
|
||||
{connector?.is_indexable && (
|
||||
<>
|
||||
{/* Date range selector - not shown for Google Drive (uses folder selection), Webcrawler (uses config), or YouTube (uses URL selection) */}
|
||||
{config.connectorType !== "GOOGLE_DRIVE_CONNECTOR" && config.connectorType !== "WEBCRAWLER_CONNECTOR" && config.connectorType !== "YOUTUBE_CONNECTOR" && (
|
||||
<DateRangeSelector
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
onStartDateChange={onStartDateChange}
|
||||
onEndDateChange={onEndDateChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
<PeriodicSyncConfig
|
||||
enabled={periodicEnabled}
|
||||
frequencyMinutes={frequencyMinutes}
|
||||
onEnabledChange={onPeriodicEnabledChange}
|
||||
onFrequencyChange={onFrequencyChange}
|
||||
/>
|
||||
<PeriodicSyncConfig
|
||||
enabled={periodicEnabled}
|
||||
frequencyMinutes={frequencyMinutes}
|
||||
onEnabledChange={onPeriodicEnabledChange}
|
||||
onFrequencyChange={onFrequencyChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Info box */}
|
||||
<div className="rounded-xl border border-border bg-primary/5 p-4 flex items-start gap-3">
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ import { searchSourceConnectorTypeEnum } from "@/contracts/types/connector.types
|
|||
export const connectorPopupQueryParamsSchema = z.object({
|
||||
modal: z.enum(["connectors"]).optional(),
|
||||
tab: z.enum(["all", "active"]).optional(),
|
||||
view: z.enum(["configure", "edit"]).optional(),
|
||||
view: z.enum(["configure", "edit", "connect"]).optional(),
|
||||
connector: z.string().optional(),
|
||||
connectorId: z.string().optional(),
|
||||
connectorType: z.string().optional(),
|
||||
success: z.enum(["true", "false"]).optional(),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useAtomValue } from "jotai";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { createConnectorMutationAtom, deleteConnectorMutationAtom, indexConnectorMutationAtom, updateConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
|
||||
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
|
||||
|
|
@ -51,6 +51,12 @@ export const useConnectorDialog = () => {
|
|||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isDisconnecting, setIsDisconnecting] = useState(false);
|
||||
const [connectorConfig, setConnectorConfig] = useState<Record<string, unknown> | null>(null);
|
||||
const [connectorName, setConnectorName] = useState<string | null>(null);
|
||||
|
||||
// Connect mode state (for non-OAuth connectors)
|
||||
const [connectingConnectorType, setConnectingConnectorType] = useState<string | null>(null);
|
||||
const [isCreatingConnector, setIsCreatingConnector] = useState(false);
|
||||
const isCreatingConnectorRef = useRef(false);
|
||||
|
||||
// Helper function to get frequency label
|
||||
const getFrequencyLabel = useCallback((minutes: string): string => {
|
||||
|
|
@ -85,6 +91,17 @@ export const useConnectorDialog = () => {
|
|||
// Clear editing connector if view is not "edit" anymore
|
||||
if (params.view !== "edit" && editingConnector) {
|
||||
setEditingConnector(null);
|
||||
setConnectorName(null);
|
||||
}
|
||||
|
||||
// Clear connecting connector type if view is not "connect" anymore
|
||||
if (params.view !== "connect" && connectingConnectorType) {
|
||||
setConnectingConnectorType(null);
|
||||
}
|
||||
|
||||
// Handle connect view
|
||||
if (params.view === "connect" && params.connectorType && !connectingConnectorType) {
|
||||
setConnectingConnectorType(params.connectorType);
|
||||
}
|
||||
|
||||
if (params.view === "configure" && params.connector && !indexingConfig) {
|
||||
|
|
@ -147,6 +164,7 @@ export const useConnectorDialog = () => {
|
|||
// Clear editing connector when modal is closed
|
||||
if (editingConnector) {
|
||||
setEditingConnector(null);
|
||||
setConnectorName(null);
|
||||
setConnectorConfig(null);
|
||||
setStartDate(undefined);
|
||||
setEndDate(undefined);
|
||||
|
|
@ -155,13 +173,17 @@ export const useConnectorDialog = () => {
|
|||
setIsScrolled(false);
|
||||
setSearchQuery("");
|
||||
}
|
||||
// Clear connecting connector type when modal is closed
|
||||
if (connectingConnectorType) {
|
||||
setConnectingConnectorType(null);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Invalid query params - log but don't crash
|
||||
console.warn("Invalid connector popup query params:", error);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchParams, allConnectors, editingConnector, indexingConfig]);
|
||||
}, [searchParams, allConnectors, editingConnector, indexingConfig, connectingConnectorType]);
|
||||
|
||||
// Detect OAuth success and transition to config view
|
||||
useEffect(() => {
|
||||
|
|
@ -358,6 +380,106 @@ export const useConnectorDialog = () => {
|
|||
}
|
||||
}, [searchSpaceId, createConnector, refetchAllConnectors]);
|
||||
|
||||
// Handle connecting non-OAuth connectors (like Tavily API)
|
||||
const handleConnectNonOAuth = useCallback((connectorType: string) => {
|
||||
if (!searchSpaceId) return;
|
||||
|
||||
// Set connecting state
|
||||
setConnectingConnectorType(connectorType);
|
||||
|
||||
// Update URL to show connect view
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("modal", "connectors");
|
||||
url.searchParams.set("view", "connect");
|
||||
url.searchParams.set("connectorType", connectorType);
|
||||
window.history.pushState({ modal: true }, "", url.toString());
|
||||
}, [searchSpaceId]);
|
||||
|
||||
// Handle submitting connect form
|
||||
const handleSubmitConnectForm = useCallback(async (
|
||||
formData: {
|
||||
name: string;
|
||||
connector_type: string;
|
||||
config: Record<string, unknown>;
|
||||
is_indexable: boolean;
|
||||
last_indexed_at: null;
|
||||
periodic_indexing_enabled: boolean;
|
||||
indexing_frequency_minutes: null;
|
||||
next_scheduled_at: null;
|
||||
}
|
||||
) => {
|
||||
if (!searchSpaceId || !connectingConnectorType) return;
|
||||
|
||||
// Prevent multiple submissions using ref for immediate check
|
||||
if (isCreatingConnectorRef.current) return;
|
||||
isCreatingConnectorRef.current = true;
|
||||
|
||||
setIsCreatingConnector(true);
|
||||
try {
|
||||
// Create connector
|
||||
const newConnector = await createConnector({
|
||||
data: formData,
|
||||
queryParams: {
|
||||
search_space_id: searchSpaceId,
|
||||
},
|
||||
});
|
||||
|
||||
// Refetch connectors to get the new one
|
||||
const result = await refetchAllConnectors();
|
||||
if (result.data) {
|
||||
const connector = result.data.find(
|
||||
(c: SearchSourceConnector) => c.id === newConnector.id
|
||||
);
|
||||
if (connector) {
|
||||
// Validate connector data
|
||||
const connectorValidation = searchSourceConnector.safeParse(connector);
|
||||
if (connectorValidation.success) {
|
||||
// Find connector title from constants
|
||||
const connectorInfo = OTHER_CONNECTORS.find(
|
||||
c => c.connectorType === connectingConnectorType
|
||||
);
|
||||
const connectorTitle = connectorInfo?.title || connector.name;
|
||||
|
||||
// Set up indexing config
|
||||
const config = validateIndexingConfigState({
|
||||
connectorType: connectingConnectorType as EnumConnectorName,
|
||||
connectorId: connector.id,
|
||||
connectorTitle,
|
||||
});
|
||||
setIndexingConfig(config);
|
||||
setIndexingConnector(connector);
|
||||
setIndexingConnectorConfig(connector.config || {});
|
||||
|
||||
// Transition to configure view
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("view", "configure");
|
||||
url.searchParams.delete("connectorType");
|
||||
window.history.replaceState({}, "", url.toString());
|
||||
|
||||
toast.success(`${connectorTitle} connected successfully!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating connector:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Failed to create connector");
|
||||
} finally {
|
||||
isCreatingConnectorRef.current = false;
|
||||
setIsCreatingConnector(false);
|
||||
setConnectingConnectorType(null);
|
||||
}
|
||||
}, [connectingConnectorType, searchSpaceId, createConnector, refetchAllConnectors]);
|
||||
|
||||
// Handle going back from connect view
|
||||
const handleBackFromConnect = useCallback(() => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("modal", "connectors");
|
||||
url.searchParams.set("tab", "all");
|
||||
url.searchParams.delete("view");
|
||||
url.searchParams.delete("connectorType");
|
||||
router.replace(url.pathname + url.search, { scroll: false });
|
||||
}, [router]);
|
||||
|
||||
// Handle starting indexing
|
||||
const handleStartIndexing = useCallback(async (refreshConnectors: () => void) => {
|
||||
if (!indexingConfig || !searchSpaceId) return;
|
||||
|
|
@ -494,11 +616,12 @@ export const useConnectorDialog = () => {
|
|||
(oauthConnector) => oauthConnector.connectorType === connector.connector_type
|
||||
);
|
||||
|
||||
// Check if this is webcrawler (can be managed in popup)
|
||||
// Check if this is webcrawler or Tavily API (can be managed in popup)
|
||||
const isWebcrawler = connector.connector_type === EnumConnectorName.WEBCRAWLER_CONNECTOR;
|
||||
const isTavilyApi = connector.connector_type === EnumConnectorName.TAVILY_API;
|
||||
|
||||
// If not OAuth and not webcrawler, redirect to old connector edit page
|
||||
if (!isOAuthConnector && !isWebcrawler) {
|
||||
// If not OAuth, not webcrawler, and not Tavily API, redirect to old connector edit page
|
||||
if (!isOAuthConnector && !isWebcrawler && !isTavilyApi) {
|
||||
router.push(`/dashboard/${searchSpaceId}/connectors/${connector.id}/edit`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -511,6 +634,7 @@ export const useConnectorDialog = () => {
|
|||
}
|
||||
|
||||
setEditingConnector(connector);
|
||||
setConnectorName(connector.name);
|
||||
// Load existing periodic sync settings
|
||||
setPeriodicEnabled(connector.periodic_indexing_enabled);
|
||||
setFrequencyMinutes(connector.indexing_frequency_minutes?.toString() || "1440");
|
||||
|
|
@ -530,8 +654,8 @@ export const useConnectorDialog = () => {
|
|||
const handleSaveConnector = useCallback(async (refreshConnectors: () => void) => {
|
||||
if (!editingConnector || !searchSpaceId) return;
|
||||
|
||||
// Validate date range (skip for Google Drive which uses folder selection, and Webcrawler which uses config)
|
||||
if (editingConnector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && editingConnector.connector_type !== "WEBCRAWLER_CONNECTOR") {
|
||||
// Validate date range (skip for Google Drive which uses folder selection, Webcrawler which uses config, and non-indexable connectors)
|
||||
if (editingConnector.is_indexable && editingConnector.connector_type !== "GOOGLE_DRIVE_CONNECTOR" && editingConnector.connector_type !== "WEBCRAWLER_CONNECTOR") {
|
||||
const dateRangeValidation = dateRangeSchema.safeParse({ startDate, endDate });
|
||||
if (!dateRangeValidation.success) {
|
||||
toast.error(dateRangeValidation.error.issues[0]?.message || "Invalid date range");
|
||||
|
|
@ -539,8 +663,14 @@ export const useConnectorDialog = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// Validate frequency minutes if periodic is enabled
|
||||
if (periodicEnabled) {
|
||||
// Prevent periodic indexing for non-indexable connectors
|
||||
if (periodicEnabled && !editingConnector.is_indexable) {
|
||||
toast.error("Periodic indexing is not available for this connector type");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate frequency minutes if periodic is enabled (only for indexable connectors)
|
||||
if (periodicEnabled && editingConnector.is_indexable) {
|
||||
const frequencyValidation = frequencyMinutesSchema.safeParse(frequencyMinutes);
|
||||
if (!frequencyValidation.success) {
|
||||
toast.error("Invalid frequency value");
|
||||
|
|
@ -553,20 +683,24 @@ export const useConnectorDialog = () => {
|
|||
const startDateStr = startDate ? format(startDate, "yyyy-MM-dd") : undefined;
|
||||
const endDateStr = endDate ? format(endDate, "yyyy-MM-dd") : undefined;
|
||||
|
||||
// Update connector with periodic sync settings and config changes
|
||||
// Update connector with periodic sync settings, config changes, and name
|
||||
const frequency = periodicEnabled ? parseInt(frequencyMinutes, 10) : null;
|
||||
await updateConnector({
|
||||
id: editingConnector.id,
|
||||
data: {
|
||||
name: connectorName || editingConnector.name,
|
||||
periodic_indexing_enabled: periodicEnabled,
|
||||
indexing_frequency_minutes: frequency,
|
||||
config: connectorConfig || editingConnector.config,
|
||||
},
|
||||
});
|
||||
|
||||
// Re-index based on connector type
|
||||
// Re-index based on connector type (only for indexable connectors)
|
||||
let indexingDescription = "Settings saved.";
|
||||
if (editingConnector.connector_type === "GOOGLE_DRIVE_CONNECTOR") {
|
||||
if (!editingConnector.is_indexable) {
|
||||
// Non-indexable connectors (like Tavily API) don't need re-indexing
|
||||
indexingDescription = "Settings saved.";
|
||||
} else if (editingConnector.connector_type === "GOOGLE_DRIVE_CONNECTOR") {
|
||||
// Google Drive uses folder selection from config, not date ranges
|
||||
const selectedFolders = (connectorConfig || editingConnector.config)?.selected_folders as Array<{ id: string; name: string }> | undefined;
|
||||
if (selectedFolders && selectedFolders.length > 0) {
|
||||
|
|
@ -628,7 +762,7 @@ export const useConnectorDialog = () => {
|
|||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [editingConnector, searchSpaceId, startDate, endDate, indexConnector, updateConnector, periodicEnabled, frequencyMinutes, getFrequencyLabel, router, connectorConfig]);
|
||||
}, [editingConnector, searchSpaceId, startDate, endDate, indexConnector, updateConnector, periodicEnabled, frequencyMinutes, getFrequencyLabel, router, connectorConfig, connectorName]);
|
||||
|
||||
// Handle disconnecting connector
|
||||
const handleDisconnectConnector = useCallback(async (refreshConnectors: () => void) => {
|
||||
|
|
@ -692,12 +826,14 @@ export const useConnectorDialog = () => {
|
|||
window.history.pushState({ modal: false }, "", url.toString());
|
||||
setIsScrolled(false);
|
||||
setSearchQuery("");
|
||||
if (!isStartingIndexing && !isSaving && !isDisconnecting) {
|
||||
if (!isStartingIndexing && !isSaving && !isDisconnecting && !isCreatingConnector) {
|
||||
setIndexingConfig(null);
|
||||
setIndexingConnector(null);
|
||||
setIndexingConnectorConfig(null);
|
||||
setEditingConnector(null);
|
||||
setConnectorName(null);
|
||||
setConnectorConfig(null);
|
||||
setConnectingConnectorType(null);
|
||||
setStartDate(undefined);
|
||||
setEndDate(undefined);
|
||||
setPeriodicEnabled(false);
|
||||
|
|
@ -705,7 +841,7 @@ export const useConnectorDialog = () => {
|
|||
}
|
||||
}
|
||||
},
|
||||
[activeTab, isStartingIndexing, isDisconnecting, isSaving]
|
||||
[activeTab, isStartingIndexing, isDisconnecting, isSaving, isCreatingConnector]
|
||||
);
|
||||
|
||||
// Handle tab change
|
||||
|
|
@ -735,6 +871,8 @@ export const useConnectorDialog = () => {
|
|||
indexingConnector,
|
||||
indexingConnectorConfig,
|
||||
editingConnector,
|
||||
connectingConnectorType,
|
||||
isCreatingConnector,
|
||||
startDate,
|
||||
endDate,
|
||||
isStartingIndexing,
|
||||
|
|
@ -751,20 +889,24 @@ export const useConnectorDialog = () => {
|
|||
setEndDate,
|
||||
setPeriodicEnabled,
|
||||
setFrequencyMinutes,
|
||||
setConnectorName,
|
||||
|
||||
// Handlers
|
||||
handleOpenChange,
|
||||
handleTabChange,
|
||||
handleScroll,
|
||||
handleConnectOAuth,
|
||||
handleConnectNonOAuth,
|
||||
handleCreateWebcrawler,
|
||||
handleCreateYouTube,
|
||||
handleSubmitConnectForm,
|
||||
handleStartIndexing,
|
||||
handleSkipIndexing,
|
||||
handleStartEdit,
|
||||
handleSaveConnector,
|
||||
handleDisconnectConnector,
|
||||
handleBackFromEdit,
|
||||
handleBackFromConnect,
|
||||
connectorConfig,
|
||||
setConnectorConfig,
|
||||
setIndexingConnectorConfig,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ interface AllConnectorsTabProps {
|
|||
connectingId: string | null;
|
||||
allConnectors: SearchSourceConnector[] | undefined;
|
||||
onConnectOAuth: (connector: (typeof OAUTH_CONNECTORS)[0]) => void;
|
||||
onConnectNonOAuth?: (connectorType: string) => void;
|
||||
onCreateWebcrawler?: () => void;
|
||||
onCreateYouTube?: () => void;
|
||||
onManage?: (connector: SearchSourceConnector) => void;
|
||||
|
|
@ -25,6 +26,7 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
|||
connectingId,
|
||||
allConnectors,
|
||||
onConnectOAuth,
|
||||
onConnectNonOAuth,
|
||||
onCreateWebcrawler,
|
||||
onCreateYouTube,
|
||||
onManage,
|
||||
|
|
@ -98,13 +100,16 @@ export const AllConnectorsTab: FC<AllConnectorsTabProps> = ({
|
|||
? allConnectors.find((c: SearchSourceConnector) => c.connector_type === connector.connectorType)
|
||||
: undefined;
|
||||
|
||||
// Special handling for webcrawler and YouTube - create in popup
|
||||
// Special handling for connectors that can be created in popup
|
||||
const isWebcrawler = connector.id === "webcrawler-connector";
|
||||
const isYouTube = connector.id === "youtube-connector";
|
||||
const isTavily = connector.id === "tavily-api";
|
||||
const handleConnect = isWebcrawler && onCreateWebcrawler
|
||||
? onCreateWebcrawler
|
||||
: isYouTube && onCreateYouTube
|
||||
? onCreateYouTube
|
||||
: isTavily && onConnectNonOAuth
|
||||
? () => onConnectNonOAuth(connector.connectorType)
|
||||
: () => router.push(`/dashboard/${searchSpaceId}/connectors/add/${connector.id}`);
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue