166 lines
4.2 KiB
Vue
166 lines
4.2 KiB
Vue
<template>
|
|
<div class="search-test">
|
|
<a-input-search
|
|
v-model:value="searchQuery"
|
|
placeholder="输入查询内容测试检索效果"
|
|
enter-button="检索"
|
|
size="large"
|
|
:loading="kbStore.isSearching"
|
|
@search="handleSearch"
|
|
/>
|
|
|
|
<a-collapse v-model:activeKey="activeCollapse" class="advanced-options" ghost>
|
|
<a-collapse-panel key="advanced" header="高级选项">
|
|
<a-form layout="inline" class="advanced-form">
|
|
<a-form-item label="信息源">
|
|
<a-select
|
|
v-model:value="selectedSourceIds"
|
|
mode="multiple"
|
|
placeholder="选择信息源(默认全部)"
|
|
style="min-width: 240px"
|
|
:options="sourceOptions"
|
|
allow-clear
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="返回数量">
|
|
<a-input-number
|
|
v-model:value="topK"
|
|
:min="1"
|
|
:max="20"
|
|
style="width: 80px"
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item label="检索策略">
|
|
<a-radio-group v-model:value="strategy">
|
|
<a-radio-button value="vector">向量</a-radio-button>
|
|
<a-radio-button value="hybrid">混合</a-radio-button>
|
|
</a-radio-group>
|
|
</a-form-item>
|
|
</a-form>
|
|
</a-collapse-panel>
|
|
</a-collapse>
|
|
|
|
<div v-if="kbStore.searchResults.length > 0" class="search-results">
|
|
<a-divider>检索结果 ({{ kbStore.searchResults.length }})</a-divider>
|
|
<div
|
|
v-for="(result, index) in kbStore.searchResults"
|
|
:key="index"
|
|
class="search-result-item"
|
|
>
|
|
<div class="search-result-item__header">
|
|
<span class="search-result-item__index">#{{ index + 1 }}</span>
|
|
<a-tag color="blue">{{ result.source }}</a-tag>
|
|
<span class="search-result-item__score">
|
|
相关度: {{ (result.score * 100).toFixed(1) }}%
|
|
</span>
|
|
</div>
|
|
<div class="search-result-item__content">
|
|
{{ result.content }}
|
|
</div>
|
|
<div v-if="Object.keys(result.metadata).length > 0" class="search-result-item__meta">
|
|
<a-tag v-for="(value, key) in result.metadata" :key="String(key)" size="small">
|
|
{{ key }}: {{ value }}
|
|
</a-tag>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<a-empty
|
|
v-else-if="hasSearched && !kbStore.isSearching"
|
|
description="未找到相关结果"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useKnowledgeStore } from '@/stores/knowledge'
|
|
|
|
const kbStore = useKnowledgeStore()
|
|
const searchQuery = ref('')
|
|
const hasSearched = ref(false)
|
|
const activeCollapse = ref<string[]>([])
|
|
|
|
const selectedSourceIds = ref<string[]>([])
|
|
const topK = ref(5)
|
|
const strategy = ref('vector')
|
|
|
|
const sourceOptions = computed(() =>
|
|
kbStore.sources.map((s) => ({ label: s.name, value: s.id }))
|
|
)
|
|
|
|
async function handleSearch(): Promise<void> {
|
|
if (!searchQuery.value.trim()) return
|
|
hasSearched.value = true
|
|
const sourceIds = selectedSourceIds.value.length > 0 ? selectedSourceIds.value : undefined
|
|
await kbStore.search(searchQuery.value, sourceIds, topK.value, strategy.value)
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (kbStore.sources.length === 0) {
|
|
kbStore.fetchSources()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.search-test {
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.advanced-options {
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.advanced-form {
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
|
|
.search-results {
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.search-result-item {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-color-split);
|
|
border-radius: 6px;
|
|
padding: 12px 16px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.search-result-item__header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.search-result-item__index {
|
|
font-weight: 600;
|
|
color: var(--color-primary);
|
|
}
|
|
|
|
.search-result-item__score {
|
|
margin-left: auto;
|
|
font-size: 12px;
|
|
color: var(--text-placeholder);
|
|
}
|
|
|
|
.search-result-item__content {
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
color: var(--text-primary);
|
|
white-space: pre-wrap;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.search-result-item__meta {
|
|
margin-top: 8px;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px;
|
|
}
|
|
</style>
|