Merge remote-tracking branch 'upstream/dev' into feat/local-folder-sync

This commit is contained in:
Anish Sarkar 2026-04-03 11:42:43 +05:30
commit 62b44889d1
66 changed files with 3359 additions and 2626 deletions

View file

@ -1,18 +1,39 @@
"use client";
import { useAtomValue, useSetAtom } from "jotai";
import { FileQuestionMark, RefreshCw, XIcon } from "lucide-react";
import { Download, FileQuestionMark, FileText, Loader2, RefreshCw, XIcon } from "lucide-react";
import dynamic from "next/dynamic";
import { useCallback, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { closeEditorPanelAtom, editorPanelAtom } from "@/atoms/editor/editor-panel.atom";
import { VersionHistoryButton } from "@/components/documents/version-history";
import { MarkdownViewer } from "@/components/markdown-viewer";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Drawer, DrawerContent, DrawerHandle, DrawerTitle } from "@/components/ui/drawer";
import { Skeleton } from "@/components/ui/skeleton";
import { useMediaQuery } from "@/hooks/use-media-query";
import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils";
const PlateEditor = dynamic(
() => import("@/components/editor/plate-editor").then((m) => ({ default: m.PlateEditor })),
{ ssr: false, loading: () => <Skeleton className="h-64 w-full" /> }
);
const LARGE_DOCUMENT_THRESHOLD = 2 * 1024 * 1024; // 2MB
interface EditorContent {
document_id: number;
title: string;
document_type?: string;
source_markdown: string;
content_size_bytes?: number;
chunk_count?: number;
truncated?: boolean;
}
const EDITABLE_DOCUMENT_TYPES = new Set(["FILE", "NOTE"]);
function EditorPanelSkeleton() {
return (
<div className="space-y-6 p-6">
@ -33,19 +54,6 @@ function EditorPanelSkeleton() {
);
}
const PlateEditor = dynamic(
() => import("@/components/editor/plate-editor").then((m) => ({ default: m.PlateEditor })),
{ ssr: false, loading: () => null }
);
interface EditorContent {
document_id: number;
title: string;
document_type?: string;
source_markdown: string;
}
const EDITABLE_DOCUMENT_TYPES = new Set(["FILE", "NOTE"]);
export function EditorPanelContent({
documentId,
@ -62,6 +70,7 @@ export function EditorPanelContent({
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [saving, setSaving] = useState(false);
const [downloading, setDownloading] = useState(false);
const [editedMarkdown, setEditedMarkdown] = useState<string | null>(null);
const markdownRef = useRef<string>("");
@ -69,6 +78,8 @@ export function EditorPanelContent({
const changeCountRef = useRef(0);
const [displayTitle, setDisplayTitle] = useState(title || "Untitled");
const isLargeDocument = (editorDoc?.content_size_bytes ?? 0) > LARGE_DOCUMENT_THRESHOLD;
useEffect(() => {
let cancelled = false;
setIsLoading(true);
@ -86,10 +97,12 @@ export function EditorPanelContent({
}
try {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`,
{ method: "GET" }
const url = new URL(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/editor-content`
);
url.searchParams.set("max_length", String(LARGE_DOCUMENT_THRESHOLD));
const response = await authenticatedFetch(url.toString(), { method: "GET" });
if (cancelled) return;
@ -175,7 +188,7 @@ export function EditorPanelContent({
}, [documentId, searchSpaceId]);
const isEditableType = editorDoc
? EDITABLE_DOCUMENT_TYPES.has(editorDoc.document_type ?? "")
? EDITABLE_DOCUMENT_TYPES.has(editorDoc.document_type ?? "") && !isLargeDocument
: false;
return (
@ -223,6 +236,59 @@ export function EditorPanelContent({
<p className="text-sm text-muted-foreground">{error || "An unknown error occurred"}</p>
</div>
</div>
) : isLargeDocument ? (
<div className="h-full overflow-y-auto px-5 py-4">
<Alert className="mb-4">
<FileText className="size-4" />
<AlertDescription className="flex items-center justify-between gap-4">
<span>
This document is too large for the editor (
{Math.round((editorDoc.content_size_bytes ?? 0) / 1024 / 1024)}MB,{" "}
{editorDoc.chunk_count ?? 0} chunks). Showing a preview below.
</span>
<Button
variant="outline"
size="sm"
className="shrink-0 gap-1.5"
disabled={downloading}
onClick={async () => {
setDownloading(true);
try {
const response = await authenticatedFetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/search-spaces/${searchSpaceId}/documents/${documentId}/download-markdown`,
{ method: "GET" }
);
if (!response.ok) throw new Error("Download failed");
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
const disposition = response.headers.get("content-disposition");
const match = disposition?.match(/filename="(.+)"/);
a.download = match?.[1] ?? `${editorDoc.title || "document"}.md`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
toast.success("Download started");
} catch {
toast.error("Failed to download document");
} finally {
setDownloading(false);
}
}}
>
{downloading ? (
<Loader2 className="size-3.5 animate-spin" />
) : (
<Download className="size-3.5" />
)}
{downloading ? "Preparing..." : "Download .md"}
</Button>
</AlertDescription>
</Alert>
<MarkdownViewer content={editorDoc.source_markdown} />
</div>
) : isEditableType ? (
<PlateEditor
key={documentId}