diff --git a/web/src/app/home/knowledge/KBDetailDialog.tsx b/web/src/app/home/knowledge/KBDetailDialog.tsx index 262d872f..7ad8d4a4 100644 --- a/web/src/app/home/knowledge/KBDetailDialog.tsx +++ b/web/src/app/home/knowledge/KBDetailDialog.tsx @@ -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({ ), }, + { + key: 'retrieve', + label: t('knowledge.retrieve'), + icon: ( + + + + ), + }, ]; const confirmDelete = () => { @@ -168,7 +182,9 @@ export default function KBDetailDialog({ {activeMenu === 'metadata' ? t('knowledge.editKnowledgeBase') - : t('knowledge.editDocument')} + : activeMenu === 'documents' + ? t('knowledge.editDocument') + : t('knowledge.retrieveTest')}
@@ -180,6 +196,7 @@ export default function KBDetailDialog({ /> )} {activeMenu === 'documents' && } + {activeMenu === 'retrieve' && }
{activeMenu === 'metadata' && ( diff --git a/web/src/app/home/knowledge/components/kb-retrieve/KBRetrieve.tsx b/web/src/app/home/knowledge/components/kb-retrieve/KBRetrieve.tsx new file mode 100644 index 00000000..46c38f5f --- /dev/null +++ b/web/src/app/home/knowledge/components/kb-retrieve/KBRetrieve.tsx @@ -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([]); + const [files, setFiles] = useState([]); + 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 ( +
+
+ setQuery(e.target.value)} + placeholder={t('knowledge.queryPlaceholder')} + onKeyPress={(e) => e.key === 'Enter' && handleRetrieve()} + /> + +
+ +
+

+ {t('knowledge.retrieveResults')} ({results.length}) +

+ + {results.length === 0 && !loading && ( +

{t('knowledge.noResults')}

+ )} + + {results.map((result) => ( + + + + {getFileName(result.metadata.file_id)} + + {t('knowledge.distance')}: {result.distance.toFixed(4)} + + + + +

+ {result.metadata.text} +

+
+
+ ))} +
+
+ ); +} diff --git a/web/src/app/infra/entities/api/index.ts b/web/src/app/infra/entities/api/index.ts index f16333b1..76d0a1f6 100644 --- a/web/src/app/infra/entities/api/index.ts +++ b/web/src/app/infra/entities/api/index.ts @@ -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[]; +} diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts index 2e91683a..9a49c1e3 100644 --- a/web/src/app/infra/http/HttpClient.ts +++ b/web/src/app/infra/http/HttpClient.ts @@ -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 { + return this.post(`/api/v1/knowledge/bases/${uuid}/retrieve`, { query }); + } + // ============ Plugins API ============ public getPlugins(): Promise { return this.get('/api/v1/plugins'); diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 3e9089fc..6cd110f1 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -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 👋', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 49c09015..e839e046 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -274,6 +274,16 @@ const zhHans = { }, deleteKnowledgeBaseConfirmation: '你确定要删除这个知识库吗?此知识库下的所有文档将被删除。', + retrieve: '检索测试', + retrieveTest: '检索测试', + query: '查询', + queryPlaceholder: '输入查询内容...', + retrieveResults: '检索结果', + distance: '距离', + content: '内容', + fileName: '文件名', + noResults: '暂无结果', + retrieveError: '检索失败', }, register: { title: '初始化 LangBot 👋',