From 2832ca91f3b0e41316b3916b6c0616fdacf0bd99 Mon Sep 17 00:00:00 2001 From: qixinbo Date: Sun, 29 Mar 2026 15:04:39 +0800 Subject: [PATCH] UI: kb manag optimize --- frontend/src/components/KnowledgeBaseForm.tsx | 216 ++++ frontend/src/i18n/locales/en.json | 10 +- frontend/src/i18n/locales/zh.json | 8 +- frontend/src/pages/KnowledgeBases.tsx | 952 +++++++----------- 4 files changed, 583 insertions(+), 603 deletions(-) create mode 100644 frontend/src/components/KnowledgeBaseForm.tsx diff --git a/frontend/src/components/KnowledgeBaseForm.tsx b/frontend/src/components/KnowledgeBaseForm.tsx new file mode 100644 index 0000000..beb666e --- /dev/null +++ b/frontend/src/components/KnowledgeBaseForm.tsx @@ -0,0 +1,216 @@ +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 { Save, Loader2 } from "lucide-react"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { useProjectStore } from "@/store/projectStore"; +import { api } from "@/lib/api"; + +export interface KnowledgeBaseFormValues { + name: string; + description: string; + embedding_model: string; + chunk_size: number; + chunk_overlap: number; + top_k: number; + is_active: boolean; +} + +export interface KnowledgeBaseFormProps { + initialData?: KnowledgeBaseFormValues | null; + onSubmit: (data: KnowledgeBaseFormValues) => Promise; + onCancel: () => void; + isSubmitting?: boolean; +} + +interface EmbeddingModelConfig { + id: string; + name?: string; + provider: string; + model: string; +} + +const defaultFormValues: KnowledgeBaseFormValues = { + name: '', + description: '', + embedding_model: '', + chunk_size: 512, + chunk_overlap: 50, + top_k: 3, + is_active: true, +}; + +export function KnowledgeBaseForm({ initialData, onSubmit, onCancel, isSubmitting = false }: KnowledgeBaseFormProps) { + const { t } = useTranslation(); + const { currentProject } = useProjectStore(); + const [form, setForm] = useState(initialData || defaultFormValues); + const [embeddingModels, setEmbeddingModels] = useState([]); + const [error, setError] = useState(''); + + useEffect(() => { + void fetchEmbeddingModels(); + }, []); + + const fetchEmbeddingModels = async () => { + try { + const data = await api.get('/api/v1/embedding-models'); + setEmbeddingModels(data); + } catch (err) { + console.error('Failed to fetch embedding models', err); + } + }; + + const validate = () => { + if (!currentProject) { + return t('selectProjectBeforeManageKnowledgeBase', 'Please select a project before managing knowledge bases'); + } + if (!form.name.trim()) { + return t('knowledgeBaseNameRequired', 'Knowledge base name is required'); + } + if (form.chunk_size < 64 || form.chunk_size > 4096) { + return t('knowledgeBaseChunkSizeRange', 'Chunk size must be between 64 and 4096'); + } + if (form.chunk_overlap < 0 || form.chunk_overlap > 512) { + return t('knowledgeBaseChunkOverlapRange', 'Chunk overlap must be between 0 and 512'); + } + if (form.chunk_overlap >= form.chunk_size) { + return t('knowledgeBaseChunkOverlapTooLarge', 'Chunk overlap must be less than chunk size'); + } + if (form.top_k < 1 || form.top_k > 20) { + return t('knowledgeBaseTopKRange', 'Top K must be between 1 and 20'); + } + return ''; + }; + + const handleSubmit = async () => { + setError(''); + const validationMessage = validate(); + if (validationMessage) { + setError(validationMessage); + return; + } + try { + await onSubmit(form); + } catch (err: any) { + setError(err.message || t('knowledgeBaseSaveFailed', 'Failed to save knowledge base')); + } + }; + + const selectedEmbeddingModel = embeddingModels.find(m => m.id === form.embedding_model); + + return ( +
+ {error &&
{error}
} + + {!currentProject ? ( +
+ {t('selectProjectBeforeManageKnowledgeBase')} +
+ ) : null} + +
+
+ + setForm((prev) => ({ ...prev, name: e.target.value }))} + disabled={!currentProject || isSubmitting} + /> +
+ +
+ + setForm((prev) => ({ ...prev, description: e.target.value }))} + disabled={!currentProject || isSubmitting} + /> +
+ +
+ + +
+ +
+ + setForm((prev) => ({ ...prev, chunk_size: Number(e.target.value) || 0 }))} + disabled={!currentProject || isSubmitting} + /> +
+ +
+ + setForm((prev) => ({ ...prev, chunk_overlap: Number(e.target.value) || 0 }))} + disabled={!currentProject || isSubmitting} + /> +
+ +
+ + setForm((prev) => ({ ...prev, top_k: Number(e.target.value) || 0 }))} + disabled={!currentProject || isSubmitting} + /> +
+ +
+ + setForm((prev) => ({ ...prev, is_active: checked }))} + disabled={!currentProject || isSubmitting} + /> +
+
+ +
+ + +
+
+ ); +} diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index f8b7d78..e96340b 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -141,7 +141,7 @@ "saveSettings": "Save Settings", "knowledgeBase": "Knowledge Base", "knowledgeBaseSettings": "Knowledge Base Configuration", - "knowledgeBaseSettingsDesc": "Create, edit, reindex, and manage knowledge bases in the current project.", + "knowledgeBaseSettingsDesc": "Manage knowledge bases and their documents", "knowledgeGlobalConfigTitle": "Knowledge Global Configuration", "knowledgeGlobalConfigDesc": "Configure global API base and key for knowledge service shared across projects.", "knowledgeGlobalApiBase": "API Base", @@ -169,15 +169,15 @@ "knowledgeGlobalArkModelRequiredForTest": "For Volcengine Ark, model name is required for connection testing (Model ID or Endpoint ID)", "saveKnowledgeGlobalConfig": "Save Global Configuration", "refresh": "Refresh", - "knowledgeBaseName": "Knowledge Base Name", - "knowledgeBaseNamePlaceholder": "Enter knowledge base name", - "knowledgeBaseDescriptionPlaceholder": "Enter knowledge base description (optional)", + "knowledgeBaseName": "Name", + "knowledgeBaseNamePlaceholder": "e.g. Sales Q&A", + "knowledgeBaseDescriptionPlaceholder": "Optional description", "knowledgeBaseEmbeddingModel": "Embedding Model", "knowledgeBaseEmbeddingModelPlaceholder": "Select an embedding model", "knowledgeBaseChunkSize": "Chunk Size", "knowledgeBaseChunkOverlap": "Chunk Overlap", "knowledgeBaseTopK": "Top K", - "createKnowledgeBase": "Create Knowledge Base", + "createKnowledgeBase": "New Knowledge Base", "updateKnowledgeBase": "Update Knowledge Base", "knowledgeBaseList": "Knowledge Base List", "knowledgeBaseMeta": "{{count}} docs · Updated {{updatedAt}}", diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json index 3b3a4f0..48d4e27 100644 --- a/frontend/src/i18n/locales/zh.json +++ b/frontend/src/i18n/locales/zh.json @@ -154,7 +154,7 @@ "saveSettings": "保存设置", "knowledgeBase": "知识库", "knowledgeBaseSettings": "知识库配置与建库管理", - "knowledgeBaseSettingsDesc": "在当前项目下创建、编辑、重建索引并维护知识库配置。", + "knowledgeBaseSettingsDesc": "管理知识库及其文档内容", "knowledgeGlobalConfigTitle": "知识库全局配置", "knowledgeGlobalConfigDesc": "配置知识库服务的全局 API 地址与密钥,所有项目共享。", "knowledgeGlobalApiBase": "API Base", @@ -183,14 +183,14 @@ "saveKnowledgeGlobalConfig": "保存全局配置", "refresh": "刷新", "knowledgeBaseName": "知识库名称", - "knowledgeBaseNamePlaceholder": "请输入知识库名称", - "knowledgeBaseDescriptionPlaceholder": "请输入知识库描述(可选)", + "knowledgeBaseNamePlaceholder": "例如:销售问答库", + "knowledgeBaseDescriptionPlaceholder": "选填,知识库描述", "knowledgeBaseEmbeddingModel": "Embedding 模型", "knowledgeBaseEmbeddingModelPlaceholder": "请选择一个嵌入模型", "knowledgeBaseChunkSize": "Chunk Size", "knowledgeBaseChunkOverlap": "Chunk Overlap", "knowledgeBaseTopK": "Top K", - "createKnowledgeBase": "创建知识库", + "createKnowledgeBase": "新建知识库", "updateKnowledgeBase": "更新知识库", "knowledgeBaseList": "知识库列表", "knowledgeBaseMeta": "文档 {{count}} 个 · 更新时间 {{updatedAt}}", diff --git a/frontend/src/pages/KnowledgeBases.tsx b/frontend/src/pages/KnowledgeBases.tsx index 576ddeb..bdbfcaf 100644 --- a/frontend/src/pages/KnowledgeBases.tsx +++ b/frontend/src/pages/KnowledgeBases.tsx @@ -3,13 +3,12 @@ import { useTranslation } from 'react-i18next'; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"; -import { Save, Loader2, Database, RefreshCw, Pencil, Trash2, FileText, Plus } from "lucide-react"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +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 { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; +import { KnowledgeBaseForm, type KnowledgeBaseFormValues } from "@/components/KnowledgeBaseForm"; interface KnowledgeBase { id: string; @@ -25,16 +24,6 @@ interface KnowledgeBase { documents?: Array<{ id: string }>; } -interface KnowledgeBaseForm { - name: string; - description: string; - embedding_model: string; - chunk_size: number; - chunk_overlap: number; - top_k: number; - is_active: boolean; -} - interface KnowledgeDocument { id: string; title: string; @@ -44,23 +33,6 @@ interface KnowledgeDocument { updated_at: string; } -interface EmbeddingModelConfig { - id: string; - name?: string; - provider: string; - model: string; -} - -const defaultKnowledgeBaseForm: KnowledgeBaseForm = { - name: '', - description: '', - embedding_model: '', - chunk_size: 512, - chunk_overlap: 50, - top_k: 3, - is_active: true, -}; - const defaultKnowledgeDocumentForm = { title: '', content: '', @@ -70,678 +42,470 @@ const defaultKnowledgeDocumentForm = { export function KnowledgeBases() { const { t } = useTranslation(); const { currentProject } = useProjectStore(); - const [isLoadingKnowledgeBases, setIsLoadingKnowledgeBases] = useState(false); - const [isSavingKnowledgeBase, setIsSavingKnowledgeBase] = useState(false); - const [deletingKnowledgeBaseId, setDeletingKnowledgeBaseId] = useState(''); - const [reindexingKnowledgeBaseId, setReindexingKnowledgeBaseId] = useState(''); const [knowledgeBases, setKnowledgeBases] = useState([]); - const [editingKnowledgeBaseId, setEditingKnowledgeBaseId] = useState(''); - const [knowledgeBaseForm, setKnowledgeBaseForm] = useState(defaultKnowledgeBaseForm); - const [selectedKnowledgeBaseId, setSelectedKnowledgeBaseId] = 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 [isLoadingKnowledgeDocuments, setIsLoadingKnowledgeDocuments] = useState(false); - const [isSavingKnowledgeDocument, setIsSavingKnowledgeDocument] = useState(false); - const [deletingKnowledgeDocumentId, setDeletingKnowledgeDocumentId] = useState(''); - const [editingKnowledgeDocumentId, setEditingKnowledgeDocumentId] = useState(''); - const [knowledgeDocumentForm, setKnowledgeDocumentForm] = useState(defaultKnowledgeDocumentForm); - const [uploadingKnowledgeDocuments, setUploadingKnowledgeDocuments] = useState(false); - const [knowledgeUploadFiles, setKnowledgeUploadFiles] = useState([]); - const [embeddingModels, setEmbeddingModels] = useState([]); - const [error, setError] = useState(''); - const [success, setSuccess] = 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]); - useEffect(() => { - void fetchEmbeddingModels(); - }, []); - - const fetchEmbeddingModels = async () => { - try { - const data = await api.get('/api/v1/embedding-models'); - setEmbeddingModels(data); - } catch (err) { - console.error('Failed to fetch embedding models', err); - } - }; - const fetchKnowledgeBases = async () => { if (!currentProject) { setKnowledgeBases([]); return; } - setIsLoadingKnowledgeBases(true); + setIsLoading(true); try { const data = await api.get(`/api/v1/knowledge-bases?project_id=${currentProject.id}`); setKnowledgeBases(data); - if (editingKnowledgeBaseId && !data.find((item) => item.id === editingKnowledgeBaseId)) { - setEditingKnowledgeBaseId(''); - setKnowledgeBaseForm(defaultKnowledgeBaseForm); - } - if (selectedKnowledgeBaseId && !data.find((item) => item.id === selectedKnowledgeBaseId)) { - setSelectedKnowledgeBaseId(''); - setKnowledgeDocuments([]); - setEditingKnowledgeDocumentId(''); - setKnowledgeDocumentForm(defaultKnowledgeDocumentForm); - } } catch (err: any) { - setError(err.message || t('knowledgeBaseLoadFailed', 'Failed to load knowledge bases')); + console.error(err); } finally { - setIsLoadingKnowledgeBases(false); + setIsLoading(false); } }; - const resetKnowledgeBaseForm = () => { - setEditingKnowledgeBaseId(''); - setKnowledgeBaseForm(defaultKnowledgeBaseForm); + const handleCreateKb = () => { + setEditingKb(null); + setIsFormOpen(true); }; - const validateKnowledgeBaseForm = () => { - if (!currentProject) { - return t('selectProjectBeforeManageKnowledgeBase', 'Please select a project before managing knowledge bases'); - } - if (!knowledgeBaseForm.name.trim()) { - return t('knowledgeBaseNameRequired', 'Knowledge base name is required'); - } - if (knowledgeBaseForm.chunk_size < 64 || knowledgeBaseForm.chunk_size > 4096) { - return t('knowledgeBaseChunkSizeRange', 'Chunk size must be between 64 and 4096'); - } - if (knowledgeBaseForm.chunk_overlap < 0 || knowledgeBaseForm.chunk_overlap > 512) { - return t('knowledgeBaseChunkOverlapRange', 'Chunk overlap must be between 0 and 512'); - } - if (knowledgeBaseForm.chunk_overlap >= knowledgeBaseForm.chunk_size) { - return t('knowledgeBaseChunkOverlapTooLarge', 'Chunk overlap must be less than chunk size'); - } - if (knowledgeBaseForm.top_k < 1 || knowledgeBaseForm.top_k > 20) { - return t('knowledgeBaseTopKRange', 'Top K must be between 1 and 20'); - } - return ''; + const handleEditKb = (kb: KnowledgeBase) => { + setEditingKb(kb); + setIsFormOpen(true); }; - const handleSaveKnowledgeBase = async () => { - setError(''); - setSuccess(''); - const validationMessage = validateKnowledgeBaseForm(); - if (validationMessage) { - setError(validationMessage); - return; - } + const handleSaveKb = async (data: KnowledgeBaseFormValues) => { if (!currentProject) return; - setIsSavingKnowledgeBase(true); + setIsSavingKb(true); try { const payload = { - name: knowledgeBaseForm.name.trim(), - description: knowledgeBaseForm.description.trim() || null, - embedding_model: knowledgeBaseForm.embedding_model.trim() || null, - chunk_size: knowledgeBaseForm.chunk_size, - chunk_overlap: knowledgeBaseForm.chunk_overlap, - top_k: knowledgeBaseForm.top_k, - is_active: knowledgeBaseForm.is_active, + ...data, project_id: currentProject.id, }; - if (editingKnowledgeBaseId) { - await api.put(`/api/v1/knowledge-bases/${editingKnowledgeBaseId}`, payload); - setSuccess(t('knowledgeBaseUpdated', 'Knowledge base updated successfully')); + if (editingKb) { + await api.put(`/api/v1/knowledge-bases/${editingKb.id}`, payload); } else { await api.post('/api/v1/knowledge-bases', payload); - setSuccess(t('knowledgeBaseCreated', 'Knowledge base created successfully')); } + setIsFormOpen(false); await fetchKnowledgeBases(); - resetKnowledgeBaseForm(); - } catch (err: any) { - setError(err.message || t('knowledgeBaseSaveFailed', 'Failed to save knowledge base')); } finally { - setIsSavingKnowledgeBase(false); + setIsSavingKb(false); } }; - const handleEditKnowledgeBase = (item: KnowledgeBase) => { - setEditingKnowledgeBaseId(item.id); - setKnowledgeBaseForm({ - name: item.name || '', - description: item.description || '', - embedding_model: item.embedding_model || '', - chunk_size: item.chunk_size, - chunk_overlap: item.chunk_overlap, - top_k: item.top_k, - is_active: item.is_active, - }); - }; - - const handleDeleteKnowledgeBase = async (id: string) => { + const handleDeleteKb = async (id: string) => { if (!window.confirm(t('confirmDeleteKnowledgeBase', 'Are you sure to delete this knowledge base?'))) { return; } - setError(''); - setSuccess(''); - setDeletingKnowledgeBaseId(id); try { await api.delete(`/api/v1/knowledge-bases/${id}`); - setSuccess(t('knowledgeBaseDeleted', 'Knowledge base deleted successfully')); - if (editingKnowledgeBaseId === id) { - resetKnowledgeBaseForm(); - } - if (selectedKnowledgeBaseId === id) { - setSelectedKnowledgeBaseId(''); - setKnowledgeDocuments([]); - setEditingKnowledgeDocumentId(''); - setKnowledgeDocumentForm(defaultKnowledgeDocumentForm); - } await fetchKnowledgeBases(); } catch (err: any) { - setError(err.message || t('knowledgeBaseDeleteFailed', 'Failed to delete knowledge base')); - } finally { - setDeletingKnowledgeBaseId(''); + console.error(err); } }; - const handleReindexKnowledgeBase = async (id: string) => { - setError(''); - setSuccess(''); - setReindexingKnowledgeBaseId(id); + const handleReindexKb = async (id: string) => { + setReindexingKbId(id); try { await api.post(`/api/v1/knowledge-bases/${id}/reindex`, {}); - setSuccess(t('knowledgeBaseReindexSuccess', 'Knowledge base reindexed successfully')); + alert(t('knowledgeBaseReindexSuccess', 'Knowledge base reindexed successfully')); } catch (err: any) { - setError(err.message || t('knowledgeBaseReindexFailed', 'Failed to reindex knowledge base')); + console.error(err); + alert(t('knowledgeBaseReindexFailed', 'Failed to reindex knowledge base')); } finally { - setReindexingKnowledgeBaseId(''); + setReindexingKbId(''); } }; - const resetKnowledgeDocumentForm = () => { - setEditingKnowledgeDocumentId(''); - setKnowledgeDocumentForm(defaultKnowledgeDocumentForm); + // --- 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) { - setKnowledgeDocuments([]); - return; - } - setIsLoadingKnowledgeDocuments(true); + if (!kbId) return; + setIsLoadingDocs(true); try { const data = await api.get(`/api/v1/knowledge-bases/${kbId}/documents`); setKnowledgeDocuments(data); - if (editingKnowledgeDocumentId && !data.find((item) => item.id === editingKnowledgeDocumentId)) { - resetKnowledgeDocumentForm(); - } } catch (err: any) { - setError(err.message || t('knowledgeDocumentLoadFailed', 'Failed to load knowledge documents')); + console.error(err); } finally { - setIsLoadingKnowledgeDocuments(false); + setIsLoadingDocs(false); } }; - const handleOpenKnowledgeDocuments = async (kbId: string) => { - if (selectedKnowledgeBaseId === kbId) { - setSelectedKnowledgeBaseId(''); - setKnowledgeDocuments([]); - resetKnowledgeDocumentForm(); + 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; } - setSelectedKnowledgeBaseId(kbId); - resetKnowledgeDocumentForm(); - await fetchKnowledgeDocuments(kbId); - }; + + let parsedMetadata = {}; + if (docForm.metadata.trim()) { + try { + parsedMetadata = JSON.parse(docForm.metadata.trim()); + } catch { + alert(t('knowledgeDocumentMetadataInvalid', 'Document metadata must be valid JSON')); + return; + } + } - const validateKnowledgeDocumentForm = () => { - if (!selectedKnowledgeBaseId) { - return t('selectKnowledgeBaseToManageDocuments', 'Please select a knowledge base to manage documents'); - } - if (!knowledgeDocumentForm.title.trim()) { - return t('knowledgeDocumentTitleRequired', 'Document title is required'); - } - if (!knowledgeDocumentForm.content.trim()) { - return t('knowledgeDocumentContentRequired', 'Document content is required'); - } - const metadataText = knowledgeDocumentForm.metadata.trim(); - if (!metadataText) { - return ''; - } + setIsSavingDoc(true); try { - JSON.parse(metadataText); - return ''; - } catch { - return t('knowledgeDocumentMetadataInvalid', 'Document metadata must be valid JSON'); - } - }; - - const handleSaveKnowledgeDocument = async () => { - setError(''); - setSuccess(''); - const validationMessage = validateKnowledgeDocumentForm(); - if (validationMessage) { - setError(validationMessage); - return; - } - if (!selectedKnowledgeBaseId) return; - setIsSavingKnowledgeDocument(true); - try { - const metadataText = knowledgeDocumentForm.metadata.trim(); const payload = { - title: knowledgeDocumentForm.title.trim(), - content: knowledgeDocumentForm.content.trim(), - metadata: metadataText ? JSON.parse(metadataText) : {}, + title: docForm.title.trim(), + content: docForm.content.trim(), + metadata: parsedMetadata, }; - if (editingKnowledgeDocumentId) { - await api.put(`/api/v1/knowledge-bases/${selectedKnowledgeBaseId}/documents/${editingKnowledgeDocumentId}`, payload); - setSuccess(t('knowledgeDocumentUpdated', 'Knowledge document updated successfully')); + + if (editingDocId) { + await api.put(`/api/v1/knowledge-bases/${selectedKbForDocs.id}/documents/${editingDocId}`, payload); } else { - await api.post(`/api/v1/knowledge-bases/${selectedKnowledgeBaseId}/documents`, payload); - setSuccess(t('knowledgeDocumentCreated', 'Knowledge document created successfully')); + await api.post(`/api/v1/knowledge-bases/${selectedKbForDocs.id}/documents`, payload); } - await fetchKnowledgeDocuments(selectedKnowledgeBaseId); - await fetchKnowledgeBases(); - resetKnowledgeDocumentForm(); + resetDocForm(); + await fetchKnowledgeDocuments(selectedKbForDocs.id); + await fetchKnowledgeBases(); // To update doc count } catch (err: any) { - setError(err.message || t('knowledgeDocumentSaveFailed', 'Failed to save knowledge document')); + console.error(err); } finally { - setIsSavingKnowledgeDocument(false); + setIsSavingDoc(false); } }; - const handleEditKnowledgeDocument = (item: KnowledgeDocument) => { - setEditingKnowledgeDocumentId(item.id); - setKnowledgeDocumentForm({ - title: item.title || '', - content: item.content || '', - metadata: item.metadata && Object.keys(item.metadata).length > 0 ? JSON.stringify(item.metadata, null, 2) : '', + 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 handleDeleteKnowledgeDocument = async (docId: string) => { - if (!selectedKnowledgeBaseId) return; - if (!window.confirm(t('confirmDeleteKnowledgeDocument', 'Are you sure to delete this document?'))) { - return; - } - setError(''); - setSuccess(''); - setDeletingKnowledgeDocumentId(docId); + 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/${selectedKnowledgeBaseId}/documents/${docId}`); - if (editingKnowledgeDocumentId === docId) { - resetKnowledgeDocumentForm(); - } - setSuccess(t('knowledgeDocumentDeleted', 'Knowledge document deleted successfully')); - await fetchKnowledgeDocuments(selectedKnowledgeBaseId); + await api.delete(`/api/v1/knowledge-bases/${selectedKbForDocs.id}/documents/${docId}`); + if (editingDocId === docId) resetDocForm(); + await fetchKnowledgeDocuments(selectedKbForDocs.id); await fetchKnowledgeBases(); } catch (err: any) { - setError(err.message || t('knowledgeDocumentDeleteFailed', 'Failed to delete knowledge document')); - } finally { - setDeletingKnowledgeDocumentId(''); + console.error(err); } }; - const handleUploadKnowledgeDocuments = async () => { - setError(''); - setSuccess(''); - if (!selectedKnowledgeBaseId) { - setError(t('selectKnowledgeBaseToManageDocuments', 'Please select a knowledge base to manage documents')); - return; - } - if (knowledgeUploadFiles.length === 0) { - setError(t('knowledgeDocumentUploadEmpty', 'Please select files to upload')); - return; - } - setUploadingKnowledgeDocuments(true); + const handleUploadDocs = async () => { + if (!selectedKbForDocs || uploadFiles.length === 0) return; + setUploadingDocs(true); try { const formData = new FormData(); - knowledgeUploadFiles.forEach((file) => formData.append('files', file)); - await api.post(`/api/v1/knowledge-bases/${selectedKnowledgeBaseId}/documents/upload`, formData); - setSuccess(t('knowledgeDocumentUploadSuccess', { count: knowledgeUploadFiles.length })); - setKnowledgeUploadFiles([]); - await fetchKnowledgeDocuments(selectedKnowledgeBaseId); + 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) { - setError(err.message || t('knowledgeDocumentUploadFailed', 'Failed to upload knowledge documents')); + console.error(err); + alert(t('knowledgeDocumentUploadFailed', 'Failed to upload knowledge documents')); } finally { - setUploadingKnowledgeDocuments(false); + setUploadingDocs(false); } }; - const selectedKnowledgeBase = knowledgeBases.find((item) => item.id === selectedKnowledgeBaseId) || null; - const selectedEmbeddingModel = embeddingModels.find(m => m.id === knowledgeBaseForm.embedding_model); - return ( -
-
-
- - {t('knowledgeBases')} +
+
+
+

{t('knowledgeBases')}

+

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

+
-
-
- {error &&
{error}
} - {success &&
{success}
} - - - - - - {t('knowledgeBaseSettings')} - - {t('knowledgeBaseSettingsDesc')} - - - {!currentProject ? ( -
- {t('selectProjectBeforeManageKnowledgeBase')} +
+ {!currentProject ? ( +
+
+ {t('selectProjectBeforeManageKnowledgeBase')} +
+
+ ) : isLoading ? ( +
+ +
+ ) : knowledgeBases.length === 0 ? ( +
+ +

{t('noKnowledgeBases')}

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

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

+

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

+
+
+
+ + + + +
- ) : null} - -
-
- - setKnowledgeBaseForm((prev) => ({ ...prev, name: e.target.value }))} - disabled={!currentProject} - /> -
- -
- - setKnowledgeBaseForm((prev) => ({ ...prev, description: e.target.value }))} - disabled={!currentProject} - /> -
- -
- - -
- +
- - setKnowledgeBaseForm((prev) => ({ ...prev, chunk_size: Number(e.target.value) || 0 }))} - disabled={!currentProject} - /> -
- -
- - setKnowledgeBaseForm((prev) => ({ ...prev, chunk_overlap: Number(e.target.value) || 0 }))} - disabled={!currentProject} - /> -
- -
- - setKnowledgeBaseForm((prev) => ({ ...prev, top_k: Number(e.target.value) || 0 }))} - disabled={!currentProject} - /> -
- -
- - setKnowledgeBaseForm((prev) => ({ ...prev, is_active: checked }))} - disabled={!currentProject} - /> +
+ 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} +

+ )}
+ ))} +
+ )} +
-
- {editingKnowledgeBaseId ? ( - - ) : null} -
- -
-
{t('knowledgeBaseList')}
- {isLoadingKnowledgeBases ? ( -
- -
- ) : knowledgeBases.length === 0 ? ( -
- {t('noKnowledgeBases')} -
- ) : ( -
- {knowledgeBases.map((item) => ( -
-
-
{item.name}
-
- {t('knowledgeBaseMeta', { - count: item.documents?.length || 0, - updatedAt: new Date(item.updated_at).toLocaleString(), - })} -
- {item.description ? ( -
{item.description}
- ) : null} -
-
- - - - -
-
- ))} -
- )} -
- -
-
- {selectedKnowledgeBase - ? t('knowledgeDocumentManagerTitle', { name: selectedKnowledgeBase.name }) - : t('knowledgeDocumentManagerTitleEmpty')} + {uploadFiles.length > 0 && ( +
+ {t('knowledgeDocumentUploadSelected', { count: uploadFiles.length })}
- {!selectedKnowledgeBase ? ( -
- {t('selectKnowledgeBaseToManageDocuments')} -
- ) : ( -
-
-
{t('knowledgeDocumentUploadTitle')}
- setKnowledgeUploadFiles(Array.from(e.target.files || []))} - disabled={uploadingKnowledgeDocuments} - /> -
- {t('knowledgeDocumentUploadHint')} -
-
-
- {knowledgeUploadFiles.length > 0 - ? t('knowledgeDocumentUploadSelected', { count: knowledgeUploadFiles.length }) - : t('knowledgeDocumentUploadNone')} -
- -
-
+ )} +
-
-
- - setKnowledgeDocumentForm((prev) => ({ ...prev, title: e.target.value }))} - disabled={isSavingKnowledgeDocument} - /> -
-
- -