Add index button
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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?: {
|
||||||
|
|||||||
Reference in New Issue
Block a user