mirror of
https://github.com/james-m-jordan/morphik-core.git
synced 2025-05-09 19:32:38 +00:00
Enhance alert system: add warning type and improve event handling; add Checkbox component and update dependencies
This commit is contained in:
parent
e6ae047aad
commit
05d5fab228
@ -56,10 +56,10 @@ class ColpaliEmbeddingModel(BaseEmbeddingModel):
|
||||
else:
|
||||
contents.append(chunk.content)
|
||||
|
||||
return [self.generate_embeddings(content) for content in contents]
|
||||
return [await self.generate_embeddings(content) for content in contents]
|
||||
|
||||
async def embed_for_query(self, text: str) -> torch.Tensor:
|
||||
return self.generate_embeddings(text)
|
||||
return await self.generate_embeddings(text)
|
||||
|
||||
async def generate_embeddings(self, content: str | Image) -> np.ndarray:
|
||||
if isinstance(content, Image):
|
||||
|
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, ChangeEvent } from 'react';
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@ -59,15 +60,17 @@ interface QueryOptions extends SearchOptions {
|
||||
graph_name?: string;
|
||||
}
|
||||
|
||||
interface BatchUploadError {
|
||||
filename: string;
|
||||
error: string;
|
||||
}
|
||||
// Commented out as currently unused
|
||||
// interface BatchUploadError {
|
||||
// filename: string;
|
||||
// error: string;
|
||||
// }
|
||||
|
||||
const MorphikUI = () => {
|
||||
const [activeSection, setActiveSection] = useState('documents');
|
||||
const [documents, setDocuments] = useState<Document[]>([]);
|
||||
const [selectedDocument, setSelectedDocument] = useState<Document | null>(null);
|
||||
const [selectedDocuments, setSelectedDocuments] = useState<string[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
||||
const [chatQuery, setChatQuery] = useState('');
|
||||
@ -250,6 +253,108 @@ const MorphikUI = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle multiple document deletion
|
||||
const handleDeleteMultipleDocuments = async () => {
|
||||
if (selectedDocuments.length === 0) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Show initial alert for deletion progress
|
||||
const alertId = 'delete-multiple-progress';
|
||||
showAlert(`Deleting ${selectedDocuments.length} documents...`, {
|
||||
type: 'info',
|
||||
dismissible: false,
|
||||
id: alertId
|
||||
});
|
||||
|
||||
// Perform deletions sequentially
|
||||
const results = await Promise.all(
|
||||
selectedDocuments.map(docId =>
|
||||
fetch(`${API_BASE_URL}/documents/${docId}`, {
|
||||
method: 'DELETE',
|
||||
headers
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// Check if any deletion failed
|
||||
const failedCount = results.filter(res => !res.ok).length;
|
||||
|
||||
// Clear selected document if it was among deleted ones
|
||||
if (selectedDocument && selectedDocuments.includes(selectedDocument.external_id)) {
|
||||
setSelectedDocument(null);
|
||||
}
|
||||
|
||||
// Clear selection
|
||||
setSelectedDocuments([]);
|
||||
|
||||
// Refresh documents list
|
||||
await fetchDocuments();
|
||||
|
||||
// Remove progress alert
|
||||
removeAlert(alertId);
|
||||
|
||||
// Show final result alert
|
||||
if (failedCount > 0) {
|
||||
showAlert(`Deleted ${selectedDocuments.length - failedCount} documents. ${failedCount} deletions failed.`, {
|
||||
type: "warning",
|
||||
duration: 4000
|
||||
});
|
||||
} else {
|
||||
showAlert(`Successfully deleted ${selectedDocuments.length} documents`, {
|
||||
type: "success",
|
||||
duration: 3000
|
||||
});
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : 'An unknown error occurred';
|
||||
showAlert(errorMsg, {
|
||||
type: 'error',
|
||||
title: 'Delete Failed',
|
||||
duration: 5000
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle document selection - currently handled by handleCheckboxChange
|
||||
// Keeping implementation in comments for reference
|
||||
/*
|
||||
const toggleDocumentSelection = (e: React.MouseEvent, docId: string) => {
|
||||
e.stopPropagation(); // Prevent document selection/details view
|
||||
|
||||
setSelectedDocuments(prev => {
|
||||
if (prev.includes(docId)) {
|
||||
return prev.filter(id => id !== docId);
|
||||
} else {
|
||||
return [...prev, docId];
|
||||
}
|
||||
});
|
||||
};
|
||||
*/
|
||||
|
||||
// Handle checkbox change (wrapper function for use with shadcn checkbox)
|
||||
const handleCheckboxChange = (checked: boolean | "indeterminate", docId: string) => {
|
||||
setSelectedDocuments(prev => {
|
||||
if (checked === true && !prev.includes(docId)) {
|
||||
return [...prev, docId];
|
||||
} else if (checked === false && prev.includes(docId)) {
|
||||
return prev.filter(id => id !== docId);
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to get "indeterminate" state for select all checkbox
|
||||
const getSelectAllState = () => {
|
||||
if (selectedDocuments.length === 0) return false;
|
||||
if (selectedDocuments.length === documents.length) return true;
|
||||
return "indeterminate";
|
||||
};
|
||||
|
||||
// Handle file upload
|
||||
const handleFileUpload = async () => {
|
||||
if (!fileToUpload) {
|
||||
@ -780,10 +885,22 @@ const MorphikUI = () => {
|
||||
{activeSection === 'documents' && (
|
||||
<div className="flex-1 flex flex-col h-full">
|
||||
<div className="flex justify-between items-center bg-white py-3 mb-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold leading-tight">Your Documents</h2>
|
||||
<p className="text-muted-foreground">Manage your uploaded documents and view their metadata.</p>
|
||||
</div>
|
||||
{selectedDocuments.length > 0 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleDeleteMultipleDocuments}
|
||||
disabled={loading}
|
||||
className="border-red-500 text-red-500 hover:bg-red-50 ml-4"
|
||||
>
|
||||
Delete {selectedDocuments.length} selected
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Dialog
|
||||
open={showUploadDialog}
|
||||
onOpenChange={(open) => {
|
||||
@ -939,7 +1056,21 @@ const MorphikUI = () => {
|
||||
<div className="border rounded-md">
|
||||
<div className="bg-gray-100 border-b p-3 font-medium sticky top-0">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-5">Filename</div>
|
||||
<div className="col-span-1 flex items-center justify-center">
|
||||
<Checkbox
|
||||
id="select-all-documents"
|
||||
checked={getSelectAllState()}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
setSelectedDocuments(documents.map(doc => doc.external_id));
|
||||
} else {
|
||||
setSelectedDocuments([]);
|
||||
}
|
||||
}}
|
||||
aria-label="Select all documents"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-4">Filename</div>
|
||||
<div className="col-span-3">Type</div>
|
||||
<div className="col-span-4">ID</div>
|
||||
</div>
|
||||
@ -952,7 +1083,16 @@ const MorphikUI = () => {
|
||||
onClick={() => handleDocumentClick(doc)}
|
||||
className="grid grid-cols-12 p-3 cursor-pointer hover:bg-gray-50 border-b"
|
||||
>
|
||||
<div className="col-span-5 flex items-center">
|
||||
<div className="col-span-1 flex items-center justify-center">
|
||||
<Checkbox
|
||||
id={`doc-${doc.external_id}`}
|
||||
checked={selectedDocuments.includes(doc.external_id)}
|
||||
onCheckedChange={(checked) => handleCheckboxChange(checked, doc.external_id)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={`Select ${doc.filename || 'document'}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-4 flex items-center">
|
||||
{doc.filename || 'N/A'}
|
||||
{doc.external_id === selectedDocument?.external_id && (
|
||||
<Badge variant="outline" className="ml-2">Selected</Badge>
|
||||
@ -1083,14 +1223,14 @@ const MorphikUI = () => {
|
||||
|
||||
{/* Search Section */}
|
||||
{activeSection === 'search' && (
|
||||
<Card>
|
||||
<Card className="flex-1 flex flex-col h-full">
|
||||
<CardHeader>
|
||||
<CardTitle>Search Documents</CardTitle>
|
||||
<CardDescription>
|
||||
Search across your documents to find relevant information.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent className="flex-1 flex flex-col">
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
@ -1108,19 +1248,22 @@ const MorphikUI = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center text-sm text-gray-600 hover:text-gray-900"
|
||||
onClick={() => setShowSearchAdvanced(!showSearchAdvanced)}
|
||||
>
|
||||
<Settings className="mr-1 h-4 w-4" />
|
||||
<Dialog open={showSearchAdvanced} onOpenChange={setShowSearchAdvanced}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="flex items-center">
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
Advanced Options
|
||||
{showSearchAdvanced ? <ChevronUp className="ml-1 h-4 w-4" /> : <ChevronDown className="ml-1 h-4 w-4" />}
|
||||
</button>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Search Options</DialogTitle>
|
||||
<DialogDescription>
|
||||
Configure advanced search parameters
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{showSearchAdvanced && (
|
||||
<div className="mt-3 p-4 border rounded-md bg-gray-50">
|
||||
<div className="space-y-4">
|
||||
<div className="grid gap-4 py-4">
|
||||
<div>
|
||||
<Label htmlFor="search-filters" className="block mb-2">Filters (JSON)</Label>
|
||||
<Textarea
|
||||
@ -1178,16 +1321,22 @@ const MorphikUI = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setShowSearchAdvanced(false)}>Apply</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="mt-6 flex-1 overflow-hidden">
|
||||
{searchResults.length > 0 ? (
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-lg font-medium">Results ({searchResults.length})</h3>
|
||||
<div>
|
||||
<h3 className="text-lg font-medium mb-4">Results ({searchResults.length})</h3>
|
||||
|
||||
<ScrollArea className="h-[calc(100vh-320px)]">
|
||||
<div className="space-y-6 pr-4">
|
||||
{searchResults.map((result) => (
|
||||
<Card key={`${result.document_id}-${result.chunk_number}`}>
|
||||
<CardHeader className="pb-2">
|
||||
@ -1222,6 +1371,8 @@ const MorphikUI = () => {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-16 border border-dashed rounded-lg">
|
||||
<Search className="mx-auto h-12 w-12 mb-2 text-gray-400" />
|
||||
|
@ -6,7 +6,7 @@ import { cn } from '@/lib/utils';
|
||||
|
||||
interface AlertInstanceProps {
|
||||
id: string;
|
||||
type: 'error' | 'success' | 'info' | 'upload';
|
||||
type: 'error' | 'success' | 'info' | 'upload' | 'warning';
|
||||
title?: string;
|
||||
message: string;
|
||||
duration?: number;
|
||||
@ -29,7 +29,8 @@ const AlertInstance = ({
|
||||
type === 'error' && "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||
type === 'upload' && "bg-blue-50 text-blue-700 border-blue-200",
|
||||
type === 'success' && "bg-green-50 text-green-700 border-green-200",
|
||||
type === 'info' && "bg-gray-50 text-gray-700 border-gray-200"
|
||||
type === 'info' && "bg-gray-50 text-gray-700 border-gray-200",
|
||||
type === 'warning' && "bg-amber-50 text-amber-700 border-amber-200"
|
||||
)}
|
||||
>
|
||||
{dismissible && (
|
||||
@ -57,13 +58,22 @@ export function AlertSystem({ position = 'bottom-right' }: AlertSystemProps) {
|
||||
|
||||
// Custom event handlers for adding and removing alerts
|
||||
useEffect(() => {
|
||||
const handleAddAlert = (event: CustomEvent) => {
|
||||
const alert = event.detail;
|
||||
const handleAddAlert = (event: Event) => {
|
||||
const customEvent = event as CustomEvent<{
|
||||
id?: string;
|
||||
type: 'error' | 'success' | 'info' | 'upload' | 'warning';
|
||||
title?: string;
|
||||
message: string;
|
||||
duration?: number;
|
||||
dismissible?: boolean;
|
||||
}>;
|
||||
const alert = customEvent.detail;
|
||||
if (alert) {
|
||||
const newAlert = {
|
||||
const newAlert: AlertInstanceProps = {
|
||||
...alert,
|
||||
id: alert.id || Date.now().toString(),
|
||||
dismissible: alert.dismissible !== false,
|
||||
onDismiss: removeAlert,
|
||||
};
|
||||
|
||||
setAlerts(prev => [...prev, newAlert]);
|
||||
@ -77,20 +87,20 @@ export function AlertSystem({ position = 'bottom-right' }: AlertSystemProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveAlert = (event: CustomEvent) => {
|
||||
const { id } = event.detail;
|
||||
const handleRemoveAlert = (event: Event) => {
|
||||
const customEvent = event as CustomEvent<{id: string}>;
|
||||
const { id } = customEvent.detail;
|
||||
if (id) {
|
||||
removeAlert(id);
|
||||
}
|
||||
};
|
||||
|
||||
// Cast to any to handle CustomEvent
|
||||
window.addEventListener('morphik:alert' as any, handleAddAlert);
|
||||
window.addEventListener('morphik:alert:remove' as any, handleRemoveAlert);
|
||||
window.addEventListener('morphik:alert', handleAddAlert as EventListener);
|
||||
window.addEventListener('morphik:alert:remove', handleRemoveAlert as EventListener);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('morphik:alert' as any, handleAddAlert);
|
||||
window.removeEventListener('morphik:alert:remove' as any, handleRemoveAlert);
|
||||
window.removeEventListener('morphik:alert', handleAddAlert as EventListener);
|
||||
window.removeEventListener('morphik:alert:remove', handleRemoveAlert as EventListener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -125,7 +135,7 @@ export function AlertSystem({ position = 'bottom-right' }: AlertSystemProps) {
|
||||
export const showAlert = (
|
||||
message: string,
|
||||
options?: {
|
||||
type?: 'error' | 'success' | 'info' | 'upload';
|
||||
type?: 'error' | 'success' | 'info' | 'upload' | 'warning';
|
||||
title?: string;
|
||||
duration?: number; // in milliseconds, none means it stays until dismissed
|
||||
dismissible?: boolean;
|
||||
|
30
ui-component/components/ui/checkbox.tsx
Normal file
30
ui-component/components/ui/checkbox.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { Check } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
))
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||
|
||||
export { Checkbox }
|
@ -17,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.2.3",
|
||||
"@radix-ui/react-checkbox": "^1.1.5",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||
@ -29,6 +30,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"force-graph": "^1.49.4",
|
||||
"formdata-node": "^6.0.3",
|
||||
"label": "^0.2.2",
|
||||
"lucide-react": "^0.469.0",
|
||||
"next": "^14.2.24",
|
||||
@ -40,7 +42,8 @@
|
||||
"shadcn-ui": "^0.9.4",
|
||||
"sheet": "^0.2.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"web-streams-polyfill": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@shadcn/ui": "^0.0.4",
|
||||
|
Loading…
x
Reference in New Issue
Block a user