Merge remote-tracking branch 'upstream/dev' into fix/chatpage-ux

This commit is contained in:
Anish Sarkar 2025-12-27 19:48:13 +05:30
commit ae4ee9ab70
36 changed files with 4225 additions and 3591 deletions

5981
surfsense_backend/uv.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { format } from "date-fns"; import { format } from "date-fns";
import { useAtomValue } from "jotai";
import { import {
Calendar as CalendarIcon, Calendar as CalendarIcon,
Clock, Clock,
@ -15,6 +16,12 @@ import { useParams, useRouter } from "next/navigation";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import {
deleteConnectorMutationAtom,
indexConnectorMutationAtom,
updateConnectorMutationAtom,
} from "@/atoms/connectors/connector-mutation.atoms";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
@ -59,7 +66,6 @@ import {
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export default function ConnectorsPage() { export default function ConnectorsPage() {
@ -84,8 +90,12 @@ export default function ConnectorsPage() {
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const today = new Date(); const today = new Date();
const { connectors, isLoading, error, deleteConnector, indexConnector, updateConnector } = const { data: connectors = [], isLoading, error } = useAtomValue(connectorsAtom);
useSearchSourceConnectors(false, parseInt(searchSpaceId));
const { mutateAsync: deleteConnector } = useAtomValue(deleteConnectorMutationAtom);
const { mutateAsync: indexConnector } = useAtomValue(indexConnectorMutationAtom);
const { mutateAsync: updateConnector } = useAtomValue(updateConnectorMutationAtom);
const [connectorToDelete, setConnectorToDelete] = useState<number | null>(null); const [connectorToDelete, setConnectorToDelete] = useState<number | null>(null);
const [indexingConnectorId, setIndexingConnectorId] = useState<number | null>(null); const [indexingConnectorId, setIndexingConnectorId] = useState<number | null>(null);
const [datePickerOpen, setDatePickerOpen] = useState(false); const [datePickerOpen, setDatePickerOpen] = useState(false);
@ -117,11 +127,9 @@ export default function ConnectorsPage() {
if (connectorToDelete === null) return; if (connectorToDelete === null) return;
try { try {
await deleteConnector(connectorToDelete); await deleteConnector({ id: connectorToDelete });
toast.success(t("delete_success"));
} catch (error) { } catch (error) {
console.error("Error deleting connector:", error); console.error("Error deleting connector:", error);
toast.error(t("delete_failed"));
} finally { } finally {
setConnectorToDelete(null); setConnectorToDelete(null);
} }
@ -144,7 +152,14 @@ export default function ConnectorsPage() {
const startDateStr = startDate ? format(startDate, "yyyy-MM-dd") : undefined; const startDateStr = startDate ? format(startDate, "yyyy-MM-dd") : undefined;
const endDateStr = endDate ? format(endDate, "yyyy-MM-dd") : undefined; const endDateStr = endDate ? format(endDate, "yyyy-MM-dd") : undefined;
await indexConnector(selectedConnectorForIndexing, searchSpaceId, startDateStr, endDateStr); await indexConnector({
connector_id: selectedConnectorForIndexing,
queryParams: {
search_space_id: searchSpaceId,
start_date: startDateStr,
end_date: endDateStr,
},
});
toast.success(t("indexing_started")); toast.success(t("indexing_started"));
} catch (error) { } catch (error) {
console.error("Error indexing connector content:", error); console.error("Error indexing connector content:", error);
@ -161,7 +176,12 @@ export default function ConnectorsPage() {
const handleQuickIndexConnector = async (connectorId: number) => { const handleQuickIndexConnector = async (connectorId: number) => {
setIndexingConnectorId(connectorId); setIndexingConnectorId(connectorId);
try { try {
await indexConnector(connectorId, searchSpaceId); await indexConnector({
connector_id: connectorId,
queryParams: {
search_space_id: searchSpaceId,
},
});
toast.success(t("indexing_started")); toast.success(t("indexing_started"));
} catch (error) { } catch (error) {
console.error("Error indexing connector content:", error); console.error("Error indexing connector content:", error);
@ -221,9 +241,12 @@ export default function ConnectorsPage() {
} }
} }
await updateConnector(selectedConnectorForPeriodic, { await updateConnector({
periodic_indexing_enabled: periodicEnabled, id: selectedConnectorForPeriodic,
indexing_frequency_minutes: frequency, data: {
periodic_indexing_enabled: periodicEnabled,
indexing_frequency_minutes: frequency,
},
}); });
toast.success( toast.success(

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,8 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { updateConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@ -21,10 +24,8 @@ import {
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import type { EnumConnectorName } from "@/contracts/enums/connector";
type SearchSourceConnector, import type { SearchSourceConnector } from "@/hooks/use-search-source-connectors";
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const apiConnectorFormSchema = z.object({ const apiConnectorFormSchema = z.object({
@ -85,7 +86,8 @@ export default function EditConnectorPage() {
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const connectorId = parseInt(params.connector_id as string, 10); const connectorId = parseInt(params.connector_id as string, 10);
const { connectors, updateConnector } = useSearchSourceConnectors(false, parseInt(searchSpaceId)); const { data: connectors = [] } = useAtomValue(connectorsAtom);
const { mutateAsync: updateConnector } = useAtomValue(updateConnectorMutationAtom);
const [connector, setConnector] = useState<SearchSourceConnector | null>(null); const [connector, setConnector] = useState<SearchSourceConnector | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
@ -99,14 +101,12 @@ export default function EditConnectorPage() {
}, },
}); });
// Find connector in the list
useEffect(() => { useEffect(() => {
const currentConnector = connectors.find((c) => c.id === connectorId); const currentConnector = connectors.find((c) => c.id === connectorId);
if (currentConnector) { if (currentConnector) {
setConnector(currentConnector); setConnector(currentConnector);
// Check if connector type is supported
const apiKeyField = getApiKeyFieldName(currentConnector.connector_type); const apiKeyField = getApiKeyFieldName(currentConnector.connector_type);
if (apiKeyField) { if (apiKeyField) {
form.reset({ form.reset({
@ -114,14 +114,12 @@ export default function EditConnectorPage() {
api_key: currentConnector.config[apiKeyField] || "", api_key: currentConnector.config[apiKeyField] || "",
}); });
} else { } else {
// Redirect if not a supported connector type
toast.error("This connector type is not supported for editing"); toast.error("This connector type is not supported for editing");
router.push(`/dashboard/${searchSpaceId}/connectors`); router.push(`/dashboard/${searchSpaceId}/connectors`);
} }
setIsLoading(false); setIsLoading(false);
} else if (!isLoading && connectors.length > 0) { } else if (!isLoading && connectors.length > 0) {
// If connectors are loaded but this one isn't found
toast.error("Connector not found"); toast.error("Connector not found");
router.push(`/dashboard/${searchSpaceId}/connectors`); router.push(`/dashboard/${searchSpaceId}/connectors`);
} }
@ -135,18 +133,20 @@ export default function EditConnectorPage() {
try { try {
const apiKeyField = getApiKeyFieldName(connector.connector_type); const apiKeyField = getApiKeyFieldName(connector.connector_type);
// Only update the API key if a new one was provided
const updatedConfig = { ...connector.config }; const updatedConfig = { ...connector.config };
if (values.api_key) { if (values.api_key) {
updatedConfig[apiKeyField] = values.api_key; updatedConfig[apiKeyField] = values.api_key;
} }
await updateConnector(connectorId, { await updateConnector({
name: values.name, id: connectorId,
connector_type: connector.connector_type, data: {
config: updatedConfig, name: values.name,
is_indexable: connector.is_indexable, connector_type: connector.connector_type as EnumConnectorName,
last_indexed_at: connector.last_indexed_at, config: updatedConfig,
is_indexable: connector.is_indexable,
last_indexed_at: connector.last_indexed_at,
},
}); });
toast.success("Connector updated successfully!"); toast.success("Connector updated successfully!");

View file

@ -1,11 +1,13 @@
"use client"; "use client";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react"; import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import Link from "next/link"; import Link from "next/link";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
@ -18,11 +20,8 @@ import {
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
// import { IconBrandAirtable } from "@tabler/icons-react"; // import { IconBrandAirtable } from "@tabler/icons-react";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import {
type SearchSourceConnector,
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
import { authenticatedFetch } from "@/lib/auth-utils"; import { authenticatedFetch } from "@/lib/auth-utils";
import { SearchSourceConnector } from "@/contracts/types/connector.types";
export default function AirtableConnectorPage() { export default function AirtableConnectorPage() {
const router = useRouter(); const router = useRouter();
@ -31,11 +30,12 @@ export default function AirtableConnectorPage() {
const [isConnecting, setIsConnecting] = useState(false); const [isConnecting, setIsConnecting] = useState(false);
const [doesConnectorExist, setDoesConnectorExist] = useState(false); const [doesConnectorExist, setDoesConnectorExist] = useState(false);
const { fetchConnectors } = useSearchSourceConnectors(true, parseInt(searchSpaceId)); const { refetch : fetchConnectors } = useAtomValue(connectorsAtom);
useEffect(() => { useEffect(() => {
fetchConnectors(parseInt(searchSpaceId)).then((data) => { fetchConnectors().then((data) => {
const connector = data.find( const connectors = data.data || [];
const connector = connectors.find(
(c: SearchSourceConnector) => c.connector_type === EnumConnectorName.AIRTABLE_CONNECTOR (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.AIRTABLE_CONNECTOR
); );
if (connector) { if (connector) {

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -38,7 +40,6 @@ import {
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const baiduSearchApiFormSchema = z.object({ const baiduSearchApiFormSchema = z.object({
@ -61,7 +62,7 @@ export default function BaiduSearchApiPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<BaiduSearchApiFormValues>({ const form = useForm<BaiduSearchApiFormValues>({
@ -95,8 +96,8 @@ export default function BaiduSearchApiPage() {
config.BAIDU_ENABLE_DEEP_SEARCH = values.enable_deep_search; config.BAIDU_ENABLE_DEEP_SEARCH = values.enable_deep_search;
} }
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.BAIDU_SEARCH_API, connector_type: EnumConnectorName.BAIDU_SEARCH_API,
config, config,
@ -106,8 +107,10 @@ export default function BaiduSearchApiPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Baidu Search connector created successfully!"); toast.success("Baidu Search connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@ -24,7 +26,6 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const bookstackConnectorFormSchema = z.object({ const bookstackConnectorFormSchema = z.object({
@ -50,7 +51,7 @@ export default function BookStackConnectorPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<BookStackConnectorFormValues>({ const form = useForm<BookStackConnectorFormValues>({
@ -67,8 +68,8 @@ export default function BookStackConnectorPage() {
const onSubmit = async (values: BookStackConnectorFormValues) => { const onSubmit = async (values: BookStackConnectorFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.BOOKSTACK_CONNECTOR, connector_type: EnumConnectorName.BOOKSTACK_CONNECTOR,
config: { config: {
@ -82,8 +83,10 @@ export default function BookStackConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("BookStack connector created successfully!"); toast.success("BookStack connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, ExternalLink, Eye, EyeOff } from "lucide-react"; import { ArrowLeft, ExternalLink, Eye, EyeOff } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { import {
@ -22,7 +24,6 @@ import {
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const clickupConnectorFormSchema = z.object({ const clickupConnectorFormSchema = z.object({
@ -41,7 +42,7 @@ export default function ClickUpConnectorPage() {
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [showApiToken, setShowApiToken] = useState(false); const [showApiToken, setShowApiToken] = useState(false);
@ -59,20 +60,23 @@ export default function ClickUpConnectorPage() {
setIsLoading(true); setIsLoading(true);
try { try {
const connectorData = { await createConnector({
name: values.name, data: {
connector_type: EnumConnectorName.CLICKUP_CONNECTOR, name: values.name,
is_indexable: true, connector_type: EnumConnectorName.CLICKUP_CONNECTOR,
config: { is_indexable: true,
CLICKUP_API_TOKEN: values.api_token, config: {
CLICKUP_API_TOKEN: values.api_token,
},
last_indexed_at: null,
periodic_indexing_enabled: false,
indexing_frequency_minutes: null,
next_scheduled_at: null,
}, },
last_indexed_at: null, queryParams: {
periodic_indexing_enabled: false, search_space_id: searchSpaceId,
indexing_frequency_minutes: null, },
next_scheduled_at: null, });
};
await createConnector(connectorData, parseInt(searchSpaceId));
toast.success("ClickUp connector created successfully!"); toast.success("ClickUp connector created successfully!");
router.push(`/dashboard/${searchSpaceId}/connectors`); router.push(`/dashboard/${searchSpaceId}/connectors`);

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@ -24,7 +26,6 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const confluenceConnectorFormSchema = z.object({ const confluenceConnectorFormSchema = z.object({
@ -60,7 +61,7 @@ export default function ConfluenceConnectorPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<ConfluenceConnectorFormValues>({ const form = useForm<ConfluenceConnectorFormValues>({
@ -77,8 +78,8 @@ export default function ConfluenceConnectorPage() {
const onSubmit = async (values: ConfluenceConnectorFormValues) => { const onSubmit = async (values: ConfluenceConnectorFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.CONFLUENCE_CONNECTOR, connector_type: EnumConnectorName.CONFLUENCE_CONNECTOR,
config: { config: {
@ -92,8 +93,10 @@ export default function ConfluenceConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Confluence connector created successfully!"); toast.success("Confluence connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@ -37,7 +39,6 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const discordConnectorFormSchema = z.object({ const discordConnectorFormSchema = z.object({
@ -58,7 +59,7 @@ export default function DiscordConnectorPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<DiscordConnectorFormValues>({ const form = useForm<DiscordConnectorFormValues>({
@ -73,8 +74,8 @@ export default function DiscordConnectorPage() {
const onSubmit = async (values: DiscordConnectorFormValues) => { const onSubmit = async (values: DiscordConnectorFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.DISCORD_CONNECTOR, connector_type: EnumConnectorName.DISCORD_CONNECTOR,
config: { config: {
@ -86,8 +87,10 @@ export default function DiscordConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Discord connector created successfully!"); toast.success("Discord connector created successfully!");
router.push(`/dashboard/${searchSpaceId}/connectors`); router.push(`/dashboard/${searchSpaceId}/connectors`);

View file

@ -2,6 +2,7 @@
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import * as RadioGroup from "@radix-ui/react-radio-group"; import * as RadioGroup from "@radix-ui/react-radio-group";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useParams, useRouter, useSearchParams } from "next/navigation";
@ -9,7 +10,7 @@ import { useId, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@ -40,10 +41,8 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const elasticsearchConnectorFormSchema = z const elasticsearchConnectorFormSchema = z
@ -91,7 +90,7 @@ export default function ElasticsearchConnectorPage() {
const authBasicId = useId(); const authBasicId = useId();
const authApiKeyId = useId(); const authApiKeyId = useId();
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<ElasticsearchConnectorFormValues>({ const form = useForm<ElasticsearchConnectorFormValues>({
@ -173,19 +172,21 @@ export default function ElasticsearchConnectorPage() {
config.ELASTICSEARCH_MAX_DOCUMENTS = values.max_documents; config.ELASTICSEARCH_MAX_DOCUMENTS = values.max_documents;
} }
const connectorPayload = { await createConnector({
name: values.name, data: {
connector_type: EnumConnectorName.ELASTICSEARCH_CONNECTOR, name: values.name,
is_indexable: true, connector_type: EnumConnectorName.ELASTICSEARCH_CONNECTOR,
last_indexed_at: null, is_indexable: true,
periodic_indexing_enabled: false, last_indexed_at: null,
indexing_frequency_minutes: null, periodic_indexing_enabled: false,
next_scheduled_at: null, indexing_frequency_minutes: null,
config, next_scheduled_at: null,
}; config,
},
// Use existing hook method queryParams: {
await createConnector(connectorPayload, searchSpaceIdNum); search_space_id: searchSpaceId,
},
});
toast.success("Elasticsearch connector created successfully!"); toast.success("Elasticsearch connector created successfully!");
router.push(`/dashboard/${searchSpaceId}/connectors`); router.push(`/dashboard/${searchSpaceId}/connectors`);

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, CircleAlert, Github, Info, ListChecks, Loader2 } from "lucide-react"; import { ArrowLeft, Check, CircleAlert, Github, Info, ListChecks, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@ -38,8 +40,6 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
// Assuming useSearchSourceConnectors hook exists and works similarly
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
import { authenticatedFetch, redirectToLogin } from "@/lib/auth-utils"; import { authenticatedFetch, redirectToLogin } from "@/lib/auth-utils";
// Define the form schema with Zod for GitHub PAT entry step // Define the form schema with Zod for GitHub PAT entry step
@ -85,7 +85,7 @@ export default function GithubConnectorPage() {
const [connectorName, setConnectorName] = useState<string>("GitHub Connector"); const [connectorName, setConnectorName] = useState<string>("GitHub Connector");
const [validatedPat, setValidatedPat] = useState<string>(""); // Store the validated PAT const [validatedPat, setValidatedPat] = useState<string>(""); // Store the validated PAT
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form for PAT entry // Initialize the form for PAT entry
const form = useForm<GithubPatFormValues>({ const form = useForm<GithubPatFormValues>({
@ -141,8 +141,8 @@ export default function GithubConnectorPage() {
setIsCreatingConnector(true); setIsCreatingConnector(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: connectorName, // Use the stored name name: connectorName, // Use the stored name
connector_type: EnumConnectorName.GITHUB_CONNECTOR, connector_type: EnumConnectorName.GITHUB_CONNECTOR,
config: { config: {
@ -155,8 +155,10 @@ export default function GithubConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("GitHub connector created successfully!"); toast.success("GitHub connector created successfully!");
router.push(`/dashboard/${searchSpaceId}/connectors`); router.push(`/dashboard/${searchSpaceId}/connectors`);

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react"; import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import Link from "next/link"; import Link from "next/link";
@ -9,6 +10,7 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
@ -20,11 +22,8 @@ import {
} from "@/components/ui/card"; } from "@/components/ui/card";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import {
type SearchSourceConnector,
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
import { authenticatedFetch } from "@/lib/auth-utils"; import { authenticatedFetch } from "@/lib/auth-utils";
import { SearchSourceConnector } from "@/contracts/types/connector.types";
export default function GoogleCalendarConnectorPage() { export default function GoogleCalendarConnectorPage() {
const router = useRouter(); const router = useRouter();
@ -33,13 +32,13 @@ export default function GoogleCalendarConnectorPage() {
const [isConnecting, setIsConnecting] = useState(false); const [isConnecting, setIsConnecting] = useState(false);
const [doesConnectorExist, setDoesConnectorExist] = useState(false); const [doesConnectorExist, setDoesConnectorExist] = useState(false);
const { fetchConnectors } = useSearchSourceConnectors(true, parseInt(searchSpaceId)); const { refetch : fetchConnectors } = useAtomValue(connectorsAtom);
useEffect(() => { useEffect(() => {
fetchConnectors(parseInt(searchSpaceId)).then((data) => { fetchConnectors().then((data) => {
const connector = data.find( const connectors = data.data || [];
(c: SearchSourceConnector) => const connector = connectors.find(
c.connector_type === EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR
); );
if (connector) { if (connector) {
setDoesConnectorExist(true); setDoesConnectorExist(true);

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react"; import { ArrowLeft, Check, ExternalLink, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import Link from "next/link"; import Link from "next/link";
@ -9,6 +10,7 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
@ -20,11 +22,8 @@ import {
} from "@/components/ui/card"; } from "@/components/ui/card";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import {
type SearchSourceConnector,
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
import { authenticatedFetch } from "@/lib/auth-utils"; import { authenticatedFetch } from "@/lib/auth-utils";
import { SearchSourceConnector } from "@/contracts/types/connector.types";
export default function GoogleGmailConnectorPage() { export default function GoogleGmailConnectorPage() {
const router = useRouter(); const router = useRouter();
@ -33,11 +32,12 @@ export default function GoogleGmailConnectorPage() {
const [isConnecting, setIsConnecting] = useState(false); const [isConnecting, setIsConnecting] = useState(false);
const [doesConnectorExist, setDoesConnectorExist] = useState(false); const [doesConnectorExist, setDoesConnectorExist] = useState(false);
const { fetchConnectors } = useSearchSourceConnectors(true, parseInt(searchSpaceId)); const { refetch : fetchConnectors } = useAtomValue(connectorsAtom);
useEffect(() => { useEffect(() => {
fetchConnectors(parseInt(searchSpaceId)).then((data) => { fetchConnectors().then((data) => {
const connector = data.find( const connectors = data.data || [];
const connector = connectors.find(
(c: SearchSourceConnector) => c.connector_type === EnumConnectorName.GOOGLE_GMAIL_CONNECTOR (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.GOOGLE_GMAIL_CONNECTOR
); );
if (connector) { if (connector) {

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@ -37,7 +39,6 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const jiraConnectorFormSchema = z.object({ const jiraConnectorFormSchema = z.object({
@ -73,7 +74,7 @@ export default function JiraConnectorPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<JiraConnectorFormValues>({ const form = useForm<JiraConnectorFormValues>({
@ -90,8 +91,8 @@ export default function JiraConnectorPage() {
const onSubmit = async (values: JiraConnectorFormValues) => { const onSubmit = async (values: JiraConnectorFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.JIRA_CONNECTOR, connector_type: EnumConnectorName.JIRA_CONNECTOR,
config: { config: {
@ -105,8 +106,10 @@ export default function JiraConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Jira connector created successfully!"); toast.success("Jira connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@ -37,7 +39,6 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const linearConnectorFormSchema = z.object({ const linearConnectorFormSchema = z.object({
@ -62,7 +63,7 @@ export default function LinearConnectorPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<LinearConnectorFormValues>({ const form = useForm<LinearConnectorFormValues>({
@ -77,8 +78,8 @@ export default function LinearConnectorPage() {
const onSubmit = async (values: LinearConnectorFormValues) => { const onSubmit = async (values: LinearConnectorFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.LINEAR_CONNECTOR, connector_type: EnumConnectorName.LINEAR_CONNECTOR,
config: { config: {
@ -90,8 +91,10 @@ export default function LinearConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Linear connector created successfully!"); toast.success("Linear connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -30,7 +32,6 @@ import {
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const linkupApiFormSchema = z.object({ const linkupApiFormSchema = z.object({
@ -50,7 +51,7 @@ export default function LinkupApiPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<LinkupApiFormValues>({ const form = useForm<LinkupApiFormValues>({
@ -65,8 +66,8 @@ export default function LinkupApiPage() {
const onSubmit = async (values: LinkupApiFormValues) => { const onSubmit = async (values: LinkupApiFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.LINKUP_API, connector_type: EnumConnectorName.LINKUP_API,
config: { config: {
@ -78,8 +79,10 @@ export default function LinkupApiPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Linkup API connector created successfully!"); toast.success("Linkup API connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Key, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Key, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import Link from "next/link"; import Link from "next/link";
@ -9,6 +10,8 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
@ -30,10 +33,7 @@ import {
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { import { SearchSourceConnector } from "@/contracts/types/connector.types";
type SearchSourceConnector,
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const lumaConnectorFormSchema = z.object({ const lumaConnectorFormSchema = z.object({
@ -55,10 +55,8 @@ export default function LumaConnectorPage() {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [doesConnectorExist, setDoesConnectorExist] = useState(false); const [doesConnectorExist, setDoesConnectorExist] = useState(false);
const { fetchConnectors, createConnector } = useSearchSourceConnectors( const { data: connectors } = useAtomValue(connectorsAtom);
true, const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
parseInt(searchSpaceId)
);
// Initialize the form // Initialize the form
const form = useForm<LumaConnectorFormValues>({ const form = useForm<LumaConnectorFormValues>({
@ -69,29 +67,26 @@ export default function LumaConnectorPage() {
}, },
}); });
const { refetch : fetchConnectors } = useAtomValue(connectorsAtom);
useEffect(() => { useEffect(() => {
fetchConnectors(parseInt(searchSpaceId)) fetchConnectors().then((data) => {
.then((data) => { const connectors = data.data || [];
if (data && Array.isArray(data)) { const connector = connectors.find(
const connector = data.find( (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.LUMA_CONNECTOR
(c: SearchSourceConnector) => c.connector_type === EnumConnectorName.LUMA_CONNECTOR );
); if (connector) {
if (connector) { setDoesConnectorExist(true);
setDoesConnectorExist(true); }
} });
} }, []);
})
.catch((error) => {
console.error("Error fetching connectors:", error);
});
}, [fetchConnectors, searchSpaceId]);
// Handle form submission // Handle form submission
const onSubmit = async (values: LumaConnectorFormValues) => { const onSubmit = async (values: LumaConnectorFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.LUMA_CONNECTOR, connector_type: EnumConnectorName.LUMA_CONNECTOR,
config: { config: {
@ -103,8 +98,10 @@ export default function LumaConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Luma connector created successfully!"); toast.success("Luma connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@ -37,7 +39,6 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const notionConnectorFormSchema = z.object({ const notionConnectorFormSchema = z.object({
@ -57,7 +58,7 @@ export default function NotionConnectorPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<NotionConnectorFormValues>({ const form = useForm<NotionConnectorFormValues>({
@ -72,8 +73,8 @@ export default function NotionConnectorPage() {
const onSubmit = async (values: NotionConnectorFormValues) => { const onSubmit = async (values: NotionConnectorFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.NOTION_CONNECTOR, connector_type: EnumConnectorName.NOTION_CONNECTOR,
config: { config: {
@ -85,8 +86,10 @@ export default function NotionConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Notion connector created successfully!"); toast.success("Notion connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -31,7 +33,6 @@ import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
const searxngFormSchema = z.object({ const searxngFormSchema = z.object({
name: z.string().min(3, { name: z.string().min(3, {
@ -67,7 +68,7 @@ export default function SearxngConnectorPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
const form = useForm<SearxngFormValues>({ const form = useForm<SearxngFormValues>({
resolver: zodResolver(searxngFormSchema), resolver: zodResolver(searxngFormSchema),
@ -115,8 +116,8 @@ export default function SearxngConnectorPage() {
config.SEARXNG_VERIFY_SSL = false; config.SEARXNG_VERIFY_SSL = false;
} }
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.SEARXNG_API, connector_type: EnumConnectorName.SEARXNG_API,
config, config,
@ -126,8 +127,10 @@ export default function SearxngConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("SearxNG connector created successfully!"); toast.success("SearxNG connector created successfully!");
router.push(`/dashboard/${searchSpaceId}/connectors`); router.push(`/dashboard/${searchSpaceId}/connectors`);

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { import {
Accordion, Accordion,
AccordionContent, AccordionContent,
@ -37,7 +39,6 @@ import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const slackConnectorFormSchema = z.object({ const slackConnectorFormSchema = z.object({
@ -57,7 +58,7 @@ export default function SlackConnectorPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<SlackConnectorFormValues>({ const form = useForm<SlackConnectorFormValues>({
@ -72,8 +73,8 @@ export default function SlackConnectorPage() {
const onSubmit = async (values: SlackConnectorFormValues) => { const onSubmit = async (values: SlackConnectorFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.SLACK_CONNECTOR, connector_type: EnumConnectorName.SLACK_CONNECTOR,
config: { config: {
@ -85,8 +86,10 @@ export default function SlackConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Slack connector created successfully!"); toast.success("Slack connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Info, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Info, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
@ -8,6 +9,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -30,7 +32,6 @@ import {
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { useSearchSourceConnectors } from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const tavilyApiFormSchema = z.object({ const tavilyApiFormSchema = z.object({
@ -50,7 +51,7 @@ export default function TavilyApiPage() {
const params = useParams(); const params = useParams();
const searchSpaceId = params.search_space_id as string; const searchSpaceId = params.search_space_id as string;
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const { createConnector } = useSearchSourceConnectors(); const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
// Initialize the form // Initialize the form
const form = useForm<TavilyApiFormValues>({ const form = useForm<TavilyApiFormValues>({
@ -65,8 +66,8 @@ export default function TavilyApiPage() {
const onSubmit = async (values: TavilyApiFormValues) => { const onSubmit = async (values: TavilyApiFormValues) => {
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.TAVILY_API, connector_type: EnumConnectorName.TAVILY_API,
config: { config: {
@ -78,8 +79,10 @@ export default function TavilyApiPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Tavily API connector created successfully!"); toast.success("Tavily API connector created successfully!");

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { ArrowLeft, Check, Globe, Loader2 } from "lucide-react"; import { ArrowLeft, Check, Globe, Loader2 } from "lucide-react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import Link from "next/link"; import Link from "next/link";
@ -9,6 +10,8 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import * as z from "zod"; import * as z from "zod";
import { createConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
@ -31,10 +34,7 @@ import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { EnumConnectorName } from "@/contracts/enums/connector"; import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import { import { SearchSourceConnector } from "@/contracts/types/connector.types";
type SearchSourceConnector,
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
// Define the form schema with Zod // Define the form schema with Zod
const webcrawlerConnectorFormSchema = z.object({ const webcrawlerConnectorFormSchema = z.object({
@ -55,10 +55,8 @@ export default function WebcrawlerConnectorPage() {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [doesConnectorExist, setDoesConnectorExist] = useState(false); const [doesConnectorExist, setDoesConnectorExist] = useState(false);
const { fetchConnectors, createConnector } = useSearchSourceConnectors( const { refetch : fetchConnectors } = useAtomValue(connectorsAtom);
true, const { mutateAsync: createConnector } = useAtomValue(createConnectorMutationAtom);
parseInt(searchSpaceId)
);
// Initialize the form // Initialize the form
const form = useForm<WebcrawlerConnectorFormValues>({ const form = useForm<WebcrawlerConnectorFormValues>({
@ -71,22 +69,16 @@ export default function WebcrawlerConnectorPage() {
}); });
useEffect(() => { useEffect(() => {
fetchConnectors(parseInt(searchSpaceId)) fetchConnectors().then((data) => {
.then((data) => { const connectors = data.data || [];
if (data && Array.isArray(data)) { const connector = connectors.find(
const connector = data.find( (c: SearchSourceConnector) => c.connector_type === EnumConnectorName.WEBCRAWLER_CONNECTOR
(c: SearchSourceConnector) => );
c.connector_type === EnumConnectorName.WEBCRAWLER_CONNECTOR if (connector) {
); setDoesConnectorExist(true);
if (connector) { }
setDoesConnectorExist(true); });
} }, []);
}
})
.catch((error) => {
console.error("Error fetching connectors:", error);
});
}, [fetchConnectors, searchSpaceId]);
// Handle form submission // Handle form submission
const onSubmit = async (values: WebcrawlerConnectorFormValues) => { const onSubmit = async (values: WebcrawlerConnectorFormValues) => {
@ -104,8 +96,8 @@ export default function WebcrawlerConnectorPage() {
config.INITIAL_URLS = values.initial_urls; config.INITIAL_URLS = values.initial_urls;
} }
await createConnector( await createConnector({
{ data: {
name: values.name, name: values.name,
connector_type: EnumConnectorName.WEBCRAWLER_CONNECTOR, connector_type: EnumConnectorName.WEBCRAWLER_CONNECTOR,
config: config, config: config,
@ -115,8 +107,10 @@ export default function WebcrawlerConnectorPage() {
indexing_frequency_minutes: null, indexing_frequency_minutes: null,
next_scheduled_at: null, next_scheduled_at: null,
}, },
parseInt(searchSpaceId) queryParams: {
); search_space_id: searchSpaceId,
},
});
toast.success("Webcrawler connector created successfully!"); toast.success("Webcrawler connector created successfully!");

View file

@ -15,6 +15,7 @@ import {
useReactTable, useReactTable,
type VisibilityState, type VisibilityState,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import { useAtomValue } from "jotai";
import { import {
Activity, Activity,
AlertCircle, AlertCircle,
@ -44,8 +45,13 @@ import {
import { AnimatePresence, motion, type Variants } from "motion/react"; import { AnimatePresence, motion, type Variants } from "motion/react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import React, { useContext, useId, useMemo, useRef, useState } from "react"; import React, { useCallback, useContext, useId, useMemo, useRef, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import {
createLogMutationAtom,
deleteLogMutationAtom,
updateLogMutationAtom,
} from "@/atoms/logs/log-mutation.atoms";
import { JsonMetadataViewer } from "@/components/json-metadata-viewer"; import { JsonMetadataViewer } from "@/components/json-metadata-viewer";
import { import {
AlertDialog, AlertDialog,
@ -89,7 +95,8 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { type Log, type LogLevel, type LogStatus, useLogs, useLogsSummary } from "@/hooks/use-logs"; import type { CreateLogRequest, Log, UpdateLogRequest } from "@/contracts/types/log.types";
import { type LogLevel, type LogStatus, useLogs, useLogsSummary } from "@/hooks/use-logs";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
// Define animation variants for reuse // Define animation variants for reuse
@ -334,13 +341,50 @@ export default function LogsManagePage() {
const params = useParams(); const params = useParams();
const searchSpaceId = Number(params.search_space_id); const searchSpaceId = Number(params.search_space_id);
const { const { mutateAsync: deleteLogMutation } = useAtomValue(deleteLogMutationAtom);
logs, const { mutateAsync: updateLogMutation } = useAtomValue(updateLogMutationAtom);
loading: logsLoading, const { mutateAsync: createLogMutation } = useAtomValue(createLogMutationAtom);
error: logsError,
refreshLogs, const createLog = useCallback(
deleteLog, async (data: CreateLogRequest) => {
} = useLogs(searchSpaceId); try {
await createLogMutation(data);
return true;
} catch (error) {
console.error("Failed to create log:", error);
return false;
}
},
[createLogMutation]
);
const updateLog = useCallback(
async (logId: number, data: UpdateLogRequest) => {
try {
await updateLogMutation({ logId, data });
return true;
} catch (error) {
console.error("Failed to update log:", error);
return false;
}
},
[updateLogMutation]
);
const deleteLog = useCallback(
async (id: number) => {
try {
await deleteLogMutation({ id });
return true;
} catch (error) {
console.error("Failed to delete log:", error);
return false;
}
},
[deleteLogMutation]
);
const { logs, loading: logsLoading, error: logsError, refreshLogs } = useLogs(searchSpaceId);
const { const {
summary, summary,
loading: summaryLoading, loading: summaryLoading,
@ -408,7 +452,7 @@ export default function LogsManagePage() {
return; return;
} }
const deletePromises = selectedRows.map((row) => deleteLog(row.original.id)); const deletePromises = selectedRows.map((row) => deleteLog(row.original.id)); // Already passes { id } via wrapper
try { try {
const results = await Promise.all(deletePromises); const results = await Promise.all(deletePromises);
@ -437,7 +481,7 @@ export default function LogsManagePage() {
<LogsContext.Provider <LogsContext.Provider
value={{ value={{
deleteLog: deleteLog || (() => Promise.resolve(false)), deleteLog: deleteLog || (() => Promise.resolve(false)),
refreshLogs: refreshLogs || (() => Promise.resolve()), refreshLogs: () => refreshLogs().then(() => void 0),
}} }}
> >
<motion.div <motion.div
@ -524,7 +568,7 @@ export default function LogsManagePage() {
table={table} table={table}
logs={logs} logs={logs}
loading={logsLoading} loading={logsLoading}
error={logsError} error={logsError?.message ?? null}
onRefresh={refreshLogs} onRefresh={refreshLogs}
id={id} id={id}
t={t} t={t}

View file

@ -0,0 +1,100 @@
import { atomWithMutation } from "jotai-tanstack-query";
import { toast } from "sonner";
import type {
CreateConnectorRequest,
DeleteConnectorRequest,
GetConnectorsResponse,
IndexConnectorRequest,
IndexConnectorResponse,
UpdateConnectorRequest,
} from "@/contracts/types/connector.types";
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { queryClient } from "@/lib/query-client/client";
import { activeSearchSpaceIdAtom } from "../search-spaces/search-space-query.atoms";
export const createConnectorMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
return {
mutationKey: cacheKeys.connectors.all(searchSpaceId!),
enabled: !!searchSpaceId,
mutationFn: async (request: CreateConnectorRequest) => {
return connectorsApiService.createConnector(request);
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: cacheKeys.connectors.all(searchSpaceId!),
});
},
};
});
export const updateConnectorMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
return {
mutationKey: cacheKeys.connectors.all(searchSpaceId!),
enabled: !!searchSpaceId,
mutationFn: async (request: UpdateConnectorRequest) => {
return connectorsApiService.updateConnector(request);
},
onSuccess: (_, request: UpdateConnectorRequest) => {
queryClient.invalidateQueries({
queryKey: cacheKeys.connectors.all(searchSpaceId!),
});
queryClient.invalidateQueries({
queryKey: cacheKeys.connectors.byId(String(request.id)),
});
},
};
});
export const deleteConnectorMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
return {
mutationKey: cacheKeys.connectors.all(searchSpaceId!),
enabled: !!searchSpaceId,
mutationFn: async (request: DeleteConnectorRequest) => {
return connectorsApiService.deleteConnector(request);
},
onSuccess: (_, request: DeleteConnectorRequest) => {
queryClient.setQueryData(
cacheKeys.connectors.all(searchSpaceId!),
(oldData: GetConnectorsResponse | undefined) => {
if (!oldData) return oldData;
return oldData.filter((connector) => connector.id !== request.id);
}
);
queryClient.invalidateQueries({
queryKey: cacheKeys.connectors.byId(String(request.id)),
});
},
};
});
export const indexConnectorMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
return {
mutationKey: cacheKeys.connectors.index(),
enabled: !!searchSpaceId,
mutationFn: async (request: IndexConnectorRequest) => {
return connectorsApiService.indexConnector(request);
},
onSuccess: (response: IndexConnectorResponse) => {
toast.success(response.message);
queryClient.invalidateQueries({
queryKey: cacheKeys.connectors.all(searchSpaceId!),
});
queryClient.invalidateQueries({
queryKey: cacheKeys.connectors.byId(String(response.connector_id)),
});
},
};
});

View file

@ -0,0 +1,21 @@
import { atomWithQuery } from "jotai-tanstack-query";
import { connectorsApiService } from "@/lib/apis/connectors-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { activeSearchSpaceIdAtom } from "../search-spaces/search-space-query.atoms";
export const connectorsAtom = atomWithQuery((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
return {
queryKey: cacheKeys.connectors.all(searchSpaceId!),
enabled: !!searchSpaceId,
staleTime: 5 * 60 * 1000, // 5 minutes
queryFn: async () => {
return connectorsApiService.getConnectors({
queryParams: {
search_space_id: searchSpaceId!,
},
});
},
};
});

View file

@ -0,0 +1,7 @@
import { atom } from "jotai";
import type { GetConnectorsRequest } from "@/contracts/types/connector.types";
export const globalConnectorsQueryParamsAtom = atom<GetConnectorsRequest["queryParams"]>({
skip: 0,
limit: 10,
});

View file

@ -0,0 +1,68 @@
import { atomWithMutation } from "jotai-tanstack-query";
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import type {
CreateLogRequest,
DeleteLogRequest,
UpdateLogRequest,
} from "@/contracts/types/log.types";
import { logsApiService } from "@/lib/apis/logs-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { queryClient } from "@/lib/query-client/client";
/**
* Create Log Mutation
*/
export const createLogMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
return {
mutationKey: cacheKeys.logs.list(searchSpaceId ?? undefined),
enabled: !!searchSpaceId,
mutationFn: async (request: CreateLogRequest) => logsApiService.createLog(request),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: cacheKeys.logs.list(searchSpaceId ?? undefined) });
queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(searchSpaceId ?? undefined),
});
},
};
});
/**
* Update Log Mutation
*/
export const updateLogMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
return {
mutationKey: cacheKeys.logs.list(searchSpaceId ?? undefined),
enabled: !!searchSpaceId,
mutationFn: async ({ logId, data }: { logId: number; data: UpdateLogRequest }) =>
logsApiService.updateLog(logId, data),
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({ queryKey: cacheKeys.logs.detail(variables.logId) });
queryClient.invalidateQueries({ queryKey: cacheKeys.logs.list(searchSpaceId ?? undefined) });
queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(searchSpaceId ?? undefined),
});
},
};
});
/**
* Delete Log Mutation
*/
export const deleteLogMutationAtom = atomWithMutation((get) => {
const searchSpaceId = get(activeSearchSpaceIdAtom);
return {
mutationKey: cacheKeys.logs.list(searchSpaceId ?? undefined),
enabled: !!searchSpaceId,
mutationFn: async (request: DeleteLogRequest) => logsApiService.deleteLog(request),
onSuccess: (_data, request) => {
queryClient.invalidateQueries({ queryKey: cacheKeys.logs.list(searchSpaceId ?? undefined) });
queryClient.invalidateQueries({
queryKey: cacheKeys.logs.summary(searchSpaceId ?? undefined),
});
if (request?.id)
queryClient.invalidateQueries({ queryKey: cacheKeys.logs.detail(request.id) });
},
};
});

View file

@ -0,0 +1,159 @@
import { z } from "zod";
import { paginationQueryParams } from ".";
export const searchSourceConnectorTypeEnum = z.enum([
"SERPER_API",
"TAVILY_API",
"SEARXNG_API",
"LINKUP_API",
"BAIDU_SEARCH_API",
"SLACK_CONNECTOR",
"NOTION_CONNECTOR",
"GITHUB_CONNECTOR",
"LINEAR_CONNECTOR",
"DISCORD_CONNECTOR",
"JIRA_CONNECTOR",
"CONFLUENCE_CONNECTOR",
"CLICKUP_CONNECTOR",
"GOOGLE_CALENDAR_CONNECTOR",
"GOOGLE_GMAIL_CONNECTOR",
"AIRTABLE_CONNECTOR",
"LUMA_CONNECTOR",
"ELASTICSEARCH_CONNECTOR",
"WEBCRAWLER_CONNECTOR",
"BOOKSTACK_CONNECTOR",
]);
export const searchSourceConnector = z.object({
id: z.number(),
name: z.string(),
connector_type: searchSourceConnectorTypeEnum,
is_indexable: z.boolean(),
last_indexed_at: z.string().nullable(),
config: z.record(z.string(), z.any()),
periodic_indexing_enabled: z.boolean(),
indexing_frequency_minutes: z.number().nullable(),
next_scheduled_at: z.string().nullable(),
search_space_id: z.number(),
user_id: z.string(),
created_at: z.string(),
});
/**
* Get connectors
*/
export const getConnectorsRequest = z.object({
queryParams: paginationQueryParams
.pick({ skip: true, limit: true })
.extend({
search_space_id: z.number().or(z.string()).nullish(),
})
.nullish(),
});
export const getConnectorsResponse = z.array(searchSourceConnector);
/**
* Get connector
*/
export const getConnectorRequest = searchSourceConnector.pick({ id: true });
export const getConnectorResponse = searchSourceConnector;
/**
* Create connector
*/
export const createConnectorRequest = z.object({
data: searchSourceConnector.pick({
name: true,
connector_type: true,
is_indexable: true,
last_indexed_at: true,
config: true,
periodic_indexing_enabled: true,
indexing_frequency_minutes: true,
next_scheduled_at: true,
}),
queryParams: z.object({
search_space_id: z.number().or(z.string()),
}),
});
export const createConnectorResponse = searchSourceConnector;
/**
* Update connector
*/
export const updateConnectorRequest = z.object({
id: z.number(),
data: searchSourceConnector
.pick({
name: true,
connector_type: true,
is_indexable: true,
last_indexed_at: true,
config: true,
periodic_indexing_enabled: true,
indexing_frequency_minutes: true,
next_scheduled_at: true,
})
.partial(),
});
export const updateConnectorResponse = searchSourceConnector;
/**
* Delete connector
*/
export const deleteConnectorRequest = searchSourceConnector.pick({ id: true });
export const deleteConnectorResponse = z.object({
message: z.literal("Search source connector deleted successfully"),
});
/**
* Index connector
*/
export const indexConnectorRequest = z.object({
connector_id: z.number(),
queryParams: z.object({
search_space_id: z.number().or(z.string()),
start_date: z.string().optional(),
end_date: z.string().optional(),
}),
});
export const indexConnectorResponse = z.object({
message: z.string(),
connector_id: z.number(),
search_space_id: z.number(),
indexing_from: z.string(),
indexing_to: z.string(),
});
/**
* List GitHub repositories
*/
export const listGitHubRepositoriesRequest = z.object({
github_pat: z.string(),
});
export const listGitHubRepositoriesResponse = z.array(z.record(z.string(), z.any()));
// Inferred types
export type SearchSourceConnectorType = z.infer<typeof searchSourceConnectorTypeEnum>;
export type SearchSourceConnector = z.infer<typeof searchSourceConnector>;
export type GetConnectorsRequest = z.infer<typeof getConnectorsRequest>;
export type GetConnectorsResponse = z.infer<typeof getConnectorsResponse>;
export type GetConnectorRequest = z.infer<typeof getConnectorRequest>;
export type GetConnectorResponse = z.infer<typeof getConnectorResponse>;
export type CreateConnectorRequest = z.infer<typeof createConnectorRequest>;
export type CreateConnectorResponse = z.infer<typeof createConnectorResponse>;
export type UpdateConnectorRequest = z.infer<typeof updateConnectorRequest>;
export type UpdateConnectorResponse = z.infer<typeof updateConnectorResponse>;
export type DeleteConnectorRequest = z.infer<typeof deleteConnectorRequest>;
export type DeleteConnectorResponse = z.infer<typeof deleteConnectorResponse>;
export type IndexConnectorRequest = z.infer<typeof indexConnectorRequest>;
export type IndexConnectorResponse = z.infer<typeof indexConnectorResponse>;
export type ListGitHubRepositoriesRequest = z.infer<typeof listGitHubRepositoriesRequest>;
export type ListGitHubRepositoriesResponse = z.infer<typeof listGitHubRepositoriesResponse>;

View file

@ -0,0 +1,133 @@
import { z } from "zod";
import { paginationQueryParams } from ".";
/**
* ENUMS
*/
export const logLevelEnum = z.enum(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]);
export const logStatusEnum = z.enum(["IN_PROGRESS", "SUCCESS", "FAILED"]);
/**
* Base log schema
*/
export const log = z.object({
id: z.number(),
level: logLevelEnum,
status: logStatusEnum,
message: z.string(),
source: z.string().nullable().optional(),
log_metadata: z.record(z.string(), z.any()).nullable().optional(),
created_at: z.string(),
search_space_id: z.number(),
});
export const logBase = log.omit({ id: true, created_at: true });
/**
* Create log
*/
export const createLogRequest = logBase.extend({ search_space_id: z.number() });
export const createLogResponse = log;
/**
* Update log
*/
export const updateLogRequest = logBase.partial();
export const updateLogResponse = log;
/**
* Delete log
*/
export const deleteLogRequest = z.object({ id: z.number() });
export const deleteLogResponse = z.object({
message: z.string().default("Log deleted successfully"),
});
/**
* Get logs (list)
*/
export const logFilters = z.object({
search_space_id: z.number().optional(),
level: logLevelEnum.optional(),
status: logStatusEnum.optional(),
source: z.string().optional(),
start_date: z.string().optional(),
end_date: z.string().optional(),
});
export const getLogsRequest = z.object({
queryParams: paginationQueryParams
.extend({
search_space_id: z.number().optional(),
level: logLevelEnum.optional(),
status: logStatusEnum.optional(),
source: z.string().optional(),
start_date: z.string().optional(),
end_date: z.string().optional(),
})
.nullish(),
});
export const getLogsResponse = z.array(log);
/**
* Get single log
*/
export const getLogRequest = z.object({ id: z.number() });
export const getLogResponse = log;
/**
* Log summary (used for summary dashboard)
*/
export const logActiveTask = z.object({
id: z.number(),
task_name: z.string(),
message: z.string(),
started_at: z.string(),
source: z.string().nullable().optional(),
});
export const logFailure = z.object({
id: z.number(),
task_name: z.string(),
message: z.string(),
failed_at: z.string(),
source: z.string().nullable().optional(),
error_details: z.string().nullable().optional(),
});
export const logSummary = z.object({
total_logs: z.number(),
time_window_hours: z.number(),
by_status: z.record(z.string(), z.number()),
by_level: z.record(z.string(), z.number()),
by_source: z.record(z.string(), z.number()),
active_tasks: z.array(logActiveTask),
recent_failures: z.array(logFailure),
});
export const getLogSummaryRequest = z.object({
search_space_id: z.number(),
hours: z.number().optional(),
});
export const getLogSummaryResponse = logSummary;
/**
* Typescript types
*/
export type Log = z.infer<typeof log>;
export type LogLevelEnum = z.infer<typeof logLevelEnum>;
export type LogStatusEnum = z.infer<typeof logStatusEnum>;
export type LogFilters = z.infer<typeof logFilters>;
export type CreateLogRequest = z.infer<typeof createLogRequest>;
export type CreateLogResponse = z.infer<typeof createLogResponse>;
export type UpdateLogRequest = z.infer<typeof updateLogRequest>;
export type UpdateLogResponse = z.infer<typeof updateLogResponse>;
export type DeleteLogRequest = z.infer<typeof deleteLogRequest>;
export type DeleteLogResponse = z.infer<typeof deleteLogResponse>;
export type GetLogsRequest = z.infer<typeof getLogsRequest>;
export type GetLogsResponse = z.infer<typeof getLogsResponse>;
export type GetLogRequest = z.infer<typeof getLogRequest>;
export type GetLogResponse = z.infer<typeof getLogResponse>;
export type LogSummary = z.infer<typeof logSummary>;
export type LogFailure = z.infer<typeof logFailure>;
export type LogActiveTask = z.infer<typeof logActiveTask>;
export type GetLogSummaryRequest = z.infer<typeof getLogSummaryRequest>;
export type GetLogSummaryResponse = z.infer<typeof getLogSummaryResponse>;

View file

@ -1,8 +1,11 @@
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { toast } from "sonner"; import { toast } from "sonner";
import { updateConnectorMutationAtom } from "@/atoms/connectors/connector-mutation.atoms";
import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms";
import { import {
type EditConnectorFormValues, type EditConnectorFormValues,
type EditMode, type EditMode,
@ -11,10 +14,8 @@ import {
type GithubRepo, type GithubRepo,
githubPatSchema, githubPatSchema,
} from "@/components/editConnector/types"; } from "@/components/editConnector/types";
import { import type { EnumConnectorName } from "@/contracts/enums/connector";
type SearchSourceConnector, import type { SearchSourceConnector } from "@/hooks/use-search-source-connectors";
useSearchSourceConnectors,
} from "@/hooks/use-search-source-connectors";
import { authenticatedFetch } from "@/lib/auth-utils"; import { authenticatedFetch } from "@/lib/auth-utils";
const normalizeListInput = (value: unknown): string[] => { const normalizeListInput = (value: unknown): string[] => {
@ -51,11 +52,8 @@ const normalizeBoolean = (value: unknown): boolean | null => {
export function useConnectorEditPage(connectorId: number, searchSpaceId: string) { export function useConnectorEditPage(connectorId: number, searchSpaceId: string) {
const router = useRouter(); const router = useRouter();
const { const { data: connectors = [], isLoading: connectorsLoading } = useAtomValue(connectorsAtom);
connectors, const { mutateAsync: updateConnector } = useAtomValue(updateConnectorMutationAtom);
updateConnector,
isLoading: connectorsLoading,
} = useSearchSourceConnectors(false, parseInt(searchSpaceId));
// State managed by the hook // State managed by the hook
const [connector, setConnector] = useState<SearchSourceConnector | null>(null); const [connector, setConnector] = useState<SearchSourceConnector | null>(null);
@ -532,7 +530,13 @@ export function useConnectorEditPage(connectorId: number, searchSpaceId: string)
} }
try { try {
await updateConnector(connectorId, updatePayload); await updateConnector({
id: connectorId,
data: {
...updatePayload,
connector_type: connector.connector_type as EnumConnectorName,
},
});
toast.success("Connector updated!"); toast.success("Connector updated!");
const newlySavedConfig = updatePayload.config || originalConfig; const newlySavedConfig = updatePayload.config || originalConfig;
setOriginalConfig(newlySavedConfig); setOriginalConfig(newlySavedConfig);

View file

@ -1,114 +0,0 @@
import { authenticatedFetch } from "@/lib/auth-utils";
// Types for connector API
export interface ConnectorConfig {
[key: string]: string;
}
export interface Connector {
id: number;
name: string;
connector_type: string;
config: ConnectorConfig;
created_at: string;
user_id: string;
}
export interface CreateConnectorRequest {
name: string;
connector_type: string;
config: ConnectorConfig;
}
// Get connector type display name
export const getConnectorTypeDisplay = (type: string): string => {
const typeMap: Record<string, string> = {
TAVILY_API: "Tavily API",
SEARXNG_API: "SearxNG",
};
return typeMap[type] || type;
};
// API service for connectors
export const ConnectorService = {
// Create a new connector
async createConnector(data: CreateConnectorRequest): Promise<Connector> {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || "Failed to create connector");
}
return response.json();
},
// Get all connectors
async getConnectors(skip = 0, limit = 100): Promise<Connector[]> {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors?skip=${skip}&limit=${limit}`,
{ method: "GET" }
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || "Failed to fetch connectors");
}
return response.json();
},
// Get a specific connector
async getConnector(connectorId: number): Promise<Connector> {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
{ method: "GET" }
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || "Failed to fetch connector");
}
return response.json();
},
// Update a connector
async updateConnector(connectorId: number, data: CreateConnectorRequest): Promise<Connector> {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || "Failed to update connector");
}
return response.json();
},
// Delete a connector
async deleteConnector(connectorId: number): Promise<void> {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-source-connectors/${connectorId}`,
{ method: "DELETE" }
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || "Failed to delete connector");
}
},
};

View file

@ -1,7 +1,8 @@
"use client"; "use client";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query";
import { toast } from "sonner"; import { useCallback, useMemo } from "react";
import { authenticatedFetch } from "@/lib/auth-utils"; import { logsApiService } from "@/lib/apis/logs-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
export type LogLevel = "DEBUG" | "INFO" | "WARNING" | "ERROR" | "CRITICAL"; export type LogLevel = "DEBUG" | "INFO" | "WARNING" | "ERROR" | "CRITICAL";
export type LogStatus = "IN_PROGRESS" | "SUCCESS" | "FAILED"; export type LogStatus = "IN_PROGRESS" | "SUCCESS" | "FAILED";
@ -50,282 +51,89 @@ export interface LogSummary {
} }
export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) { export function useLogs(searchSpaceId?: number, filters: LogFilters = {}) {
const [logs, setLogs] = useState<Log[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Memoize filters to prevent infinite re-renders // Memoize filters to prevent infinite re-renders
const memoizedFilters = useMemo(() => filters, [JSON.stringify(filters)]); const memoizedFilters = useMemo(() => filters, [JSON.stringify(filters)]);
const buildQueryParams = useCallback( const buildQueryParams = useCallback(
(customFilters: LogFilters = {}) => { (customFilters: LogFilters = {}) => {
const params = new URLSearchParams(); const params: Record<string, string> = {};
const allFilters = { ...memoizedFilters, ...customFilters }; const allFilters = { ...memoizedFilters, ...customFilters };
if (allFilters.search_space_id) { if (allFilters.search_space_id) {
params.append("search_space_id", allFilters.search_space_id.toString()); params["search_space_id"] = allFilters.search_space_id.toString();
} }
if (allFilters.level) { if (allFilters.level) {
params.append("level", allFilters.level); params["level"] = allFilters.level;
} }
if (allFilters.status) { if (allFilters.status) {
params.append("status", allFilters.status); params["status"] = allFilters.status;
} }
if (allFilters.source) { if (allFilters.source) {
params.append("source", allFilters.source); params["source"] = allFilters.source;
} }
if (allFilters.start_date) { if (allFilters.start_date) {
params.append("start_date", allFilters.start_date); params["start_date"] = allFilters.start_date;
} }
if (allFilters.end_date) { if (allFilters.end_date) {
params.append("end_date", allFilters.end_date); params["end_date"] = allFilters.end_date;
} }
return params.toString(); return params;
}, },
[memoizedFilters] [memoizedFilters]
); );
const fetchLogs = useCallback( const {
async (customFilters: LogFilters = {}, options: { skip?: number; limit?: number } = {}) => { data: logs,
try { isLoading: loading,
setLoading(true); error,
refetch,
const params = new URLSearchParams(buildQueryParams(customFilters)); } = useQuery({
if (options.skip !== undefined) params.append("skip", options.skip.toString()); queryKey: cacheKeys.logs.withQueryParams({
if (options.limit !== undefined) params.append("limit", options.limit.toString()); search_space_id: searchSpaceId,
skip: 0,
const response = await authenticatedFetch( limit: 5,
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs?${params}`, ...buildQueryParams(filters ?? {}),
{ method: "GET" } }),
); queryFn: () =>
logsApiService.getLogs({
if (!response.ok) { queryParams: {
const errorData = await response.json().catch(() => ({})); search_space_id: searchSpaceId,
throw new Error(errorData.detail || "Failed to fetch logs"); skip: 0,
} limit: 5,
...buildQueryParams(filters ?? {}),
const data = await response.json(); },
setLogs(data); }),
setError(null); enabled: !!searchSpaceId,
return data; staleTime: 3 * 60 * 1000,
} catch (err: any) { });
setError(err.message || "Failed to fetch logs");
console.error("Error fetching logs:", err);
throw err;
} finally {
setLoading(false);
}
},
[buildQueryParams]
);
// Initial fetch
useEffect(() => {
const initialFilters = searchSpaceId
? { ...memoizedFilters, search_space_id: searchSpaceId }
: memoizedFilters;
fetchLogs(initialFilters);
}, [searchSpaceId, fetchLogs, memoizedFilters]);
// Function to refresh the logs list
const refreshLogs = useCallback(
async (customFilters: LogFilters = {}) => {
const finalFilters = searchSpaceId
? { ...customFilters, search_space_id: searchSpaceId }
: customFilters;
return await fetchLogs(finalFilters);
},
[searchSpaceId, fetchLogs]
);
// Function to create a new log
// Use silent: true to suppress toast notifications (for internal/background operations)
const createLog = useCallback(
async (logData: Omit<Log, "id" | "created_at">, options?: { silent?: boolean }) => {
const { silent = false } = options || {};
try {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs`,
{
headers: { "Content-Type": "application/json" },
method: "POST",
body: JSON.stringify(logData),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || "Failed to create log");
}
const newLog = await response.json();
setLogs((prevLogs) => [newLog, ...prevLogs]);
// Only show toast if not silent
if (!silent) {
toast.success("Log created successfully");
}
return newLog;
} catch (err: any) {
// Only show error toast if not silent
if (!silent) {
toast.error(err.message || "Failed to create log");
}
console.error("Error creating log:", err);
throw err;
}
},
[]
);
// Function to update a log
const updateLog = useCallback(
async (
logId: number,
updateData: Partial<Omit<Log, "id" | "created_at" | "search_space_id">>
) => {
try {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/${logId}`,
{
headers: { "Content-Type": "application/json" },
method: "PUT",
body: JSON.stringify(updateData),
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || "Failed to update log");
}
const updatedLog = await response.json();
setLogs((prevLogs) => prevLogs.map((log) => (log.id === logId ? updatedLog : log)));
toast.success("Log updated successfully");
return updatedLog;
} catch (err: any) {
toast.error(err.message || "Failed to update log");
console.error("Error updating log:", err);
throw err;
}
},
[]
);
// Function to delete a log
const deleteLog = useCallback(async (logId: number) => {
try {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/${logId}`,
{ method: "DELETE" }
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || "Failed to delete log");
}
setLogs((prevLogs) => prevLogs.filter((log) => log.id !== logId));
toast.success("Log deleted successfully");
return true;
} catch (err: any) {
toast.error(err.message || "Failed to delete log");
console.error("Error deleting log:", err);
return false;
}
}, []);
// Function to get a single log
const getLog = useCallback(async (logId: number) => {
try {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/${logId}`,
{ method: "GET" }
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || "Failed to fetch log");
}
return await response.json();
} catch (err: any) {
toast.error(err.message || "Failed to fetch log");
console.error("Error fetching log:", err);
throw err;
}
}, []);
return { return {
logs, logs: logs ?? [],
loading, loading,
error, error,
refreshLogs, refreshLogs: refetch,
createLog,
updateLog,
deleteLog,
getLog,
fetchLogs,
}; };
} }
// Separate hook for log summary // Separate hook for log summary
export function useLogsSummary( export function useLogsSummary(searchSpaceId: number, hours: number = 24) {
searchSpaceId: number, const {
hours: number = 24, data: summary,
options: { refetchInterval?: number } = {} isLoading: loading,
) { error,
const [summary, setSummary] = useState<LogSummary | null>(null); refetch,
const [loading, setLoading] = useState(true); } = useQuery({
const [error, setError] = useState<string | null>(null); queryKey: cacheKeys.logs.summary(searchSpaceId),
queryFn: () =>
logsApiService.getLogSummary({
search_space_id: searchSpaceId,
hours: hours,
}),
enabled: !!searchSpaceId,
staleTime: 3 * 60 * 1000,
});
const fetchSummary = useCallback(async () => { return { summary, loading, error, refreshSummary: refetch };
if (!searchSpaceId) return; }
try {
setLoading(true);
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/logs/search-space/${searchSpaceId}/summary?hours=${hours}`,
{ method: "GET" }
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || "Failed to fetch logs summary");
}
const data = await response.json();
setSummary(data);
setError(null);
return data;
} catch (err: any) {
setError(err.message || "Failed to fetch logs summary");
console.error("Error fetching logs summary:", err);
throw err;
} finally {
setLoading(false);
}
}, [searchSpaceId, hours]);
useEffect(() => {
fetchSummary();
}, [fetchSummary]);
// Set up polling if refetchInterval is provided
useEffect(() => {
if (!options.refetchInterval || options.refetchInterval <= 0) return;
const intervalId = setInterval(() => {
fetchSummary();
}, options.refetchInterval);
return () => clearInterval(intervalId);
}, [fetchSummary, options.refetchInterval]);
const refreshSummary = useCallback(() => {
return fetchSummary();
}, [fetchSummary]);
return { summary, loading, error, refreshSummary };
}

View file

@ -21,18 +21,23 @@ export type RequestOptions = {
}; };
class BaseApiService { class BaseApiService {
bearerToken: string;
baseUrl: string; baseUrl: string;
noAuthEndpoints: string[] = ["/auth/jwt/login", "/auth/register", "/auth/refresh"]; // Add more endpoints as needed noAuthEndpoints: string[] = ["/auth/jwt/login", "/auth/register", "/auth/refresh"]; // Add more endpoints as needed
constructor(bearerToken: string, baseUrl: string) { // Use a getter to always read fresh token from localStorage
this.bearerToken = bearerToken; // This ensures the token is always up-to-date after login/logout
get bearerToken(): string {
return typeof window !== "undefined" ? getBearerToken() || "" : "";
}
constructor(baseUrl: string) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
} }
setBearerToken(bearerToken: string) { // Keep for backward compatibility, but token is now always read from localStorage
this.bearerToken = bearerToken; setBearerToken(_bearerToken: string) {
// No-op: token is now always read fresh from localStorage via the getter
} }
async request<T, R extends ResponseType = ResponseType.JSON>( async request<T, R extends ResponseType = ResponseType.JSON>(
@ -293,7 +298,4 @@ class BaseApiService {
} }
} }
export const baseApiService = new BaseApiService( export const baseApiService = new BaseApiService(process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "");
typeof window !== "undefined" ? getBearerToken() || "" : "",
process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || ""
);

View file

@ -0,0 +1,200 @@
import {
type CreateConnectorRequest,
createConnectorRequest,
createConnectorResponse,
type DeleteConnectorRequest,
deleteConnectorRequest,
deleteConnectorResponse,
type GetConnectorRequest,
type GetConnectorsRequest,
getConnectorRequest,
getConnectorResponse,
getConnectorsRequest,
getConnectorsResponse,
type IndexConnectorRequest,
indexConnectorRequest,
indexConnectorResponse,
type ListGitHubRepositoriesRequest,
listGitHubRepositoriesRequest,
listGitHubRepositoriesResponse,
type UpdateConnectorRequest,
updateConnectorRequest,
updateConnectorResponse,
} from "@/contracts/types/connector.types";
import { ValidationError } from "../error";
import { baseApiService } from "./base-api.service";
class ConnectorsApiService {
/**
* Get all connectors for a search space
*/
getConnectors = async (request: GetConnectorsRequest) => {
const parsedRequest = getConnectorsRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.errors.map((err) => err.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
// Transform query params to be string values
const transformedQueryParams = parsedRequest.data.queryParams
? Object.fromEntries(
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => {
return [k, String(v)];
})
)
: undefined;
const queryParams = transformedQueryParams
? new URLSearchParams(transformedQueryParams).toString()
: "";
return baseApiService.get(
`/api/v1/search-source-connectors?${queryParams}`,
getConnectorsResponse
);
};
/**
* Get a single connector by ID
*/
getConnector = async (request: GetConnectorRequest) => {
const parsedRequest = getConnectorRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.errors.map((err) => err.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.get(
`/api/v1/search-source-connectors/${request.id}`,
getConnectorResponse
);
};
/**
* Create a new connector
*/
createConnector = async (request: CreateConnectorRequest) => {
const parsedRequest = createConnectorRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.errors.map((err) => err.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
const { data, queryParams } = parsedRequest.data;
// Transform query params to be string values
const transformedQueryParams = Object.fromEntries(
Object.entries(queryParams).map(([k, v]) => {
return [k, String(v)];
})
);
const queryString = new URLSearchParams(transformedQueryParams).toString();
return baseApiService.post(
`/api/v1/search-source-connectors?${queryString}`,
createConnectorResponse,
{
body: data,
}
);
};
/**
* Update an existing connector
*/
updateConnector = async (request: UpdateConnectorRequest) => {
const parsedRequest = updateConnectorRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.errors.map((err) => err.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
const { id, data } = parsedRequest.data;
return baseApiService.put(`/api/v1/search-source-connectors/${id}`, updateConnectorResponse, {
body: data,
});
};
/**
* Delete a connector
*/
deleteConnector = async (request: DeleteConnectorRequest) => {
const parsedRequest = deleteConnectorRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.errors.map((err) => err.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.delete(
`/api/v1/search-source-connectors/${request.id}`,
deleteConnectorResponse
);
};
/**
* Index connector content
*/
indexConnector = async (request: IndexConnectorRequest) => {
const parsedRequest = indexConnectorRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.errors.map((err) => err.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
const { connector_id, queryParams } = parsedRequest.data;
// Transform query params to be string values
const transformedQueryParams = Object.fromEntries(
Object.entries(queryParams).map(([k, v]) => {
return [k, String(v)];
})
);
const queryString = new URLSearchParams(transformedQueryParams).toString();
return baseApiService.post(
`/api/v1/search-source-connectors/${connector_id}/index?${queryString}`,
indexConnectorResponse
);
};
/**
* List GitHub repositories using a Personal Access Token
*/
listGitHubRepositories = async (request: ListGitHubRepositoriesRequest) => {
const parsedRequest = listGitHubRepositoriesRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.errors.map((err) => err.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.post(`/api/v1/github/repositories`, listGitHubRepositoriesResponse, {
body: parsedRequest.data,
});
};
}
export const connectorsApiService = new ConnectorsApiService();

View file

@ -0,0 +1,128 @@
import {
type CreateLogRequest,
createLogRequest,
createLogResponse,
type DeleteLogRequest,
deleteLogRequest,
deleteLogResponse,
type GetLogRequest,
type GetLogSummaryRequest,
type GetLogsRequest,
getLogRequest,
getLogResponse,
getLogSummaryRequest,
getLogSummaryResponse,
getLogsRequest,
getLogsResponse,
type Log,
log,
type UpdateLogRequest,
updateLogRequest,
updateLogResponse,
} from "@/contracts/types/log.types";
import { ValidationError } from "../error";
import { baseApiService } from "./base-api.service";
class LogsApiService {
/**
* Get a list of logs with optional filtering and pagination
*/
getLogs = async (request: GetLogsRequest) => {
const parsedRequest = getLogsRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
// Transform query params to be string values
const transformedQueryParams = parsedRequest.data.queryParams
? Object.fromEntries(
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => {
// Handle array values (document_type)
if (Array.isArray(v)) {
return [k, v.join(",")];
}
return [k, String(v)];
})
)
: undefined;
const queryParams = transformedQueryParams
? new URLSearchParams(transformedQueryParams).toString()
: "";
return baseApiService.get(`/api/v1/logs?${queryParams}`, getLogsResponse);
};
/**
* Get a single log by ID
*/
getLog = async (request: GetLogRequest) => {
const parsedRequest = getLogRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.get(`/api/v1/logs/${request.id}`, getLogResponse);
};
/**
* Create a log entry
*/
createLog = async (request: CreateLogRequest) => {
const parsedRequest = createLogRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.post(`/api/v1/logs`, createLogResponse, {
body: parsedRequest.data,
});
};
/**
* Update a log entry
*/
updateLog = async (logId: number, request: UpdateLogRequest) => {
const parsedRequest = updateLogRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.put(`/api/v1/logs/${logId}`, updateLogResponse, {
body: parsedRequest.data,
});
};
/**
* Delete a log entry
*/
deleteLog = async (request: DeleteLogRequest) => {
const parsedRequest = deleteLogRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
return baseApiService.delete(`/api/v1/logs/${parsedRequest.data.id}`, deleteLogResponse);
};
/**
* Get summary for logs by search space
*/
getLogSummary = async (request: GetLogSummaryRequest) => {
const parsedRequest = getLogSummaryRequest.safeParse(request);
if (!parsedRequest.success) {
console.error("Invalid request:", parsedRequest.error);
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
throw new ValidationError(`Invalid request: ${errorMessage}`);
}
const { search_space_id, hours } = parsedRequest.data;
const url = `/api/v1/logs/search-space/${search_space_id}/summary${hours ? `?hours=${hours}` : ""}`;
return baseApiService.get(url, getLogSummaryResponse);
};
}
export const logsApiService = new LogsApiService();

View file

@ -1,4 +1,6 @@
import type { GetConnectorsRequest } from "@/contracts/types/connector.types";
import type { GetDocumentsRequest } from "@/contracts/types/document.types"; import type { GetDocumentsRequest } from "@/contracts/types/document.types";
import type { GetLogsRequest } from "@/contracts/types/log.types";
import type { GetSearchSpacesRequest } from "@/contracts/types/search-space.types"; import type { GetSearchSpacesRequest } from "@/contracts/types/search-space.types";
export const cacheKeys = { export const cacheKeys = {
@ -18,6 +20,13 @@ export const cacheKeys = {
typeCounts: (searchSpaceId?: string) => ["documents", "type-counts", searchSpaceId] as const, typeCounts: (searchSpaceId?: string) => ["documents", "type-counts", searchSpaceId] as const,
byChunk: (chunkId: string) => ["documents", "by-chunk", chunkId] as const, byChunk: (chunkId: string) => ["documents", "by-chunk", chunkId] as const,
}, },
logs: {
list: (searchSpaceId?: number | string) => ["logs", "list", searchSpaceId] as const,
detail: (logId: number | string) => ["logs", "detail", logId] as const,
summary: (searchSpaceId?: number | string) => ["logs", "summary", searchSpaceId] as const,
withQueryParams: (queries: GetLogsRequest["queryParams"]) =>
["logs", "with-query-params", ...(queries ? Object.values(queries) : [])] as const,
},
newLLMConfigs: { newLLMConfigs: {
all: (searchSpaceId: number) => ["new-llm-configs", searchSpaceId] as const, all: (searchSpaceId: number) => ["new-llm-configs", searchSpaceId] as const,
byId: (configId: number) => ["new-llm-configs", "detail", configId] as const, byId: (configId: number) => ["new-llm-configs", "detail", configId] as const,
@ -52,4 +61,11 @@ export const cacheKeys = {
all: (searchSpaceId: string) => ["invites", searchSpaceId] as const, all: (searchSpaceId: string) => ["invites", searchSpaceId] as const,
info: (inviteCode: string) => ["invites", "info", inviteCode] as const, info: (inviteCode: string) => ["invites", "info", inviteCode] as const,
}, },
connectors: {
all: (searchSpaceId: string) => ["connectors", searchSpaceId] as const,
withQueryParams: (queries: GetConnectorsRequest["queryParams"]) =>
["connectors", ...(queries ? Object.values(queries) : [])] as const,
byId: (connectorId: string) => ["connector", connectorId] as const,
index: () => ["connector", "index"] as const,
},
}; };