From 737039e988d8d8a6ed5644597a8dc8cce3609ba4 Mon Sep 17 00:00:00 2001 From: Adityavardhan Agrawal Date: Tue, 15 Apr 2025 16:55:47 -0700 Subject: [PATCH] UI component: dark mode, packaging, bug fixes (#86) --- package-lock.json | 6 + ui-component/.npmignore | 20 + ui-component/PUBLISHING.md | 67 + ui-component/README.md | 40 +- ui-component/app/layout.tsx | 14 +- ui-component/app/page.tsx | 1611 +--------- .../components/ForceGraphComponent.tsx | 2 +- ui-component/components/GraphSection.tsx | 68 +- ui-component/components/MorphikUI.tsx | 141 + ui-component/components/NotebookSection.tsx | 2749 ----------------- ui-component/components/chat/ChatMessage.tsx | 27 + .../components/chat/ChatOptionsDialog.tsx | 165 + .../components/chat/ChatOptionsPanel.tsx | 158 + ui-component/components/chat/ChatSection.tsx | 205 ++ .../components/documents/DocumentDetail.tsx | 125 + .../components/documents/DocumentList.tsx | 96 + .../components/documents/DocumentsSection.tsx | 655 ++++ .../components/documents/UploadDialog.tsx | 268 ++ ui-component/components/mode-toggle.tsx | 27 + .../components/search/SearchOptionsDialog.tsx | 110 + .../components/search/SearchResultCard.tsx | 86 + .../components/search/SearchSection.tsx | 163 + ui-component/components/theme-provider.tsx | 11 + ui-component/components/types.ts | 48 + ui-component/components/ui/dropdown-menu.tsx | 201 ++ ui-component/components/ui/progress.tsx | 28 + ui-component/components/ui/radio-group.tsx | 44 + ui-component/components/ui/select.tsx | 159 + ui-component/components/ui/sidebar.tsx | 238 +- ui-component/lib/utils.ts | 106 + ui-component/notebook-storage/notebooks.json | 11 - ui-component/package.json | 49 +- ui-component/src/index.ts | 18 + ui-component/tsconfig.lib.json | 9 + ui-component/tsup.config.ts | 19 + 35 files changed, 3318 insertions(+), 4426 deletions(-) create mode 100644 package-lock.json create mode 100644 ui-component/.npmignore create mode 100644 ui-component/PUBLISHING.md create mode 100644 ui-component/components/MorphikUI.tsx delete mode 100644 ui-component/components/NotebookSection.tsx create mode 100644 ui-component/components/chat/ChatMessage.tsx create mode 100644 ui-component/components/chat/ChatOptionsDialog.tsx create mode 100644 ui-component/components/chat/ChatOptionsPanel.tsx create mode 100644 ui-component/components/chat/ChatSection.tsx create mode 100644 ui-component/components/documents/DocumentDetail.tsx create mode 100644 ui-component/components/documents/DocumentList.tsx create mode 100644 ui-component/components/documents/DocumentsSection.tsx create mode 100644 ui-component/components/documents/UploadDialog.tsx create mode 100644 ui-component/components/mode-toggle.tsx create mode 100644 ui-component/components/search/SearchOptionsDialog.tsx create mode 100644 ui-component/components/search/SearchResultCard.tsx create mode 100644 ui-component/components/search/SearchSection.tsx create mode 100644 ui-component/components/theme-provider.tsx create mode 100644 ui-component/components/types.ts create mode 100644 ui-component/components/ui/dropdown-menu.tsx create mode 100644 ui-component/components/ui/progress.tsx create mode 100644 ui-component/components/ui/radio-group.tsx create mode 100644 ui-component/components/ui/select.tsx delete mode 100644 ui-component/notebook-storage/notebooks.json create mode 100644 ui-component/src/index.ts create mode 100644 ui-component/tsconfig.lib.json create mode 100644 ui-component/tsup.config.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8407bba --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "morphik-core", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/ui-component/.npmignore b/ui-component/.npmignore new file mode 100644 index 0000000..d179c94 --- /dev/null +++ b/ui-component/.npmignore @@ -0,0 +1,20 @@ +.git +.github +node_modules +app +public +.next +.vercel +.env* +!.env.example + +# Exclude development configs +tsconfig.json +postcss.config.mjs +tailwind.config.ts +next.config.mjs +*.lock +components.json + +# Include dist +!dist \ No newline at end of file diff --git a/ui-component/PUBLISHING.md b/ui-component/PUBLISHING.md new file mode 100644 index 0000000..626607e --- /dev/null +++ b/ui-component/PUBLISHING.md @@ -0,0 +1,67 @@ +# Publishing the @morphik/ui Package + +This document provides instructions for publishing the @morphik/ui package to npm. + +## Prerequisites + +1. You must have an npm account +2. You must be added as a contributor to the @morphik organization +3. You must be logged in to npm via the CLI (`npm login`) + +## Publishing Steps + +1. Ensure all changes are committed to the repository + +2. Update the version in package.json (follow semantic versioning) + ```bash + npm version patch # For bug fixes + npm version minor # For new features + npm version major # For breaking changes + ``` + +3. Build the package + ```bash + npm run build:package + ``` + +4. Run a dry-run to check the package contents + ```bash + npm pack --dry-run + ``` + +5. Publish the package + ```bash + npm publish --access public + ``` + +6. Create a git tag for the release + ```bash + git tag v$(node -p "require('./package.json').version") + git push origin v$(node -p "require('./package.json').version") + ``` + +## Installing for Local Development + +If you want to test the package locally before publishing, you can use npm link: + +1. In the ui-component directory: + ```bash + npm link + ``` + +2. In your project that uses the package: + ```bash + npm link @morphik/ui + ``` + +Alternatively, you can install from a local directory: + +```bash +npm install /path/to/morphik-core/ui-component +``` + +Or from a GitHub repository: + +```bash +npm install github:morphik-org/morphik-core#subdirectory=ui-component +``` \ No newline at end of file diff --git a/ui-component/README.md b/ui-component/README.md index ca58845..d351a16 100644 --- a/ui-component/README.md +++ b/ui-component/README.md @@ -1,4 +1,4 @@ -# Morphik UI Component +# @morphik/ui A modern React-based UI for Morphik, built with Next.js and Tailwind CSS. This component provides a user-friendly interface for: - Document management and uploads @@ -6,26 +6,54 @@ A modern React-based UI for Morphik, built with Next.js and Tailwind CSS. This c - Real-time document processing feedback - Query testing and prototyping +## Installation + +```bash +npm install @morphik/ui +``` + +## Usage + +```jsx +import { MorphikUI } from '@morphik/ui'; + +export default function YourApp() { + return ( + console.log('URI changed:', uri)} + /> + ); +} +``` + +## Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `connectionUri` | `string` | `undefined` | Connection URI for Morphik API | +| `apiBaseUrl` | `string` | `"http://localhost:8000"` | Base URL for API requests | +| `isReadOnlyUri` | `boolean` | `false` | Controls whether the URI can be edited | +| `onUriChange` | `(uri: string) => void` | `undefined` | Callback when URI is changed | + ## Prerequisites - Node.js 18 or later - npm or yarn package manager - A running Morphik server -## Quick Start +## Development Quick Start 1. Install dependencies: ```bash npm install -# or -yarn install ``` 2. Start the development server: ```bash npm run dev -# or -yarn dev ``` 3. Open [http://localhost:3000](http://localhost:3000) in your browser diff --git a/ui-component/app/layout.tsx b/ui-component/app/layout.tsx index 8d0fbd0..711cf7b 100644 --- a/ui-component/app/layout.tsx +++ b/ui-component/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import localFont from "next/font/local"; import "./globals.css"; import { AlertSystem } from "@/components/ui/alert-system"; +import { ThemeProvider } from "@/components/theme-provider"; const geistSans = localFont({ src: "./fonts/GeistVF.woff", @@ -25,12 +26,19 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - {children} - + + {children} + + ); diff --git a/ui-component/app/page.tsx b/ui-component/app/page.tsx index 77f003b..dea0a7f 100644 --- a/ui-component/app/page.tsx +++ b/ui-component/app/page.tsx @@ -1,1609 +1,8 @@ "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'; -import { Textarea } from '@/components/ui/textarea'; -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { Badge } from '@/components/ui/badge'; -import { Label } from '@/components/ui/label'; -import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'; -import { Switch } from '@/components/ui/switch'; -import { Upload, Search, MessageSquare, Info, Settings, ChevronDown, ChevronUp } from 'lucide-react'; -import { Sidebar } from '@/components/ui/sidebar'; -import GraphSection from '@/components/GraphSection'; -import NotebookSection from '@/components/NotebookSection'; -import { showAlert, removeAlert } from '@/components/ui/alert-system'; -import Image from 'next/image'; +import React from 'react'; +import MorphikUI from '@/components/MorphikUI'; -// API base URL - change this to match your Morphik server -const API_BASE_URL = 'http://localhost:8000'; - -interface Document { - external_id: string; - filename?: string; - content_type: string; - metadata: Record; - system_metadata: Record; - additional_metadata: Record; -} - -interface SearchResult { - document_id: string; - chunk_number: number; - content: string; - content_type: string; - score: number; - filename?: string; - metadata: Record; -} - -interface ChatMessage { - role: 'user' | 'assistant'; - content: string; -} - -interface SearchOptions { - filters: string; - k: number; - min_score: number; - use_reranking: boolean; - use_colpali: boolean; -} - -interface QueryOptions extends SearchOptions { - max_tokens: number; - temperature: number; - graph_name?: string; -} - -// Commented out as currently unused -// interface BatchUploadError { -// filename: string; -// error: string; -// } - -const MorphikUI = () => { - const [activeSection, setActiveSection] = useState('documents'); - const [documents, setDocuments] = useState([]); - const [selectedDocument, setSelectedDocument] = useState(null); - const [selectedDocuments, setSelectedDocuments] = useState([]); - const [searchQuery, setSearchQuery] = useState(''); - const [searchResults, setSearchResults] = useState([]); - const [chatQuery, setChatQuery] = useState(''); - const [chatMessages, setChatMessages] = useState([]); - const [loading, setLoading] = useState(false); - // No longer need error state as we're using alert system - const [showUploadDialog, setShowUploadDialog] = useState(false); - // Alert system now handles upload status messages - const [uploadType, setUploadType] = useState<'file' | 'text' | 'batch'>('file'); - const [textContent, setTextContent] = useState(''); - const [fileToUpload, setFileToUpload] = useState(null); - const [batchFilesToUpload, setBatchFilesToUpload] = useState([]); - const [metadata, setMetadata] = useState('{}'); - const [rules, setRules] = useState('[]'); - const [useColpali, setUseColpali] = useState(true); - - // Advanced options for search - const [showSearchAdvanced, setShowSearchAdvanced] = useState(false); - const [searchOptions, setSearchOptions] = useState({ - filters: '{}', - k: 4, - min_score: 0, - use_reranking: false, - use_colpali: true - }); - - // Advanced options for chat/query - const [showChatAdvanced, setShowChatAdvanced] = useState(false); - const [queryOptions, setQueryOptions] = useState({ - filters: '{}', - k: 4, - min_score: 0, - use_reranking: false, - use_colpali: true, - max_tokens: 500, - temperature: 0.7 - }); - - // Auth token - in a real application, you would get this from your auth system - const authToken = 'YOUR_AUTH_TOKEN'; - - // Headers for API requests - const headers = { - 'Authorization': authToken - }; - - // Fetch all documents - non-blocking implementation - const fetchDocuments = async () => { - try { - // Only set loading state for initial load, not for refreshes - if (documents.length === 0) { - setLoading(true); - } - // Using alerts instead of error state - - // Use non-blocking fetch - fetch(`${API_BASE_URL}/documents`, { - method: 'POST', - headers: { - ...headers, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({}) - }) - .then(response => { - if (!response.ok) { - throw new Error(`Failed to fetch documents: ${response.statusText}`); - } - return response.json(); - }) - .then(data => { - setDocuments(data); - setLoading(false); - }) - .catch(err => { - const errorMsg = err instanceof Error ? err.message : 'An unknown error occurred'; - showAlert(errorMsg, { - type: 'error', - title: 'Error', - duration: 5000 - }); - setLoading(false); - }); - } catch (err) { - const errorMsg = err instanceof Error ? err.message : 'An unknown error occurred'; - showAlert(errorMsg, { - type: 'error', - title: 'Error', - duration: 5000 - }); - setLoading(false); - } - }; - - // Fetch documents on component mount - useEffect(() => { - fetchDocuments(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Fetch a specific document by ID - fully non-blocking - const fetchDocument = async (documentId: string) => { - try { - // Using alerts instead of error state - - // Use non-blocking fetch to avoid locking the UI - fetch(`${API_BASE_URL}/documents/${documentId}`, { - headers - }) - .then(response => { - if (!response.ok) { - throw new Error(`Failed to fetch document: ${response.statusText}`); - } - return response.json(); - }) - .then(data => { - setSelectedDocument(data); - }) - .catch(err => { - const errorMsg = err instanceof Error ? err.message : 'An unknown error occurred'; - showAlert(errorMsg, { - type: 'error', - title: 'Error', - duration: 5000 - }); - }); - } catch (err) { - const errorMsg = err instanceof Error ? err.message : 'An unknown error occurred'; - showAlert(errorMsg, { - type: 'error', - title: 'Error', - duration: 5000 - }); - } - }; - - // Handle document click - const handleDocumentClick = (document: Document) => { - fetchDocument(document.external_id); - }; - - // Handle document deletion - const handleDeleteDocument = async (documentId: string) => { - try { - setLoading(true); - // Using alerts instead of error state - - const response = await fetch(`${API_BASE_URL}/documents/${documentId}`, { - method: 'DELETE', - headers - }); - - if (!response.ok) { - throw new Error(`Failed to delete document: ${response.statusText}`); - } - - // Clear selected document if it was the one deleted - if (selectedDocument?.external_id === documentId) { - setSelectedDocument(null); - } - - // Refresh documents list - await fetchDocuments(); - - // Show success message - showAlert("Document deleted successfully", { - 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); - } - }; - - // 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) { - showAlert('Please select a file to upload', { - type: 'error', - duration: 3000 - }); - return; - } - - // Close dialog and update upload count using alert system - setShowUploadDialog(false); - const uploadId = 'upload-progress'; - showAlert(`Uploading 1 file...`, { - type: 'upload', - dismissible: false, - id: uploadId - }); - - // Reset form data immediately so users can initiate another upload if desired - const fileToUploadRef = fileToUpload; - const metadataRef = metadata; - const rulesRef = rules; - const useColpaliRef = useColpali; - - // Reset form - setFileToUpload(null); - setMetadata('{}'); - setRules('[]'); - setUseColpali(true); - - try { - // Using alerts instead of error state - - const formData = new FormData(); - formData.append('file', fileToUploadRef); - formData.append('metadata', metadataRef); - formData.append('rules', rulesRef); - - const url = `${API_BASE_URL}/ingest/file${useColpaliRef ? '?use_colpali=true' : ''}`; - - // Non-blocking fetch - fetch(url, { - method: 'POST', - headers: { - 'Authorization': authToken - }, - body: formData - }) - .then(response => { - if (!response.ok) { - throw new Error(`Failed to upload: ${response.statusText}`); - } - return response.json(); - }) - .then(() => { - fetchDocuments(); // Refresh document list (non-blocking) - - // Show success message and remove upload progress - showAlert(`File uploaded successfully!`, { - type: 'success', - duration: 3000 - }); - - // Remove the upload alert - removeAlert('upload-progress'); - }) - .catch(err => { - const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred'; - const errorMsg = `Error uploading ${fileToUploadRef.name}: ${errorMessage}`; - - // Show error alert and remove upload progress - showAlert(errorMsg, { - type: 'error', - title: 'Upload Failed', - duration: 5000 - }); - - // Remove the upload alert - removeAlert('upload-progress'); - }); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred'; - const errorMsg = `Error uploading ${fileToUploadRef.name}: ${errorMessage}`; - - // Show error alert - showAlert(errorMsg, { - type: 'error', - title: 'Upload Failed', - duration: 5000 - }); - - // Remove the upload progress alert - removeAlert('upload-progress'); - } - }; - - // Handle batch file upload - const handleBatchFileUpload = async () => { - if (batchFilesToUpload.length === 0) { - showAlert('Please select files to upload', { - type: 'error', - duration: 3000 - }); - return; - } - - // Close dialog and update upload count using alert system - setShowUploadDialog(false); - const fileCount = batchFilesToUpload.length; - const uploadId = 'batch-upload-progress'; - showAlert(`Uploading ${fileCount} files...`, { - type: 'upload', - dismissible: false, - id: uploadId - }); - - // Save form data locally before resetting - const batchFilesRef = [...batchFilesToUpload]; - const metadataRef = metadata; - const rulesRef = rules; - const useColpaliRef = useColpali; - - // Reset form immediately - setBatchFilesToUpload([]); - setMetadata('{}'); - setRules('[]'); - setUseColpali(true); - - try { - // Using alerts instead of error state - - const formData = new FormData(); - - // Append each file to the formData with the same field name - batchFilesRef.forEach(file => { - formData.append('files', file); - }); - - formData.append('metadata', metadataRef); - formData.append('rules', rulesRef); - formData.append('parallel', 'true'); - if (useColpaliRef !== undefined) { - formData.append('use_colpali', useColpaliRef.toString()); - } - - // Non-blocking fetch - fetch(`${API_BASE_URL}/ingest/files`, { - method: 'POST', - headers: { - 'Authorization': authToken - }, - body: formData - }) - .then(response => { - if (!response.ok) { - throw new Error(`Failed to upload: ${response.statusText}`); - } - return response.json(); - }) - .then(result => { - fetchDocuments(); // Refresh document list (non-blocking) - - // If there are errors, show them in the error alert - if (result.errors && result.errors.length > 0) { - const errorMsg = `${result.errors.length} of ${fileCount} files failed to upload`; - - showAlert(errorMsg, { - type: 'error', - title: 'Upload Partially Failed', - duration: 5000 - }); - } else { - // Show success message - showAlert(`${fileCount} files uploaded successfully!`, { - type: 'success', - duration: 3000 - }); - } - - // Remove the upload alert - removeAlert('batch-upload-progress'); - }) - .catch(err => { - const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred'; - const errorMsg = `Error uploading files: ${errorMessage}`; - - // Show error alert - showAlert(errorMsg, { - type: 'error', - title: 'Upload Failed', - duration: 5000 - }); - - // Remove the upload alert - removeAlert('batch-upload-progress'); - }); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred'; - const errorMsg = `Error uploading files: ${errorMessage}`; - - // Show error alert - showAlert(errorMsg, { - type: 'error', - title: 'Upload Failed', - duration: 5000 - }); - - // Remove the upload progress alert - removeAlert('batch-upload-progress'); - } - }; - - // Handle text upload - const handleTextUpload = async () => { - if (!textContent.trim()) { - showAlert('Please enter text content', { - type: 'error', - duration: 3000 - }); - return; - } - - // Close dialog and update upload count using alert system - setShowUploadDialog(false); - const uploadId = 'text-upload-progress'; - showAlert(`Uploading text document...`, { - type: 'upload', - dismissible: false, - id: uploadId - }); - - // Save content before resetting - const textContentRef = textContent; - const metadataRef = metadata; - const rulesRef = rules; - const useColpaliRef = useColpali; - - // Reset form immediately - setTextContent(''); - setMetadata('{}'); - setRules('[]'); - setUseColpali(true); - - try { - // Using alerts instead of error state - - // Non-blocking fetch - fetch(`${API_BASE_URL}/ingest/text`, { - method: 'POST', - headers: { - 'Authorization': authToken, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - content: textContentRef, - metadata: JSON.parse(metadataRef || '{}'), - rules: JSON.parse(rulesRef || '[]'), - use_colpali: useColpaliRef - }) - }) - .then(response => { - if (!response.ok) { - throw new Error(`Failed to upload: ${response.statusText}`); - } - return response.json(); - }) - .then(() => { - fetchDocuments(); // Refresh document list (non-blocking) - - // Show success message - showAlert(`Text document uploaded successfully!`, { - type: 'success', - duration: 3000 - }); - - // Remove the upload alert - removeAlert('text-upload-progress'); - }) - .catch(err => { - const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred'; - const errorMsg = `Error uploading text: ${errorMessage}`; - - // Show error alert - showAlert(errorMsg, { - type: 'error', - title: 'Upload Failed', - duration: 5000 - }); - - // Remove the upload alert - removeAlert('text-upload-progress'); - }); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred'; - const errorMsg = `Error uploading text: ${errorMessage}`; - - // Show error alert - showAlert(errorMsg, { - type: 'error', - title: 'Upload Failed', - duration: 5000 - }); - - // Remove the upload progress alert - removeAlert('text-upload-progress'); - } - }; - - // Handle search - const handleSearch = async () => { - if (!searchQuery.trim()) { - showAlert('Please enter a search query', { - type: 'error', - duration: 3000 - }); - return; - } - - try { - setLoading(true); - // Using alerts instead of error state - - const response = await fetch(`${API_BASE_URL}/retrieve/chunks`, { - method: 'POST', - headers: { - 'Authorization': authToken, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - query: searchQuery, - filters: JSON.parse(searchOptions.filters || '{}'), - k: searchOptions.k, - min_score: searchOptions.min_score, - use_reranking: searchOptions.use_reranking, - use_colpali: searchOptions.use_colpali - }) - }); - - if (!response.ok) { - throw new Error(`Search failed: ${response.statusText}`); - } - - const data = await response.json(); - setSearchResults(data); - - if (data.length === 0) { - showAlert("No search results found for the query", { - type: "info", - duration: 3000 - }); - } - } catch (err) { - const errorMsg = err instanceof Error ? err.message : 'An unknown error occurred'; - showAlert(errorMsg, { - type: 'error', - title: 'Search Failed', - duration: 5000 - }); - } finally { - setLoading(false); - } - }; - - // Handle chat - const handleChat = async () => { - if (!chatQuery.trim()) { - showAlert('Please enter a message', { - type: 'error', - duration: 3000 - }); - return; - } - - try { - setLoading(true); - // Using alerts instead of error state - - // Add user message to chat - const userMessage: ChatMessage = { role: 'user', content: chatQuery }; - setChatMessages(prev => [...prev, userMessage]); - - // Prepare options with graph_name if it exists - const options = { - filters: JSON.parse(queryOptions.filters || '{}'), - k: queryOptions.k, - min_score: queryOptions.min_score, - use_reranking: queryOptions.use_reranking, - use_colpali: queryOptions.use_colpali, - max_tokens: queryOptions.max_tokens, - temperature: queryOptions.temperature, - graph_name: queryOptions.graph_name - }; - - const response = await fetch(`${API_BASE_URL}/query`, { - method: 'POST', - headers: { - 'Authorization': authToken, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - query: chatQuery, - ...options - }) - }); - - if (!response.ok) { - throw new Error(`Query failed: ${response.statusText}`); - } - - const data = await response.json(); - - // Add assistant response to chat - const assistantMessage: ChatMessage = { role: 'assistant', content: data.completion }; - setChatMessages(prev => [...prev, assistantMessage]); - setChatQuery(''); // Clear input - } catch (err) { - const errorMsg = err instanceof Error ? err.message : 'An unknown error occurred'; - showAlert(errorMsg, { - type: 'error', - title: 'Chat Query Failed', - duration: 5000 - }); - } finally { - setLoading(false); - } - }; - - // Render content based on content type - const renderContent = (content: string, contentType: string) => { - if (contentType.startsWith('image/')) { - return ( -
- Document content -
- ); - } else if (content.startsWith('data:image/png;base64,') || content.startsWith('data:image/jpeg;base64,')) { - return ( -
- Base64 image content -
- ); - } else { - return ( -
- {content} -
- ); - } - }; - - // Reset upload dialog - const resetUploadDialog = () => { - setUploadType('file'); - setFileToUpload(null); - setBatchFilesToUpload([]); - setTextContent(''); - setMetadata('{}'); - setRules('[]'); - setUseColpali(true); - }; - - // Update search options - const updateSearchOption = (key: K, value: SearchOptions[K]) => { - setSearchOptions(prev => ({ - ...prev, - [key]: value - })); - }; - - // Fetch available graphs for dropdown - const [availableGraphs, setAvailableGraphs] = useState([]); - - // Fetch graphs - const fetchGraphs = async () => { - try { - const response = await fetch(`${API_BASE_URL}/graphs`); - if (!response.ok) { - throw new Error(`Failed to fetch graphs: ${response.statusText}`); - } - const graphsData = await response.json(); - setAvailableGraphs(graphsData.map((graph: { name: string }) => graph.name)); - } catch (err) { - console.error('Error fetching available graphs:', err); - } - }; - - // Fetch graphs on component mount - useEffect(() => { - fetchGraphs(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Update query options - const updateQueryOption = (key: K, value: QueryOptions[K]) => { - setQueryOptions(prev => ({ - ...prev, - [key]: value - })); - }; - - return ( -
- - -
- {/* Upload status is now handled by the AlertSystem */} - - {/* Error alerts now appear in the bottom-right via the AlertSystem */} - - {/* Documents Section */} - {activeSection === 'documents' && ( -
-
-
-
-

Your Documents

-

Manage your uploaded documents and view their metadata.

-
- {selectedDocuments.length > 0 && ( - - )} -
- { - setShowUploadDialog(open); - if (!open) resetUploadDialog(); - }} - > - - - - - - Upload Document - - Upload a file or text to your Morphik repository. - - - -
-
- - - -
- - {uploadType === 'file' ? ( -
- - { - const files = e.target.files; - if (files && files.length > 0) { - setFileToUpload(files[0]); - } - }} - /> -
- ) : uploadType === 'batch' ? ( -
- - ) => { - const files = e.target.files; - if (files && files.length > 0) { - setBatchFilesToUpload(Array.from(files)); - } - }} - /> - {batchFilesToUpload.length > 0 && ( -
-

{batchFilesToUpload.length} files selected:

- -
    - {Array.from(batchFilesToUpload).map((file, index) => ( -
  • - {file.name} ({(file.size / 1024).toFixed(1)} KB) -
  • - ))} -
-
-
- )} -
- ) : ( -
- -