fischer-agentkit/src/agentkit/server/frontend/src/components/kb/SearchTest.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>