本地 RAG 知识库
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

/**
* 🔍 知识库搜索测试(支持多种检索模式)
*/
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 }
}
}