Update knowledge base layout

This commit is contained in:
Xin Wang
2026-02-09 07:51:01 +08:00
parent bdd5a7a274
commit a12ba0c4a4

View File

@@ -1,8 +1,8 @@
import React, { useEffect, useState, useRef } from 'react'; 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 { Button, Input, TableHeader, TableRow, TableHead, TableCell, Card, Dialog, Badge } from '../components/UI';
import { KnowledgeBase } from '../types'; 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 = [ const EMBEDDING_OPTIONS = [
'text-embedding-3-small', 'text-embedding-3-small',
@@ -19,6 +19,8 @@ export const KnowledgeBasePage: React.FC = () => {
const [isKbModalOpen, setIsKbModalOpen] = useState(false); const [isKbModalOpen, setIsKbModalOpen] = useState(false);
const [editingKb, setEditingKb] = useState<KnowledgeBase | null>(null); const [editingKb, setEditingKb] = useState<KnowledgeBase | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [embeddingOptions, setEmbeddingOptions] = useState<string[]>(EMBEDDING_OPTIONS);
const [openMenuKbId, setOpenMenuKbId] = useState<string | null>(null);
const [kbName, setKbName] = useState(''); const [kbName, setKbName] = useState('');
const [kbDescription, setKbDescription] = useState(''); const [kbDescription, setKbDescription] = useState('');
@@ -48,6 +50,28 @@ export const KnowledgeBasePage: React.FC = () => {
useEffect(() => { useEffect(() => {
refreshKnowledgeBases(); 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) => { const handleSelect = (kb: KnowledgeBase) => {
@@ -207,33 +231,46 @@ export const KnowledgeBasePage: React.FC = () => {
<Card <Card
key={kb.id} key={kb.id}
className="p-6 hover:border-primary/50 transition-colors cursor-pointer group relative" className="p-6 hover:border-primary/50 transition-colors cursor-pointer group relative"
onClick={() => openEditKb(kb)}
> >
<div className="absolute top-3 right-3 flex items-center gap-1 z-10"> <div className="absolute top-3 right-3 z-20">
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
title="更多操作"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
openEditKb(kb); setOpenMenuKbId((prev) => (prev === kb.id ? null : kb.id));
}} }}
title="编辑知识库"
> >
<Pencil className="h-4 w-4" /> <MoreVertical className="h-4 w-4" />
</Button> </Button>
<Button {openMenuKbId === kb.id && (
variant="ghost" <div
size="icon" className="absolute right-0 mt-1 w-36 rounded-md border border-white/10 bg-card/95 backdrop-blur-md shadow-lg overflow-hidden"
className="text-destructive hover:text-destructive" onClick={(e) => e.stopPropagation()}
onClick={(e) => { >
e.stopPropagation(); <button
className="w-full text-left px-3 py-2 text-sm hover:bg-white/10 text-foreground"
onClick={() => {
setOpenMenuKbId(null);
handleSelect(kb);
}}
>
</button>
<button
className="w-full text-left px-3 py-2 text-sm hover:bg-white/10 text-destructive"
onClick={() => {
setOpenMenuKbId(null);
handleDeleteKb(kb); handleDeleteKb(kb);
}} }}
title="删除知识库"
> >
<Trash2 className="h-4 w-4" />
</Button> </button>
</div>
)}
</div> </div>
<div onClick={() => handleSelect(kb)}>
<div className="flex items-start justify-between mb-4"> <div className="flex items-start justify-between mb-4">
<div className="p-2 bg-primary/10 rounded-lg text-primary"> <div className="p-2 bg-primary/10 rounded-lg text-primary">
<FileText className="h-6 w-6" /> <FileText className="h-6 w-6" />
@@ -246,7 +283,6 @@ export const KnowledgeBasePage: React.FC = () => {
<p>: {kb.chunkSize ?? 500}/{kb.chunkOverlap ?? 50}</p> <p>: {kb.chunkSize ?? 500}/{kb.chunkOverlap ?? 50}</p>
<p>: {kb.createdAt}</p> <p>: {kb.createdAt}</p>
</div> </div>
</div>
</Card> </Card>
))} ))}
@@ -282,6 +318,7 @@ export const KnowledgeBasePage: React.FC = () => {
setKbChunkSize={setKbChunkSize} setKbChunkSize={setKbChunkSize}
kbChunkOverlap={kbChunkOverlap} kbChunkOverlap={kbChunkOverlap}
setKbChunkOverlap={setKbChunkOverlap} setKbChunkOverlap={setKbChunkOverlap}
embeddingOptions={embeddingOptions}
/> />
</div> </div>
); );
@@ -303,6 +340,7 @@ const KnowledgeBaseModal: React.FC<{
setKbChunkSize: (v: number) => void; setKbChunkSize: (v: number) => void;
kbChunkOverlap: number; kbChunkOverlap: number;
setKbChunkOverlap: (v: number) => void; setKbChunkOverlap: (v: number) => void;
embeddingOptions: string[];
}> = ({ }> = ({
isOpen, isOpen,
onClose, onClose,
@@ -319,6 +357,7 @@ const KnowledgeBaseModal: React.FC<{
setKbChunkSize, setKbChunkSize,
kbChunkOverlap, kbChunkOverlap,
setKbChunkOverlap, setKbChunkOverlap,
embeddingOptions,
}) => ( }) => (
<Dialog <Dialog
isOpen={isOpen} isOpen={isOpen}
@@ -361,7 +400,7 @@ const KnowledgeBaseModal: React.FC<{
value={kbEmbeddingModel} value={kbEmbeddingModel}
onChange={(e) => setKbEmbeddingModel(e.target.value)} onChange={(e) => setKbEmbeddingModel(e.target.value)}
> >
{EMBEDDING_OPTIONS.map((m) => ( {embeddingOptions.map((m) => (
<option key={m} value={m}>{m}</option> <option key={m} value={m}>{m}</option>
))} ))}
</select> </select>