import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Save, Loader2, RefreshCw, Pencil, Trash2, FileText, Plus, BookOpen } from "lucide-react"; import { api } from "@/lib/api"; import { useProjectStore } from "@/store/projectStore"; import { Textarea } from "@/components/ui/textarea"; import { KnowledgeBaseForm, type KnowledgeBaseFormValues } from "@/components/KnowledgeBaseForm"; interface KnowledgeBase { id: string; name: string; description?: string; project_id?: number | null; embedding_model?: string | null; chunk_size: number; chunk_overlap: number; top_k: number; is_active: boolean; updated_at: string; documents?: Array<{ id: string }>; } interface KnowledgeDocument { id: string; title: string; content: string; metadata?: Record; created_at: string; updated_at: string; } const defaultKnowledgeDocumentForm = { title: '', content: '', metadata: '', }; export function KnowledgeBases() { const { t } = useTranslation(); const { currentProject } = useProjectStore(); const [knowledgeBases, setKnowledgeBases] = useState([]); const [isLoading, setIsLoading] = useState(false); // KB Form Dialog State const [isFormOpen, setIsFormOpen] = useState(false); const [editingKb, setEditingKb] = useState(null); const [isSavingKb, setIsSavingKb] = useState(false); const [reindexingKbId, setReindexingKbId] = useState(''); // Docs Dialog State const [docsDialogOpen, setDocsDialogOpen] = useState(false); const [selectedKbForDocs, setSelectedKbForDocs] = useState(null); const [knowledgeDocuments, setKnowledgeDocuments] = useState([]); const [isLoadingDocs, setIsLoadingDocs] = useState(false); // Doc Form State inside Docs Dialog const [isSavingDoc, setIsSavingDoc] = useState(false); const [editingDocId, setEditingDocId] = useState(''); const [docForm, setDocForm] = useState(defaultKnowledgeDocumentForm); const [uploadingDocs, setUploadingDocs] = useState(false); const [uploadFiles, setUploadFiles] = useState([]); useEffect(() => { void fetchKnowledgeBases(); }, [currentProject?.id]); const fetchKnowledgeBases = async () => { if (!currentProject) { setKnowledgeBases([]); return; } setIsLoading(true); try { const data = await api.get(`/api/v1/knowledge-bases?project_id=${currentProject.id}`); setKnowledgeBases(data); } catch (err: any) { console.error(err); } finally { setIsLoading(false); } }; const handleCreateKb = () => { setEditingKb(null); setIsFormOpen(true); }; const handleEditKb = (kb: KnowledgeBase) => { setEditingKb(kb); setIsFormOpen(true); }; const handleSaveKb = async (data: KnowledgeBaseFormValues) => { if (!currentProject) return; setIsSavingKb(true); try { const payload = { ...data, project_id: currentProject.id, }; if (editingKb) { await api.put(`/api/v1/knowledge-bases/${editingKb.id}`, payload); } else { await api.post('/api/v1/knowledge-bases', payload); } setIsFormOpen(false); await fetchKnowledgeBases(); } finally { setIsSavingKb(false); } }; const handleDeleteKb = async (id: string) => { if (!window.confirm(t('confirmDeleteKnowledgeBase', 'Are you sure to delete this knowledge base?'))) { return; } try { await api.delete(`/api/v1/knowledge-bases/${id}`); await fetchKnowledgeBases(); } catch (err: any) { console.error(err); } }; const handleReindexKb = async (id: string) => { setReindexingKbId(id); try { await api.post(`/api/v1/knowledge-bases/${id}/reindex`, {}); alert(t('knowledgeBaseReindexSuccess', 'Knowledge base reindexed successfully')); } catch (err: any) { console.error(err); alert(t('knowledgeBaseReindexFailed', 'Failed to reindex knowledge base')); } finally { setReindexingKbId(''); } }; // --- Document Management Methods --- const handleManageDocs = async (kb: KnowledgeBase) => { setSelectedKbForDocs(kb); setDocsDialogOpen(true); setEditingDocId(''); setDocForm(defaultKnowledgeDocumentForm); await fetchKnowledgeDocuments(kb.id); }; const fetchKnowledgeDocuments = async (kbId: string) => { if (!kbId) return; setIsLoadingDocs(true); try { const data = await api.get(`/api/v1/knowledge-bases/${kbId}/documents`); setKnowledgeDocuments(data); } catch (err: any) { console.error(err); } finally { setIsLoadingDocs(false); } }; const resetDocForm = () => { setEditingDocId(''); setDocForm(defaultKnowledgeDocumentForm); }; const handleSaveDoc = async () => { if (!selectedKbForDocs) return; if (!docForm.title.trim() || !docForm.content.trim()) { alert(t('knowledgeDocumentTitleRequired', 'Document title and content are required')); return; } let parsedMetadata = {}; if (docForm.metadata.trim()) { try { parsedMetadata = JSON.parse(docForm.metadata.trim()); } catch { alert(t('knowledgeDocumentMetadataInvalid', 'Document metadata must be valid JSON')); return; } } setIsSavingDoc(true); try { const payload = { title: docForm.title.trim(), content: docForm.content.trim(), metadata: parsedMetadata, }; if (editingDocId) { await api.put(`/api/v1/knowledge-bases/${selectedKbForDocs.id}/documents/${editingDocId}`, payload); } else { await api.post(`/api/v1/knowledge-bases/${selectedKbForDocs.id}/documents`, payload); } resetDocForm(); await fetchKnowledgeDocuments(selectedKbForDocs.id); await fetchKnowledgeBases(); // To update doc count } catch (err: any) { console.error(err); } finally { setIsSavingDoc(false); } }; const handleEditDoc = (doc: KnowledgeDocument) => { setEditingDocId(doc.id); setDocForm({ title: doc.title || '', content: doc.content || '', metadata: doc.metadata && Object.keys(doc.metadata).length > 0 ? JSON.stringify(doc.metadata, null, 2) : '', }); }; const handleDeleteDoc = async (docId: string) => { if (!selectedKbForDocs) return; if (!window.confirm(t('confirmDeleteKnowledgeDocument', 'Are you sure to delete this document?'))) return; try { await api.delete(`/api/v1/knowledge-bases/${selectedKbForDocs.id}/documents/${docId}`); if (editingDocId === docId) resetDocForm(); await fetchKnowledgeDocuments(selectedKbForDocs.id); await fetchKnowledgeBases(); } catch (err: any) { console.error(err); } }; const handleUploadDocs = async () => { if (!selectedKbForDocs || uploadFiles.length === 0) return; setUploadingDocs(true); try { const formData = new FormData(); uploadFiles.forEach((file) => formData.append('files', file)); await api.post(`/api/v1/knowledge-bases/${selectedKbForDocs.id}/documents/upload`, formData); setUploadFiles([]); await fetchKnowledgeDocuments(selectedKbForDocs.id); await fetchKnowledgeBases(); } catch (err: any) { console.error(err); alert(t('knowledgeDocumentUploadFailed', 'Failed to upload knowledge documents')); } finally { setUploadingDocs(false); } }; return (

{t('knowledgeBases')}

{t('knowledgeBaseSettingsDesc', 'Manage knowledge bases and their documents')}

{!currentProject ? (
{t('selectProjectBeforeManageKnowledgeBase')}
) : isLoading ? (
) : knowledgeBases.length === 0 ? (

{t('noKnowledgeBases')}

) : (
{knowledgeBases.map((kb) => (

{kb.name} {!kb.is_active && ( Inactive )}

{kb.documents?.length || 0} Documents

Model {kb.embedding_model ? kb.embedding_model.substring(0, 15) + (kb.embedding_model.length > 15 ? '...' : '') : 'Default'}
Chunking {kb.chunk_size} / {kb.chunk_overlap}
{kb.description && (

{kb.description}

)}
))}
)}
{/* Form Dialog */} {editingKb ? t('updateKnowledgeBase', 'Edit Knowledge Base') : t('createKnowledgeBase', 'New Knowledge Base')}
setIsFormOpen(false)} isSubmitting={isSavingKb} />
{/* Docs Management Dialog */} { setDocsDialogOpen(open); if (!open) { setSelectedKbForDocs(null); setKnowledgeDocuments([]); } }}> {selectedKbForDocs ? t('knowledgeDocumentManagerTitle', { name: selectedKbForDocs.name }) : 'Manage Documents'}
{/* Upload Section */}
{t('knowledgeDocumentUploadTitle')}
setUploadFiles(Array.from(e.target.files || []))} disabled={uploadingDocs} className="flex-1" />
{uploadFiles.length > 0 && (
{t('knowledgeDocumentUploadSelected', { count: uploadFiles.length })}
)}
{/* Edit/Create Doc Form */}
{editingDocId ? t('updateKnowledgeDocument') : t('createKnowledgeDocument')}
setDocForm((p) => ({ ...p, title: e.target.value }))} disabled={isSavingDoc} />