feat: enhance document export functionality for memory documents and update UI components

This commit is contained in:
Anish Sarkar 2026-05-20 11:57:31 +05:30
parent 43c8aaeaa7
commit 659007bc4d
3 changed files with 48 additions and 5 deletions

View file

@ -328,7 +328,12 @@ export const DocumentNode = React.memo(function DocumentNode({
Move to... Move to...
</DropdownMenuItem> </DropdownMenuItem>
)} )}
{onExport && ( {onExport && isMemoryDocument ? (
<DropdownMenuItem disabled={isUnavailable} onClick={() => handleExport("md")}>
<Download className="mr-2 h-4 w-4" />
Export as MD
</DropdownMenuItem>
) : onExport ? (
<DropdownMenuSub> <DropdownMenuSub>
<DropdownMenuSubTrigger disabled={isUnavailable}> <DropdownMenuSubTrigger disabled={isUnavailable}>
<Download className="mr-2 h-4 w-4" /> <Download className="mr-2 h-4 w-4" />
@ -338,7 +343,7 @@ export const DocumentNode = React.memo(function DocumentNode({
<ExportDropdownItems onExport={handleExport} exporting={exporting} /> <ExportDropdownItems onExport={handleExport} exporting={exporting} />
</DropdownMenuSubContent> </DropdownMenuSubContent>
</DropdownMenuSub> </DropdownMenuSub>
)} ) : null}
{onVersionHistory && isVersionableType(doc.document_type) && ( {onVersionHistory && isVersionableType(doc.document_type) && (
<DropdownMenuItem disabled={isUnavailable} onClick={() => onVersionHistory(doc)}> <DropdownMenuItem disabled={isUnavailable} onClick={() => onVersionHistory(doc)}>
<History className="mr-2 h-4 w-4" /> <History className="mr-2 h-4 w-4" />
@ -381,7 +386,12 @@ export const DocumentNode = React.memo(function DocumentNode({
Move to... Move to...
</ContextMenuItem> </ContextMenuItem>
)} )}
{onExport && ( {onExport && isMemoryDocument ? (
<ContextMenuItem disabled={isUnavailable} onClick={() => handleExport("md")}>
<Download className="mr-2 h-4 w-4" />
Export as MD
</ContextMenuItem>
) : onExport ? (
<ContextMenuSub> <ContextMenuSub>
<ContextMenuSubTrigger disabled={isUnavailable}> <ContextMenuSubTrigger disabled={isUnavailable}>
<Download className="mr-2 h-4 w-4" /> <Download className="mr-2 h-4 w-4" />
@ -391,7 +401,7 @@ export const DocumentNode = React.memo(function DocumentNode({
<ExportContextItems onExport={handleExport} exporting={exporting} /> <ExportContextItems onExport={handleExport} exporting={exporting} />
</ContextMenuSubContent> </ContextMenuSubContent>
</ContextMenuSub> </ContextMenuSub>
)} ) : null}
{onVersionHistory && isVersionableType(doc.document_type) && ( {onVersionHistory && isVersionableType(doc.document_type) && (
<ContextMenuItem disabled={isUnavailable} onClick={() => onVersionHistory(doc)}> <ContextMenuItem disabled={isUnavailable} onClick={() => onVersionHistory(doc)}>
<History className="mr-2 h-4 w-4" /> <History className="mr-2 h-4 w-4" />

View file

@ -254,7 +254,7 @@ export function FolderTreeView({
onDelete={onDeleteDocument} onDelete={onDeleteDocument}
onMove={onMoveDocument} onMove={onMoveDocument}
onReset={onResetDocument} onReset={onResetDocument}
onExport={isMemoryDocument ? undefined : onExportDocument} onExport={onExportDocument}
onVersionHistory={isMemoryDocument ? undefined : onVersionHistory} onVersionHistory={isMemoryDocument ? undefined : onVersionHistory}
canDelete={!isMemoryDocument} canDelete={!isMemoryDocument}
canMove={!isMemoryDocument} canMove={!isMemoryDocument}

View file

@ -113,6 +113,18 @@ const MEMORY_DOCUMENTS: DocumentNodeDoc[] = [
function isMemoryDocument(doc: { document_type: string }) { function isMemoryDocument(doc: { document_type: string }) {
return doc.document_type === "USER_MEMORY" || doc.document_type === "TEAM_MEMORY"; return doc.document_type === "USER_MEMORY" || doc.document_type === "TEAM_MEMORY";
} }
function downloadTextFile(content: string, fileName: string, type = "text/markdown;charset=utf-8") {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
const LOCAL_FILESYSTEM_TRUST_KEY = "surfsense.local-filesystem-trust.v1"; const LOCAL_FILESYSTEM_TRUST_KEY = "surfsense.local-filesystem-trust.v1";
const MAX_LOCAL_FILESYSTEM_ROOTS = 10; const MAX_LOCAL_FILESYSTEM_ROOTS = 10;
@ -808,6 +820,27 @@ function AuthenticatedDocumentsSidebarBase({
const handleExportDocument = useCallback( const handleExportDocument = useCallback(
async (doc: DocumentNodeDoc, format: string) => { async (doc: DocumentNodeDoc, format: string) => {
if (isMemoryDocument(doc)) {
try {
const endpoint =
doc.document_type === "USER_MEMORY"
? `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/users/me/memory`
: `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/memory`;
const response = await authenticatedFetch(endpoint, { method: "GET" });
if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: "Export failed" }));
throw new Error(errorData.detail || "Export failed");
}
const data = (await response.json()) as { memory_md?: string };
downloadTextFile(data.memory_md ?? "", doc.title.endsWith(".md") ? doc.title : `${doc.title}.md`);
return;
} catch (err) {
console.error("Memory export failed:", err);
toast.error(err instanceof Error ? err.message : "Export failed");
return;
}
}
const safeTitle = const safeTitle =
doc.title doc.title
.replace(/[^a-zA-Z0-9 _-]/g, "_") .replace(/[^a-zA-Z0-9 _-]/g, "_")