|
@@ -0,0 +1,163 @@
|
|
|
+<template>
|
|
|
+ <div class="flex gap-20px w-full">
|
|
|
+ <!-- 左侧输入区域 -->
|
|
|
+ <ContentWrap class="flex-1 min-w-300px">
|
|
|
+ <div class="mb-15px">
|
|
|
+ <h3 class="m-0 mb-5px">召回测试</h3>
|
|
|
+ <div class="text-gray-500 text-14px">根据给定的查询文本测试召回效果。</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="relative mb-10px">
|
|
|
+ <el-input
|
|
|
+ v-model="queryParams.content"
|
|
|
+ type="textarea"
|
|
|
+ :rows="8"
|
|
|
+ placeholder="请输入文本"
|
|
|
+ />
|
|
|
+ <div class="absolute bottom-10px right-10px text-gray-400 text-12px">
|
|
|
+ {{ queryParams.content?.length }} / 200
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center mb-10px">
|
|
|
+ <span class="w-60px text-gray-500">topK:</span>
|
|
|
+ <el-input-number v-model="queryParams.topK" :min="1" :max="20" />
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center mb-15px">
|
|
|
+ <span class="w-60px text-gray-500">相似度:</span>
|
|
|
+ <el-input-number
|
|
|
+ v-model="queryParams.similarityThreshold"
|
|
|
+ :min="0"
|
|
|
+ :max="1"
|
|
|
+ :precision="2"
|
|
|
+ :step="0.01"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-end">
|
|
|
+ <el-button type="primary" @click="getRetrievalResult" :loading="loading">测试</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </ContentWrap>
|
|
|
+
|
|
|
+ <!-- 右侧召回结果区域 -->
|
|
|
+ <ContentWrap class="flex-1 min-w-300px">
|
|
|
+ <el-empty v-if="loading" description="正在检索中..." />
|
|
|
+ <div v-else-if="segments.length > 0" class="font-bold mb-15px">
|
|
|
+ {{ segments.length }} 个召回段落
|
|
|
+ </div>
|
|
|
+ <el-empty v-else description="暂无召回结果" />
|
|
|
+ <div>
|
|
|
+ <div
|
|
|
+ v-for="(segment, index) in segments"
|
|
|
+ :key="index"
|
|
|
+ class="mb-20px border border-solid border-gray-200 rounded p-15px"
|
|
|
+ >
|
|
|
+ <div class="flex justify-between text-12px text-gray-500 mb-5px">
|
|
|
+ <span>
|
|
|
+ 分段({{ segment.id }}) · {{ segment.contentLength }} 字符数 ·
|
|
|
+ {{ segment.tokens }} Token
|
|
|
+ </span>
|
|
|
+ <span class="px-8px py-4px bg-blue-50 text-blue-500 rounded-full text-12px font-bold">
|
|
|
+ score: {{ segment.score }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="bg-gray-50 p-10px rounded mb-10px whitespace-pre-wrap overflow-hidden transition-all duration-100 text-13px"
|
|
|
+ :class="{
|
|
|
+ 'line-clamp-2 max-h-50px': !segment.expanded,
|
|
|
+ 'max-h-500px': segment.expanded
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ {{ segment.content }}
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="flex items-center text-gray-500 text-13px">
|
|
|
+ <Icon icon="ep:document" class="mr-5px" />
|
|
|
+ <span>{{ segment.documentName || '未知文档' }}</span>
|
|
|
+ </div>
|
|
|
+ <el-button size="small" @click="toggleExpand(segment)">
|
|
|
+ {{ segment.expanded ? '收起' : '展开' }}
|
|
|
+ <Icon :icon="segment.expanded ? 'ep:arrow-up' : 'ep:arrow-down'" />
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </ContentWrap>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { useMessage } from '@/hooks/web/useMessage'
|
|
|
+import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
|
|
|
+import { KnowledgeApi } from '@/api/ai/knowledge/knowledge'
|
|
|
+/** 文档召回测试 */
|
|
|
+defineOptions({ name: 'KnowledgeDocumentRetrieval' })
|
|
|
+
|
|
|
+const message = useMessage() // 消息弹窗
|
|
|
+const route = useRoute() // 路由
|
|
|
+const router = useRouter() // 路由
|
|
|
+
|
|
|
+const loading = ref(false) // 加载状态
|
|
|
+const segments = ref<any[]>([]) // 召回结果
|
|
|
+const queryParams = reactive({
|
|
|
+ id: undefined,
|
|
|
+ content: '',
|
|
|
+ topK: 10,
|
|
|
+ similarityThreshold: 0.5
|
|
|
+})
|
|
|
+
|
|
|
+/** 调用文档召回测试接口 */
|
|
|
+const getRetrievalResult = async () => {
|
|
|
+ if (!queryParams.content) {
|
|
|
+ message.warning('请输入查询文本')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ loading.value = true
|
|
|
+ segments.value = []
|
|
|
+
|
|
|
+ try {
|
|
|
+ const data = await KnowledgeSegmentApi.searchKnowledgeSegment({
|
|
|
+ knowledgeId: queryParams.id,
|
|
|
+ content: queryParams.content,
|
|
|
+ topK: queryParams.topK,
|
|
|
+ similarityThreshold: queryParams.similarityThreshold
|
|
|
+ })
|
|
|
+ segments.value = data || []
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error)
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 展开/收起段落内容 */
|
|
|
+const toggleExpand = (segment: any) => {
|
|
|
+ segment.expanded = !segment.expanded
|
|
|
+}
|
|
|
+
|
|
|
+/** 获取知识库信息 */
|
|
|
+const getKnowledgeInfo = async (id: number) => {
|
|
|
+ try {
|
|
|
+ const knowledge = await KnowledgeApi.getKnowledge(id)
|
|
|
+ if (knowledge) {
|
|
|
+ queryParams.topK = knowledge.topK || queryParams.topK
|
|
|
+ queryParams.similarityThreshold =
|
|
|
+ knowledge.similarityThreshold || queryParams.similarityThreshold
|
|
|
+ }
|
|
|
+ } catch (error) {}
|
|
|
+}
|
|
|
+
|
|
|
+/** 初始化 **/
|
|
|
+onMounted(() => {
|
|
|
+ // 如果知识库 ID 不存在,显示错误提示并关闭页面
|
|
|
+ if (!route.query.id) {
|
|
|
+ message.error('知识库 ID 不存在,无法进行召回测试')
|
|
|
+ router.back()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ queryParams.id = route.query.id as any
|
|
|
+
|
|
|
+ // 获取知识库信息并设置默认值
|
|
|
+ getKnowledgeInfo(queryParams.id as any)
|
|
|
+})
|
|
|
+</script>
|