mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-25 19:15:18 +02:00
Merge remote-tracking branch 'upstream/dev' into feat/opentelemetry
This commit is contained in:
commit
7a0de6d377
79 changed files with 1992 additions and 2296 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -82,13 +82,50 @@ import { uploadFolderScan } from "@/lib/folder-sync-upload";
|
|||
import { getSupportedExtensionsSet } from "@/lib/supported-extensions";
|
||||
import { queries } from "@/zero/queries/index";
|
||||
import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
|
||||
import { BACKEND_URL } from "@/lib/env-config";
|
||||
|
||||
const DesktopLocalTabContent = dynamic(
|
||||
() => import("./DesktopLocalTabContent").then((mod) => mod.DesktopLocalTabContent),
|
||||
{ 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";
|
||||
}
|
||||
|
||||
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 MAX_LOCAL_FILESYSTEM_ROOTS = 10;
|
||||
|
||||
|
|
@ -716,7 +753,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
.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}`,
|
||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/export?folder_id=${ctx.folder.id}`,
|
||||
`${safeName}.zip`
|
||||
);
|
||||
toast.success(`Folder "${ctx.folder.name}" exported`);
|
||||
|
|
@ -768,7 +805,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
.trim()
|
||||
.slice(0, 80) || "folder";
|
||||
await doExport(
|
||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/export?folder_id=${folder.id}`,
|
||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/export?folder_id=${folder.id}`,
|
||||
`${safeName}.zip`
|
||||
);
|
||||
toast.success(`Folder "${folder.name}" exported`);
|
||||
|
|
@ -784,6 +821,30 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
|
||||
const handleExportDocument = useCallback(
|
||||
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 =
|
||||
doc.title
|
||||
.replace(/[^a-zA-Z0-9 _-]/g, "_")
|
||||
|
|
@ -793,7 +854,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
|
||||
try {
|
||||
const response = await authenticatedFetch(
|
||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${doc.id}/export?format=${format}`,
|
||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${doc.id}/export?format=${format}`,
|
||||
{ method: "GET" }
|
||||
);
|
||||
|
||||
|
|
@ -879,6 +940,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 +989,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<Record<string, number>> = {};
|
||||
|
|
@ -1169,6 +1286,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
onCreateFolder={handleCreateFolder}
|
||||
searchQuery={debouncedSearch.trim() || undefined}
|
||||
onPreviewDocument={(doc) => {
|
||||
if (openMemoryDocument(doc)) return;
|
||||
openEditorPanel({
|
||||
documentId: doc.id,
|
||||
searchSpaceId,
|
||||
|
|
@ -1176,6 +1294,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
});
|
||||
}}
|
||||
onEditDocument={(doc) => {
|
||||
if (openMemoryDocument(doc)) return;
|
||||
openEditorPanel({
|
||||
documentId: doc.id,
|
||||
searchSpaceId,
|
||||
|
|
@ -1184,6 +1303,7 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
}}
|
||||
onDeleteDocument={(doc) => handleDeleteDocument(doc.id)}
|
||||
onMoveDocument={handleMoveDocument}
|
||||
onResetDocument={handleResetMemoryDocument}
|
||||
onExportDocument={handleExportDocument}
|
||||
onVersionHistory={(doc) => setVersionDocId(doc.id)}
|
||||
activeTypes={activeTypes}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
||||
|
||||
import { BACKEND_URL, BACKEND_URL } from "@/lib/env-config";
|
||||
const LARGE_DOCUMENT_THRESHOLD = 2 * 1024 * 1024; // 2MB
|
||||
|
||||
interface DocumentContent {
|
||||
|
|
@ -85,7 +85,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
|
||||
try {
|
||||
const url = new URL(
|
||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`
|
||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`
|
||||
);
|
||||
url.searchParams.set("max_length", String(LARGE_DOCUMENT_THRESHOLD));
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
setSaving(true);
|
||||
try {
|
||||
const response = await authenticatedFetch(
|
||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`,
|
||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/save`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
|
@ -285,7 +285,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
setDownloading(true);
|
||||
try {
|
||||
const response = await authenticatedFetch(
|
||||
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/download-markdown`,
|
||||
`${BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/download-markdown`,
|
||||
{ method: "GET" }
|
||||
);
|
||||
if (!response.ok) throw new Error("Download failed");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue