From b6e2510e55adc0508fea0f904c04add46bf63dd6 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 13 Apr 2026 21:25:17 +0530 Subject: [PATCH] refactor: remove export functionality from DocumentsFilters and streamline DocumentsSidebar export handling --- .../components/documents/DocumentsFilters.tsx | 35 +------ .../layout/ui/sidebar/DocumentsSidebar.tsx | 92 ++++--------------- .../settings/general-settings-manager.tsx | 57 +++++++++++- 3 files changed, 78 insertions(+), 106 deletions(-) diff --git a/surfsense_web/components/documents/DocumentsFilters.tsx b/surfsense_web/components/documents/DocumentsFilters.tsx index 703c9c3b4..d43f3680b 100644 --- a/surfsense_web/components/documents/DocumentsFilters.tsx +++ b/surfsense_web/components/documents/DocumentsFilters.tsx @@ -1,6 +1,6 @@ "use client"; -import { Download, FolderPlus, ListFilter, Loader2, Search, Upload, X } from "lucide-react"; +import { FolderPlus, ListFilter, Search, Upload, X } from "lucide-react"; import { useTranslations } from "next-intl"; import React, { useCallback, useMemo, useRef, useState } from "react"; import { useDocumentUploadDialog } from "@/components/assistant-ui/document-upload-popup"; @@ -20,8 +20,6 @@ export function DocumentsFilters({ onToggleType, activeTypes, onCreateFolder, - onExportKB, - isExporting, }: { typeCounts: Partial>; onSearch: (v: string) => void; @@ -29,8 +27,6 @@ export function DocumentsFilters({ onToggleType: (type: DocumentTypeEnum, checked: boolean) => void; activeTypes: DocumentTypeEnum[]; onCreateFolder?: () => void; - onExportKB?: () => void; - isExporting?: boolean; }) { const t = useTranslations("documents"); const id = React.useId(); @@ -85,33 +81,8 @@ export function DocumentsFilters({ New folder - - )} - - {onExportKB && ( - - - { - e.preventDefault(); - onExportKB(); - }} - > - {isExporting ? ( - - ) : ( - - )} - - - - {isExporting ? "Exporting…" : "Export knowledge base"} - - - )} + + )} diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 20b25a2d2..95bd21e3b 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -406,22 +406,13 @@ export function DocumentsSidebar({ setFolderPickerOpen(true); }, []); - const [isExportingKB, setIsExportingKB] = useState(false); + const [, setIsExportingKB] = useState(false); const [exportWarningOpen, setExportWarningOpen] = useState(false); const [exportWarningContext, setExportWarningContext] = useState<{ - type: "kb" | "folder"; - folder?: FolderDisplay; + folder: FolderDisplay; pendingCount: number; } | null>(null); - const pendingDocuments = useMemo( - () => - treeDocuments.filter( - (d) => d.status?.state === "pending" || d.status?.state === "processing" - ), - [treeDocuments] - ); - const doExport = useCallback(async (url: string, downloadName: string) => { const response = await authenticatedFetch(url, { method: "GET" }); if (!response.ok) { @@ -440,68 +431,28 @@ export function DocumentsSidebar({ URL.revokeObjectURL(blobUrl); }, []); - const handleExportKB = useCallback(async () => { - if (isExportingKB) return; - - if (pendingDocuments.length > 0) { - setExportWarningContext({ type: "kb", pendingCount: pendingDocuments.length }); - setExportWarningOpen(true); - return; - } - - setIsExportingKB(true); - try { - await doExport( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/export`, - "knowledge-base.zip" - ); - toast.success("Knowledge base exported"); - } catch (err) { - console.error("KB export failed:", err); - toast.error(err instanceof Error ? err.message : "Export failed"); - } finally { - setIsExportingKB(false); - } - }, [searchSpaceId, isExportingKB, pendingDocuments.length, doExport]); - const handleExportWarningConfirm = useCallback(async () => { setExportWarningOpen(false); const ctx = exportWarningContext; - if (!ctx) return; + if (!ctx?.folder) return; - if (ctx.type === "kb") { - setIsExportingKB(true); - try { - await doExport( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/export`, - "knowledge-base.zip" - ); - toast.success("Knowledge base exported"); - } catch (err) { - console.error("KB export failed:", err); - toast.error(err instanceof Error ? err.message : "Export failed"); - } finally { - setIsExportingKB(false); - } - } else if (ctx.type === "folder" && ctx.folder) { - setIsExportingKB(true); - try { - const safeName = - ctx.folder.name - .replace(/[^a-zA-Z0-9 _-]/g, "_") - .trim() - .slice(0, 80) || "folder"; - await doExport( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/export?folder_id=${ctx.folder.id}`, - `${safeName}.zip` - ); - toast.success(`Folder "${ctx.folder.name}" exported`); - } catch (err) { - console.error("Folder export failed:", err); - toast.error(err instanceof Error ? err.message : "Export failed"); - } finally { - setIsExportingKB(false); - } + setIsExportingKB(true); + try { + const safeName = + ctx.folder.name + .replace(/[^a-zA-Z0-9 _-]/g, "_") + .trim() + .slice(0, 80) || "folder"; + await doExport( + `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/export?folder_id=${ctx.folder.id}`, + `${safeName}.zip` + ); + toast.success(`Folder "${ctx.folder.name}" exported`); + } catch (err) { + console.error("Folder export failed:", err); + toast.error(err instanceof Error ? err.message : "Export failed"); + } finally { + setIsExportingKB(false); } setExportWarningContext(null); }, [exportWarningContext, searchSpaceId, doExport]); @@ -530,7 +481,6 @@ export function DocumentsSidebar({ const folderPendingCount = getPendingCountInSubtree(folder.id); if (folderPendingCount > 0) { setExportWarningContext({ - type: "folder", folder, pendingCount: folderPendingCount, }); @@ -954,8 +904,6 @@ export function DocumentsSidebar({ onToggleType={onToggleType} activeTypes={activeTypes} onCreateFolder={() => handleCreateFolder(null)} - onExportKB={handleExportKB} - isExporting={isExportingKB} /> diff --git a/surfsense_web/components/settings/general-settings-manager.tsx b/surfsense_web/components/settings/general-settings-manager.tsx index b8db34c38..99fa02f0d 100644 --- a/surfsense_web/components/settings/general-settings-manager.tsx +++ b/surfsense_web/components/settings/general-settings-manager.tsx @@ -2,9 +2,9 @@ import { useQuery } from "@tanstack/react-query"; import { useAtomValue } from "jotai"; -import { Info } from "lucide-react"; +import { FolderArchive, Info } from "lucide-react"; import { useTranslations } from "next-intl"; -import { useEffect, useState } from "react"; +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"; @@ -13,6 +13,7 @@ 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"; @@ -39,6 +40,37 @@ export function GeneralSettingsManager({ searchSpaceId }: GeneralSettingsManager 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(() => { @@ -156,6 +188,27 @@ export function GeneralSettingsManager({ searchSpaceId }: GeneralSettingsManager + +
+ +

+ Download all documents in this search space as a ZIP of markdown files. +

+ +
); }