mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-19 19:06:07 +00:00
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:
committed by
GitHub
parent
ea51cec57e
commit
dd1ec15a39
@@ -24,6 +24,7 @@ import { httpClient } from '@/app/infra/http/HttpClient';
|
|||||||
// import { KnowledgeBase } from '@/app/infra/entities/api';
|
// import { KnowledgeBase } from '@/app/infra/entities/api';
|
||||||
import KBForm from '@/app/home/knowledge/components/kb-form/KBForm';
|
import KBForm from '@/app/home/knowledge/components/kb-form/KBForm';
|
||||||
import KBDoc from '@/app/home/knowledge/components/kb-docs/KBDoc';
|
import KBDoc from '@/app/home/knowledge/components/kb-docs/KBDoc';
|
||||||
|
import KBRetrieve from '@/app/home/knowledge/components/kb-retrieve/KBRetrieve';
|
||||||
|
|
||||||
interface KBDetailDialogProps {
|
interface KBDetailDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -81,6 +82,19 @@ export default function KBDetailDialog({
|
|||||||
</svg>
|
</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 = () => {
|
const confirmDelete = () => {
|
||||||
@@ -168,7 +182,9 @@ export default function KBDetailDialog({
|
|||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{activeMenu === 'metadata'
|
{activeMenu === 'metadata'
|
||||||
? t('knowledge.editKnowledgeBase')
|
? t('knowledge.editKnowledgeBase')
|
||||||
: t('knowledge.editDocument')}
|
: activeMenu === 'documents'
|
||||||
|
? t('knowledge.editDocument')
|
||||||
|
: t('knowledge.retrieveTest')}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex-1 overflow-y-auto px-6 pb-6">
|
<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 === 'documents' && <KBDoc kbId={kbId} />}
|
||||||
|
{activeMenu === 'retrieve' && <KBRetrieve kbId={kbId} />}
|
||||||
</div>
|
</div>
|
||||||
{activeMenu === 'metadata' && (
|
{activeMenu === 'metadata' && (
|
||||||
<DialogFooter className="px-6 py-4 border-t shrink-0">
|
<DialogFooter className="px-6 py-4 border-t shrink-0">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -296,3 +296,18 @@ export interface ApiRespWebChatMessage {
|
|||||||
export interface ApiRespWebChatMessages {
|
export interface ApiRespWebChatMessages {
|
||||||
messages: Message[];
|
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[];
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
ApiRespKnowledgeBase,
|
ApiRespKnowledgeBase,
|
||||||
KnowledgeBase,
|
KnowledgeBase,
|
||||||
ApiRespKnowledgeBaseFiles,
|
ApiRespKnowledgeBaseFiles,
|
||||||
|
ApiRespKnowledgeBaseRetrieve,
|
||||||
} from '@/app/infra/entities/api';
|
} from '@/app/infra/entities/api';
|
||||||
import { GetBotLogsRequest } from '@/app/infra/http/requestParam/bots/GetBotLogsRequest';
|
import { GetBotLogsRequest } from '@/app/infra/http/requestParam/bots/GetBotLogsRequest';
|
||||||
import { GetBotLogsResponse } from '@/app/infra/http/requestParam/bots/GetBotLogsResponse';
|
import { GetBotLogsResponse } from '@/app/infra/http/requestParam/bots/GetBotLogsResponse';
|
||||||
@@ -499,6 +500,13 @@ class HttpClient {
|
|||||||
return this.delete(`/api/v1/knowledge/bases/${uuid}`);
|
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 ============
|
// ============ Plugins API ============
|
||||||
public getPlugins(): Promise<ApiRespPlugins> {
|
public getPlugins(): Promise<ApiRespPlugins> {
|
||||||
return this.get('/api/v1/plugins');
|
return this.get('/api/v1/plugins');
|
||||||
|
|||||||
@@ -282,6 +282,16 @@ const enUS = {
|
|||||||
},
|
},
|
||||||
deleteKnowledgeBaseConfirmation:
|
deleteKnowledgeBaseConfirmation:
|
||||||
'Are you sure you want to delete this knowledge base? All documents in this knowledge base will be deleted.',
|
'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: {
|
register: {
|
||||||
title: 'Initialize LangBot 👋',
|
title: 'Initialize LangBot 👋',
|
||||||
|
|||||||
@@ -274,6 +274,16 @@ const zhHans = {
|
|||||||
},
|
},
|
||||||
deleteKnowledgeBaseConfirmation:
|
deleteKnowledgeBaseConfirmation:
|
||||||
'你确定要删除这个知识库吗?此知识库下的所有文档将被删除。',
|
'你确定要删除这个知识库吗?此知识库下的所有文档将被删除。',
|
||||||
|
retrieve: '检索测试',
|
||||||
|
retrieveTest: '检索测试',
|
||||||
|
query: '查询',
|
||||||
|
queryPlaceholder: '输入查询内容...',
|
||||||
|
retrieveResults: '检索结果',
|
||||||
|
distance: '距离',
|
||||||
|
content: '内容',
|
||||||
|
fileName: '文件名',
|
||||||
|
noResults: '暂无结果',
|
||||||
|
retrieveError: '检索失败',
|
||||||
},
|
},
|
||||||
register: {
|
register: {
|
||||||
title: '初始化 LangBot 👋',
|
title: '初始化 LangBot 👋',
|
||||||
|
|||||||
Reference in New Issue
Block a user