mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-17 18:35:19 +02:00
refactor: remove export functionality from DocumentsFilters and streamline DocumentsSidebar export handling
This commit is contained in:
parent
fce465a40f
commit
b6e2510e55
3 changed files with 78 additions and 106 deletions
|
|
@ -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<Record<DocumentTypeEnum, number>>;
|
||||
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({
|
|||
</ToggleGroupItem>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>New folder</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{onExportKB && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<ToggleGroupItem
|
||||
value="export"
|
||||
disabled={isExporting}
|
||||
className="h-9 w-9 shrink-0 border-sidebar-border text-sidebar-foreground/60 hover:text-sidebar-foreground hover:border-sidebar-border bg-sidebar"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onExportKB();
|
||||
}}
|
||||
>
|
||||
{isExporting ? (
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
) : (
|
||||
<Download size={14} />
|
||||
)}
|
||||
</ToggleGroupItem>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{isExporting ? "Exporting…" : "Export knowledge base"}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Popover>
|
||||
<Tooltip>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="border-t pt-6 space-y-2">
|
||||
<Label>Export knowledge base</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Download all documents in this search space as a ZIP of markdown files.
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={isExporting}
|
||||
onClick={handleExportKB}
|
||||
className="relative"
|
||||
>
|
||||
<span className={isExporting ? "opacity-0" : ""}>
|
||||
<FolderArchive className="h-3 w-3 opacity-60" />
|
||||
</span>
|
||||
<span className={isExporting ? "opacity-0" : ""}>Export</span>
|
||||
{isExporting && <Spinner size="sm" className="absolute" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue