"use client"; import { useQuery } from "@tanstack/react-query"; import { useAtomValue } from "jotai"; import { FolderArchive, Info } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { updateSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; import { cacheKeys } from "@/lib/query-client/cache-keys"; import { Spinner } from "../ui/spinner"; interface GeneralSettingsManagerProps { searchSpaceId: number; } export function GeneralSettingsManager({ searchSpaceId }: GeneralSettingsManagerProps) { const t = useTranslations("searchSpaceSettings"); const tCommon = useTranslations("common"); const { data: searchSpace, isLoading: loading, isError, refetch: fetchSearchSpace, } = useQuery({ queryKey: cacheKeys.searchSpaces.detail(searchSpaceId.toString()), queryFn: () => searchSpacesApiService.getSearchSpace({ id: searchSpaceId }), enabled: !!searchSpaceId, }); const { mutateAsync: updateSearchSpace } = useAtomValue(updateSearchSpaceMutationAtom); const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [saving, setSaving] = useState(false); const [isExporting, setIsExporting] = useState(false); const handleExportKB = useCallback(async () => { if (isExporting) return; setIsExporting(true); try { const response = await authenticatedFetch( `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/export`, { method: "GET" } ); if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: "Export failed" })); throw new Error(errorData.detail || "Export failed"); } const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "knowledge-base.zip"; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success("Knowledge base exported"); } catch (err) { console.error("KB export failed:", err); toast.error(err instanceof Error ? err.message : "Export failed"); } finally { setIsExporting(false); } }, [searchSpaceId, isExporting]); // Initialize state from fetched search space useEffect(() => { if (searchSpace) { setName(searchSpace.name || ""); setDescription(searchSpace.description || ""); } }, [searchSpace?.name, searchSpace?.description]); // Derive hasChanges during render const hasChanges = !!searchSpace && ((searchSpace.name || "") !== name || (searchSpace.description || "") !== description); const handleSave = async () => { try { setSaving(true); await updateSearchSpace({ id: searchSpaceId, data: { name: name.trim(), description: description.trim() || undefined, }, }); await fetchSearchSpace(); } catch (error: any) { console.error("Error saving search space details:", error); toast.error(error.message || "Failed to save search space details"); } finally { setSaving(false); } }; const onSubmit = (e: React.FormEvent) => { e.preventDefault(); handleSave(); }; if (loading) { return (
Failed to load settings.
Download all documents in this search space as a ZIP of markdown files.