diff --git a/web/pages/KnowledgeBase.tsx b/web/pages/KnowledgeBase.tsx index 9fa3596..9bad711 100644 --- a/web/pages/KnowledgeBase.tsx +++ b/web/pages/KnowledgeBase.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useState, useRef } from 'react'; -import { Search, Plus, FileText, Upload, ArrowLeft, CloudUpload, File as FileIcon, X, Pencil, Trash2, Settings2 } from 'lucide-react'; +import { Search, Plus, FileText, Upload, ArrowLeft, CloudUpload, File as FileIcon, X, Pencil, Trash2, Settings2, MoreVertical } from 'lucide-react'; import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Card, Dialog, Badge } from '../components/UI'; import { KnowledgeBase } from '../types'; -import { createKnowledgeBase, deleteKnowledgeBase, deleteKnowledgeDocument, fetchKnowledgeBases, updateKnowledgeBase, uploadKnowledgeDocument } from '../services/backendApi'; +import { createKnowledgeBase, deleteKnowledgeBase, deleteKnowledgeDocument, fetchKnowledgeBases, fetchLLMModels, updateKnowledgeBase, uploadKnowledgeDocument } from '../services/backendApi'; const EMBEDDING_OPTIONS = [ 'text-embedding-3-small', @@ -19,6 +19,8 @@ export const KnowledgeBasePage: React.FC = () => { const [isKbModalOpen, setIsKbModalOpen] = useState(false); const [editingKb, setEditingKb] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [embeddingOptions, setEmbeddingOptions] = useState(EMBEDDING_OPTIONS); + const [openMenuKbId, setOpenMenuKbId] = useState(null); const [kbName, setKbName] = useState(''); const [kbDescription, setKbDescription] = useState(''); @@ -48,6 +50,28 @@ export const KnowledgeBasePage: React.FC = () => { useEffect(() => { refreshKnowledgeBases(); + const loadEmbeddingModels = async () => { + try { + const models = await fetchLLMModels(); + const fromDb = models + .filter((m) => m.type === 'embedding') + .map((m) => (m.modelName || m.name || '').trim()) + .filter(Boolean); + const merged = Array.from(new Set([...fromDb, ...EMBEDDING_OPTIONS])); + if (merged.length > 0) { + setEmbeddingOptions(merged); + } + } catch { + setEmbeddingOptions(EMBEDDING_OPTIONS); + } + }; + loadEmbeddingModels(); + }, []); + + useEffect(() => { + const onDocClick = () => setOpenMenuKbId(null); + document.addEventListener('click', onDocClick); + return () => document.removeEventListener('click', onDocClick); }, []); const handleSelect = (kb: KnowledgeBase) => { @@ -207,45 +231,57 @@ export const KnowledgeBasePage: React.FC = () => { openEditKb(kb)} > -
+
- -
-
handleSelect(kb)}> -
-
- + {openMenuKbId === kb.id && ( +
e.stopPropagation()} + > + +
- {kb.embeddingModel || 'embedding'} -
-

{kb.name}

-
-

文档数量: {kb.documents.length}

-

分片数量: {kb.chunkSize ?? 500}/{kb.chunkOverlap ?? 50}

-

创建时间: {kb.createdAt}

+ )} +
+
+
+
+ {kb.embeddingModel || 'embedding'} +
+

{kb.name}

+
+

文档数量: {kb.documents.length}

+

分片数量: {kb.chunkSize ?? 500}/{kb.chunkOverlap ?? 50}

+

创建时间: {kb.createdAt}

))} @@ -282,6 +318,7 @@ export const KnowledgeBasePage: React.FC = () => { setKbChunkSize={setKbChunkSize} kbChunkOverlap={kbChunkOverlap} setKbChunkOverlap={setKbChunkOverlap} + embeddingOptions={embeddingOptions} />
); @@ -303,6 +340,7 @@ const KnowledgeBaseModal: React.FC<{ setKbChunkSize: (v: number) => void; kbChunkOverlap: number; setKbChunkOverlap: (v: number) => void; + embeddingOptions: string[]; }> = ({ isOpen, onClose, @@ -319,6 +357,7 @@ const KnowledgeBaseModal: React.FC<{ setKbChunkSize, kbChunkOverlap, setKbChunkOverlap, + embeddingOptions, }) => ( setKbEmbeddingModel(e.target.value)} > - {EMBEDDING_OPTIONS.map((m) => ( + {embeddingOptions.map((m) => ( ))}