import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Database, Trash2, RefreshCw, Search, ChevronLeft, ChevronRight, Loader2, FileText, Wand2, CheckCircle2 } from 'lucide-react'; import { api } from '../lib/api'; import { useAuth } from '../lib/auth'; import { useTenant } from '../lib/tenant'; import { useToast } from '../hooks/useToast'; import { logError } from '../lib/logger'; interface KbEntry { id: string; content: string; metadata?: Record; createdAt: string; } interface KbResponse { entries: KbEntry[]; total: number; page: number; limit: number; } const PAGE_SIZE = 20; export default function KnowledgeBasePage() { const { t } = useTranslation(); const toast = useToast(); const { token } = useAuth(); const { selectedOrgId } = useTenant(); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [search, setSearch] = useState(''); const [deletingId, setDeletingId] = useState(null); const [reindexing, setReindexing] = useState(false); const [genDescription, setGenDescription] = useState(''); const [generating, setGenerating] = useState(false); const [genResult, setGenResult] = useState<{ faqCount: number; chunksIndexed: number; preview: Array<{ question: string; answer: string }> } | null>(null); const fetchEntries = async (p = page) => { if (!token || !selectedOrgId) return; setLoading(true); try { const res = await api.get( `/v1/organizations/${selectedOrgId}/kb?page=${p}&limit=${PAGE_SIZE}`, token ); setData(res); } catch (err) { logError('[KB] Failed to fetch entries:', err); } finally { setLoading(false); } }; useEffect(() => { fetchEntries(1); setPage(1); }, [token, selectedOrgId]); const handleDelete = async (id: string) => { if (!token || !selectedOrgId) return; if (!confirm(t('knowledge.confirm_delete'))) return; setDeletingId(id); try { await api.delete(`/v1/organizations/${selectedOrgId}/kb/${id}`, token); await fetchEntries(page); } catch (err: any) { logError('[KB] Delete failed:', err); toast.error(err?.message ?? t('knowledge.delete_error')); } finally { setDeletingId(null); } }; const handleReindex = async () => { if (!token || !selectedOrgId) return; setReindexing(true); try { await api.post(`/v1/organizations/${selectedOrgId}/index-kb`, {}, token); toast.success(t('knowledge.reindex_success')); } catch (err: any) { const msg: string = err?.message ?? ''; if (msg.toLowerCase().includes('no kb url') || msg.toLowerCase().includes('not configured')) { toast.error(t('knowledge.no_kb_url')); } else { logError('[KB] Re-index failed:', err); toast.error(t('knowledge.reindex_error')); } } finally { setReindexing(false); } }; const handleGenerate = async () => { if (!token || !selectedOrgId || !genDescription.trim()) return; setGenerating(true); setGenResult(null); try { const res = await api.post( `/v1/organizations/${selectedOrgId}/kb/generate`, { description: genDescription }, token ); setGenResult(res); toast.success(t('knowledge.generate_success_count', { count: res.faqCount })); await fetchEntries(1); setPage(1); } catch (err: any) { logError('[KB] Generate failed:', err); toast.error(err?.message ?? t('knowledge.generate_error')); } finally { setGenerating(false); } }; const handlePageChange = (newPage: number) => { setPage(newPage); fetchEntries(newPage); }; const filteredEntries = (data?.entries ?? []).filter(e => !search || e.content.toLowerCase().includes(search.toLowerCase()) ); const totalPages = data ? Math.ceil(data.total / PAGE_SIZE) : 1; return (

{t('knowledge.title')}

{data ? `${data.total} ${t('knowledge.chunks')}` : t('common.loading')}

{/* KB Auto-Generation Panel */}

{t('knowledge.generate_from_desc')}