mirror of
https://github.com/james-m-jordan/morphik-core.git
synced 2025-05-09 19:32:38 +00:00
UI: Fix document loading speed, skeleton
This commit is contained in:
parent
e8b7f6789b
commit
b9bc1d3566
@ -20,6 +20,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
// Dynamically import ForceGraphComponent to avoid SSR issues
|
// Dynamically import ForceGraphComponent to avoid SSR issues
|
||||||
const ForceGraphComponent = dynamic(() => import('@/components/ForceGraphComponent'), { ssr: false });
|
const ForceGraphComponent = dynamic(() => import('@/components/ForceGraphComponent'), { ssr: false });
|
||||||
@ -516,7 +517,15 @@ const GraphSection: React.FC<GraphSectionProps> = ({ apiBaseUrl, onSelectGraph,
|
|||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex justify-center items-center p-8">
|
<div className="flex justify-center items-center p-8">
|
||||||
<div className="animate-spin rounded-full h-10 w-10 border-b-2 border-primary"></div>
|
{/* Skeleton Loader for Graph List */}
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-4 py-2 w-full">
|
||||||
|
{[...Array(12)].map((_, i) => (
|
||||||
|
<div key={i} className="flex flex-col items-center p-2 border border-transparent rounded-md">
|
||||||
|
<Skeleton className="h-12 w-12 mb-2 rounded-md" />
|
||||||
|
<Skeleton className="h-4 w-20 rounded-md" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : graphs.length === 0 ? (
|
) : graphs.length === 0 ? (
|
||||||
<div className="text-center p-8 border-2 border-dashed rounded-lg mt-4">
|
<div className="text-center p-8 border-2 border-dashed rounded-lg mt-4">
|
||||||
|
@ -8,6 +8,7 @@ import DocumentDetail from './DocumentDetail';
|
|||||||
import FolderList from './FolderList';
|
import FolderList from './FolderList';
|
||||||
import { UploadDialog, useUploadDialog } from './UploadDialog';
|
import { UploadDialog, useUploadDialog } from './UploadDialog';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
import { Document, Folder } from '@/components/types';
|
import { Document, Folder } from '@/components/types';
|
||||||
|
|
||||||
@ -237,7 +238,7 @@ const DocumentsSection: React.FC<DocumentsSectionProps> = ({
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
// Dependencies: URL, auth, selected folder, and the folder list itself
|
// Dependencies: URL, auth, selected folder, and the folder list itself
|
||||||
}, [effectiveApiUrl, authToken, selectedFolder, folders, documents.length]);
|
}, [effectiveApiUrl, authToken, selectedFolder, folders]);
|
||||||
|
|
||||||
// Fetch all folders
|
// Fetch all folders
|
||||||
const fetchFolders = useCallback(async () => {
|
const fetchFolders = useCallback(async () => {
|
||||||
@ -282,47 +283,103 @@ const DocumentsSection: React.FC<DocumentsSectionProps> = ({
|
|||||||
|
|
||||||
// Fetch documents when folders are loaded or selectedFolder changes
|
// Fetch documents when folders are loaded or selectedFolder changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!foldersLoading && folders.length > 0) {
|
const effectSource = 'folders loaded or selectedFolder changed';
|
||||||
|
console.log(`Effect triggered: ${effectSource}, foldersLoading: ${foldersLoading}, folders count: ${folders.length}, selectedFolder: ${selectedFolder}`);
|
||||||
|
|
||||||
|
// Guard against running when folders are still loading
|
||||||
|
if (foldersLoading) {
|
||||||
|
console.log(`Effect (${effectSource}): Folders still loading, skipping.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the case where there are no folders at all
|
||||||
|
if (folders.length === 0 && selectedFolder === null) {
|
||||||
|
console.log(`Effect (${effectSource}): No folders found, clearing documents and stopping loading.`);
|
||||||
|
setDocuments([]);
|
||||||
|
setLoading(false); // Ensure loading is off
|
||||||
|
isInitialMount.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed if folders are loaded
|
||||||
|
if (folders.length >= 0) { // Check >= 0 to handle empty folders array correctly
|
||||||
// Avoid fetching documents on initial mount if selectedFolder is null
|
// Avoid fetching documents on initial mount if selectedFolder is null
|
||||||
// unless initialFolder was specified
|
// unless initialFolder was specified
|
||||||
if (isInitialMount.current && selectedFolder === null && !initialFolder) {
|
if (isInitialMount.current && selectedFolder === null && !initialFolder) {
|
||||||
console.log('Initial mount with no folder selected, skipping document fetch');
|
console.log(`Effect (${effectSource}): Initial mount with no folder selected, skipping document fetch`);
|
||||||
isInitialMount.current = false;
|
isInitialMount.current = false;
|
||||||
|
// Ensure loading is false if we skip fetching
|
||||||
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Folders loaded or selectedFolder changed, fetching documents...', selectedFolder);
|
|
||||||
fetchDocuments('folders loaded or selectedFolder changed');
|
|
||||||
isInitialMount.current = false; // Mark initial mount as complete
|
|
||||||
} else if (!foldersLoading && folders.length === 0 && selectedFolder === null) {
|
|
||||||
// Handle case where there are no folders at all
|
|
||||||
console.log('No folders found, clearing documents and stopping loading.');
|
|
||||||
setDocuments([]);
|
|
||||||
setLoading(false);
|
|
||||||
isInitialMount.current = false;
|
|
||||||
}
|
|
||||||
}, [foldersLoading, folders, selectedFolder, fetchDocuments, initialFolder]);
|
|
||||||
|
|
||||||
// Poll for document status if any document is processing
|
// If we reach here, we intend to fetch documents
|
||||||
|
console.log(`Effect (${effectSource}): Preparing to fetch documents for folder: ${selectedFolder}`);
|
||||||
|
|
||||||
|
// Wrap the async operation
|
||||||
|
const fetchWrapper = async () => {
|
||||||
|
// Explicitly set loading true *before* the async call within this effect's scope
|
||||||
|
// Note: fetchDocuments might also set this, but we ensure it's set here.
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await fetchDocuments(effectSource);
|
||||||
|
// If fetchDocuments completes successfully, it will set loading = false in its finally block.
|
||||||
|
// No need to set it here again in the try block.
|
||||||
|
console.log(`Effect (${effectSource}): fetchDocuments call completed.`);
|
||||||
|
} catch (error) {
|
||||||
|
// Catch potential errors *from* the await fetchDocuments call itself, though
|
||||||
|
// fetchDocuments has internal handling. This is an extra safeguard.
|
||||||
|
console.error(`Effect (${effectSource}): Error occurred during fetchDocuments call:`, error);
|
||||||
|
showAlert(`Error updating documents: ${error instanceof Error ? error.message : 'Unknown error'}`, { type: 'error' });
|
||||||
|
// Ensure loading is turned off even if fetchDocuments had an issue before its finally.
|
||||||
|
setLoading(false);
|
||||||
|
} finally {
|
||||||
|
// **User Request:** Explicitly set loading to false within the effect's finally block.
|
||||||
|
// This acts as a safeguard, ensuring loading is false after the attempt,
|
||||||
|
// regardless of fetchDocuments' internal state management.
|
||||||
|
console.log(`Effect (${effectSource}): Finally block reached, ensuring loading is false.`);
|
||||||
|
setLoading(false);
|
||||||
|
isInitialMount.current = false; // Mark initial mount as complete here
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchWrapper();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.log(`Effect (${effectSource}): Condition not met (folders length < 0 ?), should not happen.`);
|
||||||
|
setLoading(false); // Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [foldersLoading, folders, selectedFolder, fetchDocuments, initialFolder]); // Keep fetchDocuments dependency
|
||||||
|
|
||||||
|
// Poll for document status if any document is processing *and* user is not viewing details
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hasProcessing = documents.some(
|
const hasProcessing = documents.some(
|
||||||
(doc) => doc.system_metadata?.status === 'processing'
|
(doc) => doc.system_metadata?.status === 'processing'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasProcessing) {
|
// Only poll if there are processing documents AND no document detail view is open
|
||||||
console.log('Polling for document status...');
|
if (hasProcessing && selectedDocument === null) {
|
||||||
|
console.log('Polling for document status (list view active)...');
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
console.log('Polling interval: calling refreshDocuments');
|
console.log('Polling interval: calling refreshDocuments');
|
||||||
refreshDocuments(); // Fetch documents again to check status
|
refreshDocuments(); // Fetch documents again to check status
|
||||||
}, 5000); // Poll every 5 seconds
|
}, 5000); // Poll every 5 seconds
|
||||||
|
|
||||||
// Cleanup function to clear the interval when the component unmounts
|
|
||||||
// or when there are no more processing documents
|
|
||||||
return () => {
|
return () => {
|
||||||
console.log('Clearing polling interval');
|
console.log('Clearing polling interval (list view or processing done)');
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
// Log why polling is not active
|
||||||
|
if (hasProcessing && selectedDocument !== null) {
|
||||||
|
console.log('Polling paused: Document detail view is open.');
|
||||||
|
} else if (!hasProcessing) {
|
||||||
|
console.log('Polling stopped: No documents are processing.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [documents, refreshDocuments]);
|
// Add selectedDocument to dependencies
|
||||||
|
}, [documents, refreshDocuments, selectedDocument]);
|
||||||
|
|
||||||
// Collapse sidebar when a folder is selected
|
// Collapse sidebar when a folder is selected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1017,68 +1074,89 @@ const DocumentsSection: React.FC<DocumentsSectionProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedFolder && documents.length === 0 && !loading ? (
|
{/* Folder Grid View (selectedFolder is null) */}
|
||||||
<div className="text-center py-8 border border-dashed rounded-lg flex-1 flex items-center justify-center">
|
{selectedFolder === null ? (
|
||||||
<div>
|
|
||||||
<Upload className="mx-auto h-12 w-12 mb-2 text-muted-foreground" />
|
|
||||||
<p className="text-muted-foreground">Drag and drop files here to upload to this folder.</p>
|
|
||||||
<p className="text-xs text-muted-foreground mt-2">Or use the upload button in the top right.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : selectedFolder && loading ? (
|
|
||||||
<div className="text-center py-8 flex-1 flex items-center justify-center">
|
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mb-4"></div>
|
|
||||||
<p className="text-muted-foreground">Loading documents...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : selectedFolder === null ? (
|
|
||||||
<div className="flex flex-col gap-4 flex-1">
|
<div className="flex flex-col gap-4 flex-1">
|
||||||
<FolderList
|
{/* Skeleton for Folder List loading state */}
|
||||||
folders={folders}
|
{foldersLoading ? (
|
||||||
selectedFolder={selectedFolder}
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 p-4">
|
||||||
setSelectedFolder={setSelectedFolder}
|
{[...Array(8)].map((_, i) => (
|
||||||
apiBaseUrl={effectiveApiUrl}
|
<div key={i} className="border rounded-lg p-4 flex flex-col items-center justify-center h-32">
|
||||||
authToken={authToken}
|
<Skeleton className="h-8 w-8 mb-2 rounded-md" />
|
||||||
refreshFolders={fetchFolders}
|
<Skeleton className="h-4 w-20" />
|
||||||
loading={foldersLoading}
|
</div>
|
||||||
refreshAction={handleRefresh}
|
))}
|
||||||
selectedDocuments={selectedDocuments}
|
</div>
|
||||||
handleDeleteMultipleDocuments={handleDeleteMultipleDocuments}
|
) : (
|
||||||
uploadDialogComponent={
|
<FolderList
|
||||||
<UploadDialog
|
folders={folders}
|
||||||
showUploadDialog={showUploadDialog}
|
selectedFolder={selectedFolder}
|
||||||
setShowUploadDialog={setShowUploadDialog}
|
setSelectedFolder={setSelectedFolder}
|
||||||
loading={loading}
|
apiBaseUrl={effectiveApiUrl}
|
||||||
onFileUpload={handleFileUpload}
|
authToken={authToken}
|
||||||
onBatchFileUpload={handleBatchFileUpload}
|
refreshFolders={fetchFolders}
|
||||||
onTextUpload={handleTextUpload}
|
loading={foldersLoading}
|
||||||
/>
|
refreshAction={handleRefresh}
|
||||||
}
|
selectedDocuments={selectedDocuments}
|
||||||
/>
|
handleDeleteMultipleDocuments={handleDeleteMultipleDocuments}
|
||||||
|
uploadDialogComponent={
|
||||||
|
<UploadDialog
|
||||||
|
showUploadDialog={showUploadDialog}
|
||||||
|
setShowUploadDialog={setShowUploadDialog}
|
||||||
|
loading={loading}
|
||||||
|
onFileUpload={handleFileUpload}
|
||||||
|
onBatchFileUpload={handleBatchFileUpload}
|
||||||
|
onTextUpload={handleTextUpload}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col md:flex-row gap-4 flex-1">
|
<div className="flex flex-col md:flex-row gap-4 flex-1">
|
||||||
|
{/* Left Panel: Document List or Skeleton or Empty State */}
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"w-full transition-all duration-300",
|
"w-full transition-all duration-300 flex flex-col",
|
||||||
selectedDocument ? "md:w-2/3" : "md:w-full"
|
selectedDocument ? "md:w-2/3" : "md:w-full"
|
||||||
)}>
|
)}>
|
||||||
<DocumentList
|
{loading ? (
|
||||||
documents={documents}
|
// Skeleton Loader for Document List (within the correct layout)
|
||||||
selectedDocument={selectedDocument}
|
<div className="space-y-3 p-4 flex-1">
|
||||||
selectedDocuments={selectedDocuments}
|
<Skeleton className="h-10 w-full" />
|
||||||
handleDocumentClick={handleDocumentClick}
|
<Skeleton className="h-8 w-3/4" />
|
||||||
handleCheckboxChange={handleCheckboxChange}
|
<Skeleton className="h-8 w-full" />
|
||||||
getSelectAllState={getSelectAllState}
|
<Skeleton className="h-8 w-5/6" />
|
||||||
setSelectedDocuments={setSelectedDocuments}
|
<Skeleton className="h-8 w-full" />
|
||||||
setDocuments={setDocuments}
|
</div>
|
||||||
loading={loading}
|
) : documents.length === 0 ? (
|
||||||
apiBaseUrl={effectiveApiUrl}
|
// Empty State (moved here, ensure it fills space)
|
||||||
authToken={authToken}
|
<div className="text-center py-8 border border-dashed rounded-lg flex-1 flex items-center justify-center">
|
||||||
selectedFolder={selectedFolder}
|
<div>
|
||||||
/>
|
<Upload className="mx-auto h-12 w-12 mb-2 text-muted-foreground" />
|
||||||
|
<p className="text-muted-foreground">Drag and drop files here to upload to this folder.</p>
|
||||||
|
<p className="text-xs text-muted-foreground mt-2">Or use the upload button in the top right.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// Actual Document List
|
||||||
|
<DocumentList
|
||||||
|
documents={documents}
|
||||||
|
selectedDocument={selectedDocument}
|
||||||
|
selectedDocuments={selectedDocuments}
|
||||||
|
handleDocumentClick={handleDocumentClick}
|
||||||
|
handleCheckboxChange={handleCheckboxChange}
|
||||||
|
getSelectAllState={getSelectAllState}
|
||||||
|
setSelectedDocuments={setSelectedDocuments}
|
||||||
|
setDocuments={setDocuments}
|
||||||
|
loading={loading}
|
||||||
|
apiBaseUrl={effectiveApiUrl}
|
||||||
|
authToken={authToken}
|
||||||
|
selectedFolder={selectedFolder}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Right Panel: Document Detail (conditionally rendered) */}
|
||||||
{selectedDocument && (
|
{selectedDocument && (
|
||||||
<div className="w-full md:w-1/3 animate-in slide-in-from-right duration-300">
|
<div className="w-full md:w-1/3 animate-in slide-in-from-right duration-300">
|
||||||
<DocumentDetail
|
<DocumentDetail
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
function Skeleton({
|
function Skeleton({
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@morphik/ui",
|
"name": "@morphik/ui",
|
||||||
"version": "0.1.7",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Modern UI component for Morphik - A powerful document processing and querying system",
|
"description": "Modern UI component for Morphik - A powerful document processing and querying system",
|
||||||
"author": "Morphik Team",
|
"author": "Morphik Team",
|
||||||
@ -36,7 +36,6 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/react": "^1.2.9",
|
|
||||||
"@radix-ui/primitive": "^1.1.2",
|
"@radix-ui/primitive": "^1.1.2",
|
||||||
"@radix-ui/react-accordion": "^1.2.3",
|
"@radix-ui/react-accordion": "^1.2.3",
|
||||||
"@radix-ui/react-checkbox": "^1.1.5",
|
"@radix-ui/react-checkbox": "^1.1.5",
|
||||||
@ -51,10 +50,8 @@
|
|||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-switch": "^1.1.3",
|
"@radix-ui/react-switch": "^1.1.3",
|
||||||
"@radix-ui/react-tabs": "^1.1.2",
|
"@radix-ui/react-tabs": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.2.4",
|
|
||||||
"accessor-fn": "^1.5.3",
|
"accessor-fn": "^1.5.3",
|
||||||
"accordion": "^3.0.2",
|
"accordion": "^3.0.2",
|
||||||
"ai": "^4.3.10",
|
|
||||||
"alert": "^6.0.2",
|
"alert": "^6.0.2",
|
||||||
"caniuse-lite": "^1.0.30001714",
|
"caniuse-lite": "^1.0.30001714",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@ -79,7 +76,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@shadcn/ui": "^0.0.4",
|
"@shadcn/ui": "^0.0.4",
|
||||||
"@types/node": "^20.17.31",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18.3.18",
|
"@types/react": "^18.3.18",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
"eslint": "^8",
|
"eslint": "^8",
|
||||||
@ -90,17 +87,16 @@
|
|||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"next": "^14",
|
"next": "^14",
|
||||||
"next-themes": "^0.4.6",
|
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
}
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
||||||
}
|
}
|
||||||
|
3
ee/ui-component/react-shim.js
vendored
Normal file
3
ee/ui-component/react-shim.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export { React };
|
@ -16,4 +16,9 @@ export default defineConfig({
|
|||||||
'next/link',
|
'next/link',
|
||||||
'@radix-ui/*',
|
'@radix-ui/*',
|
||||||
],
|
],
|
||||||
|
esbuildOptions(options) {
|
||||||
|
options.jsx = 'automatic';
|
||||||
|
options.jsxImportSource = 'react';
|
||||||
|
return options;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user