You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
4.0 KiB
105 lines
4.0 KiB
/**
|
|
* 🔍 知识库搜索测试(支持多种检索模式)
|
|
*/
|
|
import { ref } from 'vue'
|
|
import { searchDocuments } from '../js/api.js'
|
|
import { toast } from '../js/utils.js'
|
|
|
|
export default {
|
|
template: `
|
|
<div class="card">
|
|
<h2>知识库搜索测试</h2>
|
|
<p style="color:var(--sub);font-size:13px;margin-bottom:12px;">输入查询语句,测试知识库检索效果(支持向量、关键词、混合检索三种模式)</p>
|
|
<div class="input-row">
|
|
<input class="input" v-model="query" placeholder="输入查询,如:Spring Boot 是什么?" @keydown.enter="search">
|
|
<label class="input-field-sm">
|
|
<span>检索模式</span>
|
|
<select class="input input-sm" v-model="searchMode">
|
|
<option value="VECTOR">向量检索</option>
|
|
<option value="KEYWORD">关键词检索</option>
|
|
<option value="HYBRID">混合检索</option>
|
|
</select>
|
|
</label>
|
|
<label class="input-field-sm">
|
|
<span>返回条数</span>
|
|
<input class="input input-sm" v-model.number="topK" type="number" min="1" max="20">
|
|
</label>
|
|
<label v-if="searchMode !== 'KEYWORD'" class="input-field-sm">
|
|
<span>相似度阈值</span>
|
|
<input class="input input-sm" v-model.number="threshold" type="number" min="0" max="1" step="0.1">
|
|
</label>
|
|
<button class="btn btn-primary" @click="search" :disabled="isSearching">{{ isSearching ? '搜索中...' : '搜索' }}</button>
|
|
</div>
|
|
|
|
<div v-if="isSearching" style="text-align:center;padding:20px;color:var(--sub);">搜索中...</div>
|
|
|
|
<div v-else-if="searched && results.length === 0" style="text-align:center;padding:20px;color:var(--sub);">未找到相关结果</div>
|
|
|
|
<div v-else-if="searched && errorMsg" style="color:var(--danger);">搜索失败:{{ errorMsg }}</div>
|
|
|
|
<div v-else>
|
|
<div v-for="(r, i) in results" :key="i" class="search-result">
|
|
<div class="score">
|
|
<span style="display:inline-block;padding:1px 6px;border-radius:3px;font-size:11px;margin-right:6px;"
|
|
:style="{background: modeColor(r.searchMode), color: '#fff'}">{{ modeLabel(r.searchMode) }}</span>
|
|
得分: {{ formatScore(r) }} | {{ r.title || '无标题' }} | {{ r.sourceName || '无来源' }}
|
|
</div>
|
|
<div class="content">{{ r.content || '' }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`,
|
|
setup() {
|
|
const query = ref('')
|
|
const topK = ref(5)
|
|
const threshold = ref(0.5)
|
|
const searchMode = ref('VECTOR')
|
|
const results = ref([])
|
|
const isSearching = ref(false)
|
|
const searched = ref(false)
|
|
const errorMsg = ref('')
|
|
|
|
async function search() {
|
|
if (!query.value.trim()) {
|
|
toast('请输入查询内容', 'error')
|
|
return
|
|
}
|
|
isSearching.value = true
|
|
searched.value = false
|
|
errorMsg.value = ''
|
|
try {
|
|
const json = await searchDocuments(query.value, topK.value, threshold.value, null, null, searchMode.value)
|
|
if (!json.success) {
|
|
errorMsg.value = json.message
|
|
results.value = []
|
|
} else {
|
|
results.value = json.data || []
|
|
}
|
|
searched.value = true
|
|
} catch (e) {
|
|
errorMsg.value = e.message
|
|
results.value = []
|
|
searched.value = true
|
|
} finally {
|
|
isSearching.value = false
|
|
}
|
|
}
|
|
|
|
function formatScore(r) {
|
|
if (r.searchMode === 'KEYWORD' || r.searchMode === 'HYBRID') {
|
|
return r.score != null ? r.score.toFixed(4) : '-'
|
|
}
|
|
return r.score != null ? (1 - r.score).toFixed(4) : '-'
|
|
}
|
|
|
|
function modeLabel(m) {
|
|
return { VECTOR: '向量', KEYWORD: '关键词', HYBRID: '混合' }[m] || m || '向量'
|
|
}
|
|
|
|
function modeColor(m) {
|
|
return { VECTOR: '#007bff', KEYWORD: '#28a745', HYBRID: '#6f42c1' }[m] || '#6c757d'
|
|
}
|
|
|
|
return { query, topK, threshold, searchMode, results, isSearching, searched, errorMsg, search, formatScore, modeLabel, modeColor }
|
|
}
|
|
}
|