dongyukun
2025-05-29 be664d7c011a473002c1b413bac8303f7905d160
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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>