Add index button

This commit is contained in:
Xin Wang
2026-02-10 10:42:40 +08:00
parent aa2a358b38
commit 375181a524
2 changed files with 103 additions and 3 deletions

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, MoreHorizontal } from 'lucide-react'; import { Search, Plus, FileText, Upload, ArrowLeft, CloudUpload, File as FileIcon, X, Pencil, Trash2, Settings2, MoreHorizontal } 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, KnowledgeDocument } from '../types';
import { createKnowledgeBase, deleteKnowledgeBase, deleteKnowledgeDocument, fetchKnowledgeBaseById, fetchKnowledgeBases, fetchLLMModels, searchKnowledgeBase, updateKnowledgeBase, uploadKnowledgeDocument, type KnowledgeSearchResultItem } from '../services/backendApi'; import { createKnowledgeBase, deleteKnowledgeBase, deleteKnowledgeDocument, fetchKnowledgeBaseById, fetchKnowledgeBases, fetchLLMModels, indexKnowledgeDocument, searchKnowledgeBase, updateKnowledgeBase, uploadKnowledgeDocument, type KnowledgeSearchResultItem } from '../services/backendApi';
const EMBEDDING_OPTIONS = [ const EMBEDDING_OPTIONS = [
'text-embedding-3-small', 'text-embedding-3-small',
@@ -189,6 +189,15 @@ export const KnowledgeBasePage: React.FC = () => {
onImport={() => setIsUploadOpen(true)} onImport={() => setIsUploadOpen(true)}
onEdit={() => openEditKb(selectedKb)} onEdit={() => openEditKb(selectedKb)}
onDelete={() => handleDeleteKb(selectedKb)} onDelete={() => handleDeleteKb(selectedKb)}
onIndexDocument={async (docId, content) => {
try {
await indexKnowledgeDocument(selectedKb.id, docId, content);
await refreshKnowledgeBases();
} catch (error: any) {
console.error(error);
throw new Error(error?.message || '索引失败。');
}
}}
onDeleteDocument={async (docId) => { onDeleteDocument={async (docId) => {
try { try {
await deleteKnowledgeDocument(selectedKb.id, docId); await deleteKnowledgeDocument(selectedKb.id, docId);
@@ -452,9 +461,13 @@ const KnowledgeBaseDetail: React.FC<{
onImport: () => void; onImport: () => void;
onEdit: () => void; onEdit: () => void;
onDelete: () => void; onDelete: () => void;
onIndexDocument: (docId: string, content: string) => Promise<void>;
onDeleteDocument: (docId: string) => void; onDeleteDocument: (docId: string) => void;
}> = ({ kb, onBack, onImport, onEdit, onDelete, onDeleteDocument }) => { }> = ({ kb, onBack, onImport, onEdit, onDelete, onIndexDocument, onDeleteDocument }) => {
const [docSearch, setDocSearch] = useState(''); const [docSearch, setDocSearch] = useState('');
const [indexingDoc, setIndexingDoc] = useState<KnowledgeDocument | null>(null);
const [indexContent, setIndexContent] = useState('');
const [indexing, setIndexing] = useState(false);
const [debugQuery, setDebugQuery] = useState(''); const [debugQuery, setDebugQuery] = useState('');
const [debugTopK, setDebugTopK] = useState(5); const [debugTopK, setDebugTopK] = useState(5);
const [debugLoading, setDebugLoading] = useState(false); const [debugLoading, setDebugLoading] = useState(false);
@@ -489,6 +502,26 @@ const KnowledgeBaseDetail: React.FC<{
return `${(score * 100).toFixed(2)}%`; return `${(score * 100).toFixed(2)}%`;
}; };
const handleStartIndex = async () => {
if (!indexingDoc) return;
const content = indexContent.trim();
if (!content) {
alert('请输入文档内容后再开始索引');
return;
}
try {
setIndexing(true);
await onIndexDocument(indexingDoc.id, content);
setIndexingDoc(null);
setIndexContent('');
} catch (error: any) {
alert(error?.message || '索引失败');
} finally {
setIndexing(false);
}
};
return ( return (
<div className="space-y-6 animate-in slide-in-from-right-4"> <div className="space-y-6 animate-in slide-in-from-right-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -546,6 +579,17 @@ const KnowledgeBaseDetail: React.FC<{
<TableCell className="text-muted-foreground">{doc.chunkCount ?? 0}</TableCell> <TableCell className="text-muted-foreground">{doc.chunkCount ?? 0}</TableCell>
<TableCell className="text-muted-foreground">{doc.uploadDate}</TableCell> <TableCell className="text-muted-foreground">{doc.uploadDate}</TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<Button
variant="ghost"
size="sm"
className="text-primary hover:text-primary/80 mr-1"
onClick={() => {
setIndexingDoc(doc);
setIndexContent('');
}}
>
</Button>
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@@ -629,6 +673,45 @@ const KnowledgeBaseDetail: React.FC<{
)} )}
</div> </div>
</Card> </Card>
<Dialog
isOpen={!!indexingDoc}
onClose={() => {
if (indexing) return;
setIndexingDoc(null);
setIndexContent('');
}}
title={`开始索引: ${indexingDoc?.name || ''}`}
footer={
<>
<Button
variant="ghost"
onClick={() => {
if (indexing) return;
setIndexingDoc(null);
setIndexContent('');
}}
>
</Button>
<Button onClick={handleStartIndex} disabled={indexing}>
{indexing ? '索引中...' : '开始索引'}
</Button>
</>
}
>
<div className="space-y-3">
<p className="text-xs text-muted-foreground">
</p>
<textarea
value={indexContent}
onChange={(e) => setIndexContent(e.target.value)}
className="flex min-h-[220px] w-full rounded-md border border-white/10 bg-white/5 px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 text-white"
placeholder="粘贴文档内容用于索引..."
/>
</div>
</Dialog>
</div> </div>
); );
}; };

View File

@@ -623,6 +623,23 @@ export const deleteKnowledgeDocument = async (kbId: string, docId: string): Prom
await apiRequest(`/knowledge/bases/${kbId}/documents/${docId}`, { method: 'DELETE' }); await apiRequest(`/knowledge/bases/${kbId}/documents/${docId}`, { method: 'DELETE' });
}; };
export const indexKnowledgeDocument = async (
kbId: string,
docId: string,
content: string,
): Promise<{ message: string; chunkCount: number }> => {
return apiRequest<{ message: string; chunkCount: number }>(
`/knowledge/bases/${kbId}/documents/${docId}/index`,
{
method: 'POST',
body: {
document_id: docId,
content,
},
}
);
};
export type KnowledgeSearchResultItem = { export type KnowledgeSearchResultItem = {
content: string; content: string;
metadata?: { metadata?: {