feat: add knowledge base retrieve test tab with Card-based UI (#1583)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Junyan Qin <Chin>, u79E6u9A8Fu8A00 in Chinese, you can call me my english name Rock Chin. <rockchinq@gmail.com>
This commit is contained in:
devin-ai-integration[bot]
2025-07-20 17:56:46 +08:00
committed by GitHub
parent ea51cec57e
commit dd1ec15a39
6 changed files with 159 additions and 1 deletions

View File

@@ -24,6 +24,7 @@ import { httpClient } from '@/app/infra/http/HttpClient';
// import { KnowledgeBase } from '@/app/infra/entities/api';
import KBForm from '@/app/home/knowledge/components/kb-form/KBForm';
import KBDoc from '@/app/home/knowledge/components/kb-docs/KBDoc';
import KBRetrieve from '@/app/home/knowledge/components/kb-retrieve/KBRetrieve';
interface KBDetailDialogProps {
open: boolean;
@@ -81,6 +82,19 @@ export default function KBDetailDialog({
</svg>
),
},
{
key: 'retrieve',
label: t('knowledge.retrieve'),
icon: (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z"></path>
</svg>
),
},
];
const confirmDelete = () => {
@@ -168,7 +182,9 @@ export default function KBDetailDialog({
<DialogTitle>
{activeMenu === 'metadata'
? t('knowledge.editKnowledgeBase')
: t('knowledge.editDocument')}
: activeMenu === 'documents'
? t('knowledge.editDocument')
: t('knowledge.retrieveTest')}
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto px-6 pb-6">
@@ -180,6 +196,7 @@ export default function KBDetailDialog({
/>
)}
{activeMenu === 'documents' && <KBDoc kbId={kbId} />}
{activeMenu === 'retrieve' && <KBRetrieve kbId={kbId} />}
</div>
{activeMenu === 'metadata' && (
<DialogFooter className="px-6 py-4 border-t shrink-0">

View File

@@ -0,0 +1,98 @@
'use client';
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { useTranslation } from 'react-i18next';
import { httpClient } from '@/app/infra/http/HttpClient';
import { RetrieveResult, KnowledgeBaseFile } from '@/app/infra/entities/api';
import { toast } from 'sonner';
interface KBRetrieveProps {
kbId: string;
}
export default function KBRetrieve({ kbId }: KBRetrieveProps) {
const { t } = useTranslation();
const [query, setQuery] = useState('');
const [results, setResults] = useState<RetrieveResult[]>([]);
const [files, setFiles] = useState<KnowledgeBaseFile[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const loadFiles = async () => {
try {
const response = await httpClient.getKnowledgeBaseFiles(kbId);
setFiles(response.files);
} catch (error) {
console.error('Failed to load files:', error);
}
};
loadFiles();
}, [kbId]);
const handleRetrieve = async () => {
if (!query.trim()) return;
setLoading(true);
try {
const response = await httpClient.retrieveKnowledgeBase(kbId, query);
setResults(response.results);
} catch (error) {
console.error('Retrieve failed:', error);
toast.error(t('knowledge.retrieveError'));
} finally {
setLoading(false);
}
};
const getFileName = (fileId: string) => {
const file = files.find((f) => f.uuid === fileId);
return file?.file_name || fileId;
};
return (
<div className="space-y-4">
<div className="flex gap-2">
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={t('knowledge.queryPlaceholder')}
onKeyPress={(e) => e.key === 'Enter' && handleRetrieve()}
/>
<Button onClick={handleRetrieve} disabled={loading || !query.trim()}>
{loading ? t('common.loading') : t('knowledge.query')}
</Button>
</div>
<div className="space-y-3">
<h3 className="text-lg font-semibold">
{t('knowledge.retrieveResults')} ({results.length})
</h3>
{results.length === 0 && !loading && (
<p className="text-muted-foreground">{t('knowledge.noResults')}</p>
)}
{results.map((result) => (
<Card key={result.id} className="w-full">
<CardHeader className="pb-3">
<CardTitle className="text-sm font-medium flex justify-between items-center">
<span>{getFileName(result.metadata.file_id)}</span>
<span className="text-xs text-muted-foreground">
{t('knowledge.distance')}: {result.distance.toFixed(4)}
</span>
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm whitespace-pre-wrap">
{result.metadata.text}
</p>
</CardContent>
</Card>
))}
</div>
</div>
);
}

View File

@@ -296,3 +296,18 @@ export interface ApiRespWebChatMessage {
export interface ApiRespWebChatMessages {
messages: Message[];
}
export interface RetrieveResult {
id: string;
metadata: {
file_id: string;
text: string;
uuid: string;
[key: string]: unknown;
};
distance: number;
}
export interface ApiRespKnowledgeBaseRetrieve {
results: RetrieveResult[];
}

View File

@@ -38,6 +38,7 @@ import {
ApiRespKnowledgeBase,
KnowledgeBase,
ApiRespKnowledgeBaseFiles,
ApiRespKnowledgeBaseRetrieve,
} from '@/app/infra/entities/api';
import { GetBotLogsRequest } from '@/app/infra/http/requestParam/bots/GetBotLogsRequest';
import { GetBotLogsResponse } from '@/app/infra/http/requestParam/bots/GetBotLogsResponse';
@@ -499,6 +500,13 @@ class HttpClient {
return this.delete(`/api/v1/knowledge/bases/${uuid}`);
}
public retrieveKnowledgeBase(
uuid: string,
query: string,
): Promise<ApiRespKnowledgeBaseRetrieve> {
return this.post(`/api/v1/knowledge/bases/${uuid}/retrieve`, { query });
}
// ============ Plugins API ============
public getPlugins(): Promise<ApiRespPlugins> {
return this.get('/api/v1/plugins');

View File

@@ -282,6 +282,16 @@ const enUS = {
},
deleteKnowledgeBaseConfirmation:
'Are you sure you want to delete this knowledge base? All documents in this knowledge base will be deleted.',
retrieve: 'Retrieve Test',
retrieveTest: 'Retrieve Test',
query: 'Query',
queryPlaceholder: 'Enter query text...',
retrieveResults: 'Retrieve Results',
distance: 'Distance',
content: 'Content',
fileName: 'File Name',
noResults: 'No results',
retrieveError: 'Retrieve failed',
},
register: {
title: 'Initialize LangBot 👋',

View File

@@ -274,6 +274,16 @@ const zhHans = {
},
deleteKnowledgeBaseConfirmation:
'你确定要删除这个知识库吗?此知识库下的所有文档将被删除。',
retrieve: '检索测试',
retrieveTest: '检索测试',
query: '查询',
queryPlaceholder: '输入查询内容...',
retrieveResults: '检索结果',
distance: '距离',
content: '内容',
fileName: '文件名',
noResults: '暂无结果',
retrieveError: '检索失败',
},
register: {
title: '初始化 LangBot 👋',