mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-15 18:25:18 +02:00
fix: improve document loading error handling and UI feedback for processing state
This commit is contained in:
parent
9d6d818712
commit
d7dd6db1b9
7 changed files with 69 additions and 24 deletions
|
|
@ -127,9 +127,16 @@ async def get_editor_content(
|
|||
chunks = sorted(document.chunks, key=lambda c: c.id)
|
||||
|
||||
if not chunks:
|
||||
doc_status = document.status or {}
|
||||
state = doc_status.get("state", "ready") if isinstance(doc_status, dict) else "ready"
|
||||
if state in ("pending", "processing"):
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="This document is still being processed. Please wait a moment and try again.",
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="This document has no content and cannot be edited. Please re-upload to enable editing.",
|
||||
detail="This document has no viewable content yet. It may still be syncing. Try again in a few seconds, or re-upload if the issue persists.",
|
||||
)
|
||||
|
||||
markdown_content = "\n\n".join(chunk.content for chunk in chunks)
|
||||
|
|
@ -137,7 +144,7 @@ async def get_editor_content(
|
|||
if not markdown_content.strip():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="This document has empty content and cannot be edited.",
|
||||
detail="This document appears to be empty. Try re-uploading or editing it to add content.",
|
||||
)
|
||||
|
||||
# Persist the lazy migration
|
||||
|
|
|
|||
|
|
@ -748,6 +748,7 @@ export function DocumentsTableShell({
|
|||
onClick={() =>
|
||||
onOpenInTab ? onOpenInTab(doc) : handleViewDocument(doc)
|
||||
}
|
||||
disabled={isBeingProcessed}
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
Open
|
||||
|
|
@ -1020,6 +1021,10 @@ export function DocumentsTableShell({
|
|||
<Button
|
||||
variant="secondary"
|
||||
className="justify-start gap-2"
|
||||
disabled={
|
||||
mobileActionDoc?.status?.state === "pending" ||
|
||||
mobileActionDoc?.status?.state === "processing"
|
||||
}
|
||||
onClick={() => {
|
||||
if (mobileActionDoc) handleViewDocument(mobileActionDoc);
|
||||
setMobileActionDoc(null);
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-40" onClick={(e) => e.stopPropagation()}>
|
||||
<DropdownMenuItem onClick={() => onPreview(doc)}>
|
||||
<DropdownMenuItem onClick={() => onPreview(doc)} disabled={isProcessing}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Open
|
||||
</DropdownMenuItem>
|
||||
|
|
@ -259,7 +259,7 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
|
||||
{contextMenuOpen && (
|
||||
<ContextMenuContent className="w-40" onClick={(e) => e.stopPropagation()}>
|
||||
<ContextMenuItem onClick={() => onPreview(doc)}>
|
||||
<ContextMenuItem onClick={() => onPreview(doc)} disabled={isProcessing}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Open
|
||||
</ContextMenuItem>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useAtom } from "jotai";
|
||||
import { CirclePlus } from "lucide-react";
|
||||
import { Search } from "lucide-react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
|
|
@ -250,8 +250,9 @@ export function FolderTreeView({
|
|||
if (treeNodes.length === 0 && (activeTypes.length > 0 || searchQuery)) {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-3 px-4 py-12 text-muted-foreground">
|
||||
<CirclePlus className="h-10 w-10 rotate-45" />
|
||||
<p className="text-sm">No matching documents</p>
|
||||
<Search className="h-10 w-10" />
|
||||
<p className="text-sm text-muted-foreground">No matching documents</p>
|
||||
<p className="text-xs text-muted-foreground/70 mt-1">Try a different search term</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { AlertCircle, XIcon } from "lucide-react";
|
||||
import { FileQuestionMark, RefreshCw, XIcon } from "lucide-react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
|
@ -200,10 +200,22 @@ export function EditorPanelContent({
|
|||
<EditorPanelSkeleton />
|
||||
) : error || !editorDoc ? (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-3 p-6 text-center">
|
||||
<AlertCircle className="size-8 text-destructive" />
|
||||
<div>
|
||||
<p className="font-medium text-foreground">Failed to load document</p>
|
||||
<p className="text-sm text-red-500 mt-1">{error || "An unknown error occurred"}</p>
|
||||
{error?.toLowerCase().includes("still being processed") ? (
|
||||
<div className="rounded-full bg-muted/50 p-3">
|
||||
<RefreshCw className="size-6 text-muted-foreground animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-full bg-muted/50 p-3">
|
||||
<FileQuestionMark className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-1 max-w-xs">
|
||||
<p className="font-medium text-foreground">
|
||||
{error?.toLowerCase().includes("still being processed")
|
||||
? "Document is processing"
|
||||
: "Document unavailable"}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{error || "An unknown error occurred"}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : isEditableType ? (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { AlertCircle, Pencil } from "lucide-react";
|
||||
import { FileQuestionMark, PenLine, RefreshCw } from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { PlateEditor } from "@/components/editor/plate-editor";
|
||||
|
|
@ -160,15 +160,35 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
if (isLoading) return <DocumentSkeleton />;
|
||||
|
||||
if (error || !doc) {
|
||||
const isProcessing = error?.toLowerCase().includes("still being processed");
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-3 p-6 text-center">
|
||||
<AlertCircle className="size-10 text-destructive" />
|
||||
<div>
|
||||
<p className="font-medium text-foreground text-lg">Failed to load document</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-4 p-8 text-center">
|
||||
<div className="rounded-full bg-muted/50 p-4">
|
||||
{isProcessing ? (
|
||||
<RefreshCw className="size-8 text-muted-foreground animate-spin" />
|
||||
) : (
|
||||
<FileQuestionMark className="size-8 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1.5 max-w-sm">
|
||||
<p className="font-semibold text-foreground text-lg">
|
||||
{isProcessing ? "Document is processing" : "Document unavailable"}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{error || "An unknown error occurred"}
|
||||
</p>
|
||||
</div>
|
||||
{!isProcessing && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="mt-1 gap-1.5"
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
<RefreshCw className="size-3.5" />
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -229,7 +249,7 @@ export function DocumentTabContent({ documentId, searchSpaceId, title }: Documen
|
|||
onClick={() => setIsEditing(true)}
|
||||
className="gap-1.5"
|
||||
>
|
||||
<Pencil className="size-3.5" />
|
||||
<PenLine className="size-3.5" />
|
||||
Edit
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { BookOpen, ChevronDown, ExternalLink, FileText, Hash, Sparkles, X } from "lucide-react";
|
||||
import { BookOpen, ChevronDown, ExternalLink, FileQuestionMark, FileText, Hash, Sparkles, X } from "lucide-react";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type React from "react";
|
||||
|
|
@ -392,12 +392,12 @@ export function SourceDetailPanel({
|
|||
animate={{ opacity: 1, scale: 1 }}
|
||||
className="flex flex-col items-center gap-4 text-center px-6"
|
||||
>
|
||||
<div className="w-20 h-20 rounded-full bg-destructive/10 flex items-center justify-center">
|
||||
<X className="h-10 w-10 text-destructive" />
|
||||
<div className="w-20 h-20 rounded-full bg-muted/50 flex items-center justify-center">
|
||||
<FileQuestionMark className="h-10 w-10 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-destructive text-lg">
|
||||
Failed to load document
|
||||
<p className="font-semibold text-foreground text-lg">
|
||||
Document unavailable
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground mt-2 max-w-md">
|
||||
{documentByChunkFetchingError.message ||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue