@@ -263,7 +126,7 @@ export function MemoryContent() {
size="sm"
className="text-xs sm:text-sm"
onClick={handleClear}
- disabled={saving || editing || !memory}
+ disabled={saving || !memory}
>
Reset Memory
Reset
diff --git a/surfsense_web/components/settings/team-memory-manager.tsx b/surfsense_web/components/settings/team-memory-manager.tsx
index 9d3a40e46..6a2cbf52f 100644
--- a/surfsense_web/components/settings/team-memory-manager.tsx
+++ b/surfsense_web/components/settings/team-memory-manager.tsx
@@ -1,12 +1,7 @@
"use client";
-import { useQuery, useQueryClient } from "@tanstack/react-query";
-import { useAtomValue } from "jotai";
-import { ArrowUp, ChevronDown, ClipboardCopy, Download, Info, Pencil } from "lucide-react";
-import { useEffect, useRef, useState } from "react";
+import { ChevronDown, ClipboardCopy, Download, Info } from "lucide-react";
import { toast } from "sonner";
-import { z } from "zod";
-import { updateSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms";
import { PlateEditor } from "@/components/editor/plate-editor";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
@@ -17,105 +12,24 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Spinner } from "@/components/ui/spinner";
-import { baseApiService } from "@/lib/apis/base-api.service";
-import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
-import { cacheKeys } from "@/lib/query-client/cache-keys";
-
-const MEMORY_HARD_LIMIT = 25_000;
-
-const SearchSpaceSchema = z
- .object({
- shared_memory_md: z.string().optional().default(""),
- })
- .passthrough();
+import { MEMORY_HARD_LIMIT, useTeamMemory } from "@/hooks/use-memory";
interface TeamMemoryManagerProps {
searchSpaceId: number;
}
export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
- const queryClient = useQueryClient();
- const { data: searchSpace, isLoading: loading } = useQuery({
- queryKey: cacheKeys.searchSpaces.detail(searchSpaceId.toString()),
- queryFn: () => searchSpacesApiService.getSearchSpace({ id: searchSpaceId }),
- enabled: !!searchSpaceId,
- });
-
- const { mutateAsync: updateSearchSpace } = useAtomValue(updateSearchSpaceMutationAtom);
-
- const [saving, setSaving] = useState(false);
- const [editQuery, setEditQuery] = useState("");
- const [editing, setEditing] = useState(false);
- const [showInput, setShowInput] = useState(false);
- const textareaRef = useRef(null);
- const inputContainerRef = useRef(null);
-
- const memory = searchSpace?.shared_memory_md || "";
-
- useEffect(() => {
- if (!showInput) return;
-
- const handlePointerDownOutside = (event: MouseEvent | TouchEvent) => {
- const target = event.target;
- if (!(target instanceof Node)) return;
- if (inputContainerRef.current?.contains(target)) return;
-
- setShowInput(false);
- };
-
- document.addEventListener("mousedown", handlePointerDownOutside);
- document.addEventListener("touchstart", handlePointerDownOutside, { passive: true });
-
- return () => {
- document.removeEventListener("mousedown", handlePointerDownOutside);
- document.removeEventListener("touchstart", handlePointerDownOutside);
- };
- }, [showInput]);
+ const { memory, displayMemory, loading, saving, reset } = useTeamMemory(searchSpaceId);
const handleClear = async () => {
try {
- setSaving(true);
- await updateSearchSpace({
- id: searchSpaceId,
- data: { shared_memory_md: "" },
- });
+ await reset();
toast.success("Team memory cleared");
} catch {
toast.error("Failed to clear team memory");
- } finally {
- setSaving(false);
}
};
- const handleEdit = async () => {
- const query = editQuery.trim();
- if (!query) return;
-
- try {
- setEditing(true);
- await baseApiService.post(
- `/api/v1/searchspaces/${searchSpaceId}/memory/edit`,
- SearchSpaceSchema,
- { body: { query } }
- );
- setEditQuery("");
- setShowInput(false);
- await queryClient.invalidateQueries({
- queryKey: cacheKeys.searchSpaces.detail(searchSpaceId.toString()),
- });
- toast.success("Team memory updated");
- } catch {
- toast.error("Failed to edit team memory");
- } finally {
- setEditing(false);
- }
- };
-
- const openInput = () => {
- setShowInput(true);
- requestAnimationFrame(() => textareaRef.current?.focus());
- };
-
const handleDownload = () => {
if (!memory) return;
try {
@@ -143,14 +57,6 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
}
};
- const handleKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- handleEdit();
- }
- };
-
- const displayMemory = memory.replace(/\(\d{4}-\d{2}-\d{2}\)\s*\[(fact|pref|instr)\]\s*/g, "");
const charCount = memory.length;
const getCounterColor = () => {
@@ -204,54 +110,6 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
className="px-5 py-4 text-sm min-h-full"
/>
-
- {showInput ? (
-
@@ -269,7 +127,7 @@ export function TeamMemoryManager({ searchSpaceId }: TeamMemoryManagerProps) {
size="sm"
className="text-xs sm:text-sm"
onClick={handleClear}
- disabled={saving || editing || !memory}
+ disabled={saving || !memory}
>
Reset Memory
Reset
diff --git a/surfsense_web/contracts/types/search-space.types.ts b/surfsense_web/contracts/types/search-space.types.ts
index 7449f82b1..08918e2af 100644
--- a/surfsense_web/contracts/types/search-space.types.ts
+++ b/surfsense_web/contracts/types/search-space.types.ts
@@ -56,7 +56,6 @@ export const updateSearchSpaceRequest = z.object({
description: true,
citations_enabled: true,
qna_custom_instructions: true,
- shared_memory_md: true,
ai_file_sort_enabled: true,
})
.partial(),
diff --git a/surfsense_web/hooks/use-memory.ts b/surfsense_web/hooks/use-memory.ts
new file mode 100644
index 000000000..1f7a51790
--- /dev/null
+++ b/surfsense_web/hooks/use-memory.ts
@@ -0,0 +1,109 @@
+"use client";
+
+import { useCallback, useEffect, useState } from "react";
+import { z } from "zod";
+import { baseApiService } from "@/lib/apis/base-api.service";
+
+export const MEMORY_HARD_LIMIT = 25_000;
+
+const MemoryReadSchema = z.object({
+ memory_md: z.string(),
+});
+
+type MemoryScope = "user" | "team";
+
+interface UseMemoryOptions {
+ scope: MemoryScope;
+ searchSpaceId?: number | null;
+ autoLoad?: boolean;
+}
+
+function getMemoryPath(scope: MemoryScope, searchSpaceId?: number | null) {
+ if (scope === "user") return "/api/v1/users/me/memory";
+ if (!searchSpaceId) throw new Error("searchSpaceId is required for team memory");
+ return `/api/v1/searchspaces/${searchSpaceId}/memory`;
+}
+
+export function stripMemoryDisplayPrefixes(memory: string) {
+ return memory.replace(
+ /^\s*-\s+(?:\(\d{4}-\d{2}-\d{2}\)\s*\[(?:fact|pref|instr)\]\s*|\d{4}-\d{2}-\d{2}:\s*)/gim,
+ "- "
+ );
+}
+
+export function useMemory({ scope, searchSpaceId, autoLoad = true }: UseMemoryOptions) {
+ const [memory, setMemory] = useState("");
+ const [loading, setLoading] = useState(autoLoad);
+ const [saving, setSaving] = useState(false);
+
+ const load = useCallback(async () => {
+ setLoading(true);
+ try {
+ const data = await baseApiService.get(getMemoryPath(scope, searchSpaceId), MemoryReadSchema);
+ setMemory(data.memory_md);
+ return data.memory_md;
+ } finally {
+ setLoading(false);
+ }
+ }, [scope, searchSpaceId]);
+
+ useEffect(() => {
+ if (!autoLoad) return;
+ load().catch(() => {
+ setLoading(false);
+ });
+ }, [autoLoad, load]);
+
+ const save = useCallback(
+ async (memoryMd: string) => {
+ setSaving(true);
+ try {
+ const data = await baseApiService.put(
+ getMemoryPath(scope, searchSpaceId),
+ MemoryReadSchema,
+ {
+ body: { memory_md: memoryMd },
+ }
+ );
+ setMemory(data.memory_md);
+ return data.memory_md;
+ } finally {
+ setSaving(false);
+ }
+ },
+ [scope, searchSpaceId]
+ );
+
+ const reset = useCallback(async () => {
+ setSaving(true);
+ try {
+ const data = await baseApiService.post(
+ `${getMemoryPath(scope, searchSpaceId)}/reset`,
+ MemoryReadSchema
+ );
+ setMemory(data.memory_md);
+ return data.memory_md;
+ } finally {
+ setSaving(false);
+ }
+ }, [scope, searchSpaceId]);
+
+ return {
+ memory,
+ setMemory,
+ displayMemory: stripMemoryDisplayPrefixes(memory),
+ loading,
+ saving,
+ load,
+ save,
+ reset,
+ };
+}
+
+export function useUserMemory(searchSpaceId?: number | null) {
+ return useMemory({ scope: "user", searchSpaceId });
+}
+
+export function useTeamMemory(searchSpaceId?: number | null) {
+ return useMemory({ scope: "team", searchSpaceId });
+}
From cb1cf26ef3436c233fb22b3d0b791f5241552c15 Mon Sep 17 00:00:00 2001
From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com>
Date: Wed, 20 May 2026 02:02:59 +0530
Subject: [PATCH 05/15] feat: improve document editor panel behavior
---
.../atoms/editor/editor-panel.atom.ts | 26 +++-
.../components/documents/DocumentNode.tsx | 132 ++++++++++++++----
.../components/documents/FolderTreeView.tsx | 79 ++++++-----
.../components/editor-panel/editor-panel.tsx | 108 ++++++++++++--
.../layout/ui/right-panel/RightPanel.tsx | 19 ++-
.../layout/ui/sidebar/DocumentsSidebar.tsx | 91 +++++++++++-
.../contracts/enums/connectorIcons.tsx | 4 +
.../contracts/types/document.types.ts | 2 +
8 files changed, 380 insertions(+), 81 deletions(-)
diff --git a/surfsense_web/atoms/editor/editor-panel.atom.ts b/surfsense_web/atoms/editor/editor-panel.atom.ts
index 28563e7d3..c302c66ee 100644
--- a/surfsense_web/atoms/editor/editor-panel.atom.ts
+++ b/surfsense_web/atoms/editor/editor-panel.atom.ts
@@ -3,10 +3,11 @@ import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right
interface EditorPanelState {
isOpen: boolean;
- kind: "document" | "local_file";
+ kind: "document" | "local_file" | "memory";
documentId: number | null;
localFilePath: string | null;
searchSpaceId: number | null;
+ memoryScope: "user" | "team" | null;
title: string | null;
}
@@ -16,6 +17,7 @@ const initialState: EditorPanelState = {
documentId: null,
localFilePath: null,
searchSpaceId: null,
+ memoryScope: null,
title: null,
};
@@ -38,6 +40,12 @@ export const openEditorPanelAtom = atom(
title?: string;
searchSpaceId?: number;
}
+ | {
+ kind: "memory";
+ memoryScope: "user" | "team";
+ title?: string;
+ searchSpaceId?: number;
+ }
) => {
if (!get(editorPanelAtom).isOpen) {
set(preEditorCollapsedAtom, get(rightPanelCollapsedAtom));
@@ -49,6 +57,21 @@ export const openEditorPanelAtom = atom(
documentId: null,
localFilePath: payload.localFilePath,
searchSpaceId: payload.searchSpaceId ?? null,
+ memoryScope: null,
+ title: payload.title ?? null,
+ });
+ set(rightPanelTabAtom, "editor");
+ set(rightPanelCollapsedAtom, false);
+ return;
+ }
+ if (payload.kind === "memory") {
+ set(editorPanelAtom, {
+ isOpen: true,
+ kind: "memory",
+ documentId: null,
+ localFilePath: null,
+ searchSpaceId: payload.searchSpaceId ?? null,
+ memoryScope: payload.memoryScope,
title: payload.title ?? null,
});
set(rightPanelTabAtom, "editor");
@@ -61,6 +84,7 @@ export const openEditorPanelAtom = atom(
documentId: payload.documentId,
localFilePath: null,
searchSpaceId: payload.searchSpaceId,
+ memoryScope: null,
title: payload.title ?? null,
});
set(rightPanelTabAtom, "editor");
diff --git a/surfsense_web/components/documents/DocumentNode.tsx b/surfsense_web/components/documents/DocumentNode.tsx
index 0f3cd4a19..bef2c6ba2 100644
--- a/surfsense_web/components/documents/DocumentNode.tsx
+++ b/surfsense_web/components/documents/DocumentNode.tsx
@@ -9,6 +9,7 @@ import {
MoreHorizontal,
Move,
Pencil,
+ RotateCcw,
Trash2,
} from "lucide-react";
import React, { useCallback, useRef, useState } from "react";
@@ -61,8 +62,13 @@ interface DocumentNodeProps {
onEdit: (doc: DocumentNodeDoc) => void;
onDelete: (doc: DocumentNodeDoc) => void;
onMove: (doc: DocumentNodeDoc) => void;
+ onReset?: (doc: DocumentNodeDoc) => void;
onExport?: (doc: DocumentNodeDoc, format: string) => void;
onVersionHistory?: (doc: DocumentNodeDoc) => void;
+ canDelete?: boolean;
+ canMove?: boolean;
+ canMention?: boolean;
+ canEdit?: boolean;
contextMenuOpen?: boolean;
onContextMenuOpenChange?: (open: boolean) => void;
}
@@ -76,8 +82,13 @@ export const DocumentNode = React.memo(function DocumentNode({
onEdit,
onDelete,
onMove,
+ onReset,
onExport,
onVersionHistory,
+ canDelete = true,
+ canMove = true,
+ canMention = true,
+ canEdit = true,
contextMenuOpen,
onContextMenuOpenChange,
}: DocumentNodeProps) {
@@ -85,8 +96,13 @@ export const DocumentNode = React.memo(function DocumentNode({
const isFailed = statusState === "failed";
const isProcessing = statusState === "pending" || statusState === "processing";
const isUnavailable = isProcessing || isFailed;
- const isSelectable = !isUnavailable;
- const isEditable = EDITABLE_DOCUMENT_TYPES.has(doc.document_type) && !isUnavailable;
+ const isMemoryDocument =
+ doc.document_type === "USER_MEMORY" || doc.document_type === "TEAM_MEMORY";
+ const isSelectable = canMention && !isUnavailable;
+ const isEditable =
+ canEdit &&
+ (isMemoryDocument || EDITABLE_DOCUMENT_TYPES.has(doc.document_type)) &&
+ !isUnavailable;
const handleCheckChange = useCallback(() => {
if (isSelectable) {
@@ -94,13 +110,22 @@ export const DocumentNode = React.memo(function DocumentNode({
}
}, [doc, isMentioned, isSelectable, onToggleChatMention]);
+ const handlePrimaryClick = useCallback(() => {
+ if (canMention) {
+ handleCheckChange();
+ return;
+ }
+ onPreview(doc);
+ }, [canMention, doc, handleCheckChange, onPreview]);
+
const [{ isDragging }, drag] = useDrag(
() => ({
type: DND_TYPES.DOCUMENT,
item: { id: doc.id },
+ canDrag: canMove,
collect: (monitor) => ({ isDragging: monitor.isDragging() }),
}),
- [doc.id]
+ [canMove, doc.id]
);
const [dropdownOpen, setDropdownOpen] = useState(false);
@@ -130,9 +155,11 @@ export const DocumentNode = React.memo(function DocumentNode({
const attachRef = useCallback(
(node: HTMLDivElement | null) => {
(rowRef as React.MutableRefObject
).current = node;
- drag(node);
+ if (canMove) {
+ drag(node);
+ }
},
- [drag]
+ [canMove, drag]
);
return (
@@ -187,12 +214,39 @@ export const DocumentNode = React.memo(function DocumentNode({
);
}
return (
- e.stopPropagation()}
- className="h-3.5 w-3.5 shrink-0"
- />
+ <>
+ {isMemoryDocument ? (
+
+ ) : canMention ? (
+ e.stopPropagation()}
+ className="h-3.5 w-3.5 shrink-0"
+ />
+ ) : (
+
+ {getDocumentTypeIcon(
+ doc.document_type as DocumentTypeEnum,
+ "h-3.5 w-3.5 text-muted-foreground"
+ )}
+
+ )}
+ >
);
})()}
@@ -205,8 +259,8 @@ export const DocumentNode = React.memo(function DocumentNode({
@@ -708,7 +788,9 @@ function DesktopEditorPanel() {
const hasTarget =
panelState.kind === "document"
? !!panelState.documentId && !!panelState.searchSpaceId
- : !!panelState.localFilePath;
+ : panelState.kind === "local_file"
+ ? !!panelState.localFilePath
+ : !!panelState.memoryScope;
if (!panelState.isOpen || !hasTarget) return null;
return (
@@ -717,6 +799,7 @@ function DesktopEditorPanel() {
kind={panelState.kind}
documentId={panelState.documentId ?? undefined}
localFilePath={panelState.localFilePath ?? undefined}
+ memoryScope={panelState.memoryScope ?? undefined}
searchSpaceId={panelState.searchSpaceId ?? undefined}
title={panelState.title}
onClose={closePanel}
@@ -734,7 +817,7 @@ function MobileEditorDrawer() {
const hasTarget =
panelState.kind === "document"
? !!panelState.documentId && !!panelState.searchSpaceId
- : !!panelState.localFilePath;
+ : !!panelState.memoryScope;
if (!hasTarget) return null;
return (
@@ -756,6 +839,7 @@ function MobileEditorDrawer() {
kind={panelState.kind}
documentId={panelState.documentId ?? undefined}
localFilePath={panelState.localFilePath ?? undefined}
+ memoryScope={panelState.memoryScope ?? undefined}
searchSpaceId={panelState.searchSpaceId ?? undefined}
title={panelState.title}
/>
@@ -771,7 +855,9 @@ export function EditorPanel() {
const hasTarget =
panelState.kind === "document"
? !!panelState.documentId && !!panelState.searchSpaceId
- : !!panelState.localFilePath;
+ : panelState.kind === "local_file"
+ ? !!panelState.localFilePath
+ : !!panelState.memoryScope;
if (!panelState.isOpen || !hasTarget) return null;
if (!isDesktop && panelState.kind === "local_file") return null;
@@ -789,7 +875,9 @@ export function MobileEditorPanel() {
const hasTarget =
panelState.kind === "document"
? !!panelState.documentId && !!panelState.searchSpaceId
- : !!panelState.localFilePath;
+ : panelState.kind === "local_file"
+ ? !!panelState.localFilePath
+ : !!panelState.memoryScope;
if (isDesktop || !panelState.isOpen || !hasTarget || panelState.kind === "local_file")
return null;
diff --git a/surfsense_web/components/layout/ui/right-panel/RightPanel.tsx b/surfsense_web/components/layout/ui/right-panel/RightPanel.tsx
index b379e58e3..5a7588979 100644
--- a/surfsense_web/components/layout/ui/right-panel/RightPanel.tsx
+++ b/surfsense_web/components/layout/ui/right-panel/RightPanel.tsx
@@ -103,7 +103,11 @@ export function RightPanelToggleButton({
const reportOpen = reportState.isOpen && !!reportState.reportId;
const editorOpen =
editorState.isOpen &&
- (editorState.kind === "document" ? !!editorState.documentId : !!editorState.localFilePath);
+ (editorState.kind === "document"
+ ? !!editorState.documentId
+ : editorState.kind === "memory"
+ ? !!editorState.memoryScope
+ : !!editorState.localFilePath);
const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave;
const citationOpen = citationState.isOpen && citationState.chunkId != null;
const hasContent = documentsOpen || reportOpen || editorOpen || hitlEditOpen || citationOpen;
@@ -151,7 +155,11 @@ export function RightPanelExpandButton() {
const reportOpen = reportState.isOpen && !!reportState.reportId;
const editorOpen =
editorState.isOpen &&
- (editorState.kind === "document" ? !!editorState.documentId : !!editorState.localFilePath);
+ (editorState.kind === "document"
+ ? !!editorState.documentId
+ : editorState.kind === "memory"
+ ? !!editorState.memoryScope
+ : !!editorState.localFilePath);
const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave;
const citationOpen = citationState.isOpen && citationState.chunkId != null;
const hasContent = documentsOpen || reportOpen || editorOpen || hitlEditOpen || citationOpen;
@@ -193,7 +201,11 @@ export function RightPanel({
const reportOpen = reportState.isOpen && !!reportState.reportId;
const editorOpen =
editorState.isOpen &&
- (editorState.kind === "document" ? !!editorState.documentId : !!editorState.localFilePath);
+ (editorState.kind === "document"
+ ? !!editorState.documentId
+ : editorState.kind === "memory"
+ ? !!editorState.memoryScope
+ : !!editorState.localFilePath);
const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave;
const citationOpen = citationState.isOpen && citationState.chunkId != null;
@@ -292,6 +304,7 @@ export function RightPanel({
kind={editorState.kind}
documentId={editorState.documentId ?? undefined}
localFilePath={editorState.localFilePath ?? undefined}
+ memoryScope={editorState.memoryScope ?? undefined}
searchSpaceId={editorState.searchSpaceId ?? undefined}
title={editorState.title}
onClose={closeEditor}
diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx
index cdb757cb2..0c37d003c 100644
--- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx
+++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx
@@ -88,7 +88,31 @@ const DesktopLocalTabContent = dynamic(
{ ssr: false }
);
-const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = ["SURFSENSE_DOCS"];
+const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = [
+ "SURFSENSE_DOCS",
+ "USER_MEMORY",
+ "TEAM_MEMORY",
+];
+const MEMORY_DOCUMENTS: DocumentNodeDoc[] = [
+ {
+ id: -1001,
+ title: "MEMORY.md",
+ document_type: "USER_MEMORY",
+ folderId: null,
+ status: { state: "ready" },
+ },
+ {
+ id: -1002,
+ title: "TEAM_MEMORY.md",
+ document_type: "TEAM_MEMORY",
+ folderId: null,
+ status: { state: "ready" },
+ },
+];
+
+function isMemoryDocument(doc: { document_type: string }) {
+ return doc.document_type === "USER_MEMORY" || doc.document_type === "TEAM_MEMORY";
+}
const LOCAL_FILESYSTEM_TRUST_KEY = "surfsense.local-filesystem-trust.v1";
const MAX_LOCAL_FILESYSTEM_ROOTS = 10;
@@ -879,6 +903,7 @@ function AuthenticatedDocumentsSidebarBase({
const handleToggleChatMention = useCallback(
(doc: { id: number; title: string; document_type: string }, isMentioned: boolean) => {
+ if (isMemoryDocument(doc)) return;
const key = getMentionDocKey({ ...doc, kind: "doc" });
if (isMentioned) {
setSidebarDocs((prev) => prev.filter((d) => getMentionDocKey(d) !== key));
@@ -927,11 +952,66 @@ function AuthenticatedDocumentsSidebarBase({
[treeFolders, setSidebarDocs]
);
+ const treeDocumentsWithMemory = useMemo(
+ () => [...MEMORY_DOCUMENTS, ...treeDocuments],
+ [treeDocuments]
+ );
+
const searchFilteredDocuments = useMemo(() => {
const query = debouncedSearch.trim().toLowerCase();
- if (!query) return treeDocuments;
- return treeDocuments.filter((d) => d.title.toLowerCase().includes(query));
- }, [treeDocuments, debouncedSearch]);
+ if (!query) return treeDocumentsWithMemory;
+ return treeDocumentsWithMemory.filter((d) => d.title.toLowerCase().includes(query));
+ }, [treeDocumentsWithMemory, debouncedSearch]);
+
+ const openMemoryDocument = useCallback(
+ (doc: DocumentNodeDoc) => {
+ if (doc.document_type === "USER_MEMORY") {
+ openEditorPanel({
+ kind: "memory",
+ memoryScope: "user",
+ searchSpaceId,
+ title: doc.title,
+ });
+ return true;
+ }
+ if (doc.document_type === "TEAM_MEMORY") {
+ openEditorPanel({
+ kind: "memory",
+ memoryScope: "team",
+ searchSpaceId,
+ title: doc.title,
+ });
+ return true;
+ }
+ return false;
+ },
+ [openEditorPanel, searchSpaceId]
+ );
+
+ const handleResetMemoryDocument = useCallback(
+ async (doc: DocumentNodeDoc) => {
+ if (!isMemoryDocument(doc)) return;
+ if (!window.confirm(`Reset ${doc.title.toLowerCase()}? This clears the memory document.`)) {
+ return;
+ }
+ const endpoint =
+ doc.document_type === "USER_MEMORY"
+ ? `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/users/me/memory/reset`
+ : `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}/memory/reset`;
+ try {
+ const response = await authenticatedFetch(endpoint, { method: "POST" });
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({ detail: "Reset failed" }));
+ throw new Error(errorData.detail || "Reset failed");
+ }
+ toast.success(`${doc.title} reset`);
+ openMemoryDocument(doc);
+ } catch (error) {
+ toast.error((error as Error)?.message || `Failed to reset ${doc.title.toLowerCase()}`);
+ }
+ },
+ [openMemoryDocument, searchSpaceId]
+ );
const typeCounts = useMemo(() => {
const counts: Partial