AI_Chat_With_Tools / components /DocumentsModal.tsx
muhammad1707's picture
Move master code to main
8421ec4
Raw
History Blame Contribute Delete
10.1 kB
import React, { useState, useEffect, useRef } from 'react';
import { Document } from '../types';
import { getDocuments, uploadDocument, deleteDocument } from '../services/api';
interface DocumentsModalProps {
isOpen: boolean;
onClose: () => void;
}
const DocumentsModal: React.FC<DocumentsModalProps> = ({ isOpen, onClose }) => {
const [documents, setDocuments] = useState<Document[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (isOpen) {
loadDocuments();
}
}, [isOpen]);
const loadDocuments = async () => {
try {
setIsLoading(true);
setError(null);
const docs = await getDocuments();
setDocuments(docs);
} catch (err) {
setError('Failed to load documents');
} finally {
setIsLoading(false);
}
};
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
if (file.type !== 'application/pdf') {
setError('Only PDF files are allowed');
return;
}
try {
setIsUploading(true);
setError(null);
await uploadDocument(file);
await loadDocuments(); // Reload list
} catch (err) {
setError(err instanceof Error ? err.message : 'Upload failed');
} finally {
setIsUploading(false);
// Reset input
if (fileInputRef.current) fileInputRef.current.value = '';
}
}
};
const handleDelete = async (docId: string) => {
try {
if (!confirm('Are you sure you want to delete this document?')) return;
setIsLoading(true);
await deleteDocument(docId);
await loadDocuments();
} catch (err) {
setError(err instanceof Error ? err.message : 'Delete failed');
} finally {
setIsLoading(false);
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl w-full max-w-2xl max-h-[80vh] flex flex-col shadow-2xl animate-in fade-in zoom-in-95 duration-200">
{/* Header */}
<div className="p-6 border-b border-gray-100 flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-gray-900">Manage Documents</h2>
<p className="text-sm text-gray-500 mt-1">Upload PDFs to include in the RAG knowledge base</p>
</div>
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-full transition-colors text-gray-500">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-6">
{/* Upload Area */}
<div className="mb-8">
<input
type="file"
ref={fileInputRef}
accept=".pdf"
onChange={handleFileSelect}
className="hidden"
/>
<button
onClick={() => fileInputRef.current?.click()}
disabled={isUploading}
className={`w-full border-2 border-dashed border-indigo-200 bg-indigo-50/50 rounded-xl p-8 flex flex-col items-center justify-center transition-all ${isUploading ? 'cursor-not-allowed opacity-75' : 'hover:border-indigo-400 hover:bg-indigo-50 cursor-pointer'
}`}
>
{isUploading ? (
<>
<div className="w-10 h-10 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin mb-3"></div>
<span className="text-indigo-600 font-medium">Processing Document...</span>
<span className="text-xs text-indigo-400 mt-1">Extracting text & generating embeddings</span>
</>
) : (
<>
<div className="w-12 h-12 bg-indigo-100 text-indigo-600 rounded-full flex items-center justify-center mb-3">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
</div>
<span className="text-gray-900 font-medium">Click to upload PDF</span>
<span className="text-xs text-gray-500 mt-1">Maximum file size: 10MB</span>
</>
)}
</button>
</div>
{/* Error Message */}
{error && (
<div className="mb-6 p-4 bg-red-50 text-red-600 rounded-xl text-sm flex items-center gap-2 border border-red-100">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{error}
</div>
)}
{/* Document List */}
<div>
<h3 className="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-3">Uploaded Documents</h3>
{isLoading && documents.length === 0 ? (
<div className="text-center py-8 text-gray-400">Loading...</div>
) : documents.length === 0 ? (
<div className="text-center py-8 text-gray-400 bg-gray-50 rounded-xl border border-gray-100 border-dashed">
No documents uploaded yet.
</div>
) : (
<div className="space-y-2">
{documents.map((doc) => (
<div key={doc.id} className="flex items-center justify-between p-3 bg-white border border-gray-200 rounded-lg hover:border-indigo-200 transition-colors">
<div className="flex items-center gap-3 overflow-hidden">
<div className="w-10 h-10 bg-red-100 text-red-600 rounded-lg flex-shrink-0 flex items-center justify-center">
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z" />
</svg>
</div>
<div className="min-w-0">
<h4 className="text-sm font-medium text-gray-900 truncate">{doc.filename}</h4>
<p className="text-xs text-gray-500">
{new Date(doc.upload_date).toLocaleDateString()} • {doc.status}
</p>
</div>
</div>
<button
onClick={() => handleDelete(doc.id)}
className="p-2 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-lg transition-colors"
title="Delete Document"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
))}
</div>
)}
</div>
</div>
{/* Footer */}
<div className="p-4 border-t border-gray-100 bg-gray-50 rounded-b-2xl">
<p className="text-xs text-center text-gray-500">
Documents are processed locally. Embeddings are stored in ChromaDB vector store.
</p>
</div>
</div>
</div>
);
};
export default DocumentsModal;