refactor(eval): 重构检索配置为弹窗并优化评估界面
- 将检索配置从标签页改为弹窗形式 - 在查询区和评估区添加检索配置入口 - 优化评估历史记录显示和刷新功能 - 移除不再需要的组件和样式
This commit is contained in:
parent
e5a06405ba
commit
6d3bef49dc
@ -16,16 +16,13 @@
|
|||||||
- conversation 待修改为异步的版本
|
- conversation 待修改为异步的版本
|
||||||
- DBManager 需要将数据库修改为异步的aiosqlite或者异步mysql,缓存使用Redis存储
|
- DBManager 需要将数据库修改为异步的aiosqlite或者异步mysql,缓存使用Redis存储
|
||||||
- 【eval】缺少自动生成评估的功能
|
- 【eval】缺少自动生成评估的功能
|
||||||
|
- chat_model 的 call 需要异步
|
||||||
|
|
||||||
### Bugs
|
### Bugs
|
||||||
- 部分异常状态下,智能体的模型名称出现重叠[#279](https://github.com/xerrors/Yuxi-Know/issues/279)
|
- 部分异常状态下,智能体的模型名称出现重叠[#279](https://github.com/xerrors/Yuxi-Know/issues/279)
|
||||||
- DeepSeek 官方接口适配会出现问题
|
- DeepSeek 官方接口适配会出现问题
|
||||||
- 目前的知识库的图片存在公开访问风险
|
- 目前的知识库的图片存在公开访问风险
|
||||||
- 深度分析智能体需要考虑上下文超限的问题
|
- 深度分析智能体需要考虑上下文超限的问题
|
||||||
- 【eval】检索评估缺少检索参数的内容
|
|
||||||
- 【eval】总体评分替换为确定的某个指标,最好是在右侧可以选择可行的指标
|
|
||||||
- 【eval】检索配置应该修改为一个弹窗
|
|
||||||
- 【eval】当前的检索配置仅保存在本地,实际并没有保存在知识库的元数据中
|
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
- 优化知识库详情页面,更加简洁清晰
|
- 优化知识库详情页面,更加简洁清晰
|
||||||
|
|||||||
@ -1,15 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mindmap-section">
|
<div class="mindmap-section">
|
||||||
<div class="section-header">
|
|
||||||
<div class="header-left">
|
|
||||||
<BrainCircuit :size="16" />
|
|
||||||
<span>知识导图</span>
|
|
||||||
<a-tag v-if="!loading && mindmapData" color="blue" size="small">
|
|
||||||
已生成
|
|
||||||
</a-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
<!-- 加载状态 -->
|
<!-- 加载状态 -->
|
||||||
<div v-if="loading" class="loading-state">
|
<div v-if="loading" class="loading-state">
|
||||||
@ -65,7 +55,6 @@
|
|||||||
import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
import { ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import {
|
import {
|
||||||
BrainCircuit,
|
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Network,
|
Network,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
@ -315,21 +304,6 @@ onUnmounted(() => {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
|
||||||
padding: 10px 16px;
|
|
||||||
background: var(--gray-25);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
|
|
||||||
.header-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-content {
|
.section-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
@ -374,7 +348,7 @@ onUnmounted(() => {
|
|||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-end;
|
||||||
|
|
||||||
.toolbar-text {
|
.toolbar-text {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
@ -382,7 +356,6 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.ant-btn-text) {
|
:deep(.ant-btn-text) {
|
||||||
padding: 4px 12px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,17 @@
|
|||||||
/>
|
/>
|
||||||
<div class="search-actions">
|
<div class="search-actions">
|
||||||
<div class="query-examples-compact">
|
<div class="query-examples-compact">
|
||||||
|
<!-- 检索配置按钮 -->
|
||||||
|
<a-tooltip title="检索配置" placement="bottom">
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
class="config-label-btn"
|
||||||
|
@click="openSearchConfigModal"
|
||||||
|
>
|
||||||
|
<SettingOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
<div class="examples-label-group">
|
<div class="examples-label-group">
|
||||||
<a-tooltip title="点击手动生成测试问题" placement="bottom">
|
<a-tooltip title="点击手动生成测试问题" placement="bottom">
|
||||||
<a-button
|
<a-button
|
||||||
@ -138,6 +149,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 检索配置弹窗 -->
|
||||||
|
<SearchConfigModal
|
||||||
|
v-model="searchConfigModalVisible"
|
||||||
|
:database-id="store.database?.db_id"
|
||||||
|
@save="handleSearchConfigSave"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -148,7 +166,9 @@ import { queryApi } from '@/apis/knowledge_api';
|
|||||||
import {
|
import {
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
|
SettingOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
|
import SearchConfigModal from './SearchConfigModal.vue';
|
||||||
|
|
||||||
const store = useDatabaseStore();
|
const store = useDatabaseStore();
|
||||||
|
|
||||||
@ -175,10 +195,24 @@ const queryExamples = ref([]);
|
|||||||
const currentExampleIndex = ref(0);
|
const currentExampleIndex = ref(0);
|
||||||
const loadingQuestions = ref(false);
|
const loadingQuestions = ref(false);
|
||||||
const generatingQuestions = ref(false);
|
const generatingQuestions = ref(false);
|
||||||
|
const searchConfigModalVisible = ref(false);
|
||||||
|
|
||||||
// 示例轮播相关
|
// 示例轮播相关
|
||||||
let exampleCarouselInterval = null;
|
let exampleCarouselInterval = null;
|
||||||
|
|
||||||
|
// 方法定义
|
||||||
|
|
||||||
|
// 打开检索配置弹窗
|
||||||
|
const openSearchConfigModal = () => {
|
||||||
|
searchConfigModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理检索配置保存
|
||||||
|
const handleSearchConfigSave = (config) => {
|
||||||
|
console.log('查询测试中的检索配置已更新:', config);
|
||||||
|
// 可以在这里添加配置更新后的处理逻辑,比如重新查询
|
||||||
|
};
|
||||||
|
|
||||||
// 加载示例问题
|
// 加载示例问题
|
||||||
const loadSampleQuestions = async () => {
|
const loadSampleQuestions = async () => {
|
||||||
if (!store.database?.db_id) return;
|
if (!store.database?.db_id) return;
|
||||||
@ -595,12 +629,31 @@ defineExpose({
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.config-label-btn {
|
||||||
|
color: var(--gray-500);
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 4px;
|
||||||
|
margin-left: -4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--main-color);
|
||||||
|
background-color: var(--gray-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.examples-label-btn {
|
.examples-label-btn {
|
||||||
color: var(--gray-500);
|
color: var(--gray-500);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-left: -8px;
|
margin-left: -8px;
|
||||||
|
margin-right: -6px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--main-color);
|
color: var(--main-color);
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<a-select
|
<a-select
|
||||||
v-model:value="selectedBenchmarkId"
|
v-model:value="selectedBenchmarkId"
|
||||||
placeholder="请选择评估基准"
|
placeholder="请选择评估基准"
|
||||||
style="width: 300px"
|
style="width: 180px"
|
||||||
@change="onBenchmarkChanged"
|
@change="onBenchmarkChanged"
|
||||||
:loading="benchmarksLoading"
|
:loading="benchmarksLoading"
|
||||||
>
|
>
|
||||||
@ -22,8 +22,15 @@
|
|||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
|
<!-- 检索配置按钮 -->
|
||||||
|
<a-button
|
||||||
|
size="middle"
|
||||||
|
@click="openSearchConfigModal"
|
||||||
|
:icon="h(SettingOutlined)"
|
||||||
|
/>
|
||||||
<!-- 开始评估按钮 -->
|
<!-- 开始评估按钮 -->
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -87,7 +94,19 @@
|
|||||||
|
|
||||||
<!-- 历史评估记录 -->
|
<!-- 历史评估记录 -->
|
||||||
<div class="history-section">
|
<div class="history-section">
|
||||||
|
<div class="section-header">
|
||||||
<h4 class="section-title">历史评估记录</h4>
|
<h4 class="section-title">历史评估记录</h4>
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
:loading="refreshingHistory"
|
||||||
|
@click="refreshHistory"
|
||||||
|
:icon="h('ReloadOutlined')"
|
||||||
|
class="refresh-btn"
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
<a-table
|
<a-table
|
||||||
class="history-table"
|
class="history-table"
|
||||||
:columns="historyColumns"
|
:columns="historyColumns"
|
||||||
@ -242,9 +261,9 @@
|
|||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'retrieval_score'">
|
<template v-else-if="column.key === 'retrieval_score'">
|
||||||
<div v-if="record.metrics && Object.keys(record.metrics).some(k => k.startsWith('recall') || k.startsWith('f1') || k.startsWith('precision') || k === 'map' || k === 'ndcg')" class="retrieval-metrics">
|
<div v-if="record.metrics && Object.keys(record.metrics).some(k => k.startsWith('recall') || k.startsWith('precision') || k === 'map' || k === 'ndcg')" class="retrieval-metrics">
|
||||||
<div v-for="(val, key) in record.metrics" :key="key" class="metric-item">
|
<div v-for="(val, key) in record.metrics" :key="key" class="metric-item">
|
||||||
<span v-if="key.startsWith('recall') || key.startsWith('f1') || key.startsWith('precision') || key === 'map' || key === 'ndcg'"
|
<span v-if="key.startsWith('recall') || key.startsWith('precision') || key === 'map' || key === 'ndcg'"
|
||||||
class="metric-content"
|
class="metric-content"
|
||||||
:class="`metric-${getMetricType(key)}`">
|
:class="`metric-${getMetricType(key)}`">
|
||||||
<span class="metric-name">{{ getMetricShortName(key) }}</span>
|
<span class="metric-name">{{ getMetricShortName(key) }}</span>
|
||||||
@ -277,13 +296,22 @@
|
|||||||
</a-empty>
|
</a-empty>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 检索配置弹窗 -->
|
||||||
|
<SearchConfigModal
|
||||||
|
v-model="searchConfigModalVisible"
|
||||||
|
:database-id="databaseId"
|
||||||
|
@save="handleSearchConfigSave"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, computed } from 'vue';
|
import { ref, reactive, onMounted, computed, h } from 'vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { evaluationApi } from '@/apis/knowledge_api';
|
import { evaluationApi } from '@/apis/knowledge_api';
|
||||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||||
|
import SearchConfigModal from './SearchConfigModal.vue';
|
||||||
|
import { SettingOutlined, ReloadOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
databaseId: {
|
databaseId: {
|
||||||
@ -306,6 +334,9 @@ const selectedResult = ref(null);
|
|||||||
const detailedResults = ref([]);
|
const detailedResults = ref([]);
|
||||||
const evaluationStats = ref({});
|
const evaluationStats = ref({});
|
||||||
const resultsLoading = ref(false);
|
const resultsLoading = ref(false);
|
||||||
|
const searchConfigModalVisible = ref(false);
|
||||||
|
const refreshingHistory = ref(false);
|
||||||
|
|
||||||
|
|
||||||
// 评估配置表单(使用知识库默认配置)
|
// 评估配置表单(使用知识库默认配置)
|
||||||
const configForm = reactive({
|
const configForm = reactive({
|
||||||
@ -358,10 +389,36 @@ const historyColumns = [
|
|||||||
width: 100
|
width: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '总体评分',
|
title: 'Recall@10',
|
||||||
dataIndex: 'overall_score',
|
key: 'recall_10',
|
||||||
key: 'overall_score',
|
width: 100,
|
||||||
width: 100
|
customRender: ({ record }) => {
|
||||||
|
// 使用后端返回的 metrics.recall@10 数据
|
||||||
|
if (record.metrics && record.metrics['recall@10'] !== undefined && record.metrics['recall@10'] !== null) {
|
||||||
|
const recallValue = record.metrics['recall@10'];
|
||||||
|
const displayValue = formatMetricValue(recallValue);
|
||||||
|
return h('a-tag', {
|
||||||
|
color: getScoreTagColor(recallValue)
|
||||||
|
}, displayValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是运行中的任务,显示计算中
|
||||||
|
if (record.status === 'running') {
|
||||||
|
return h('a-tag', {
|
||||||
|
color: 'processing'
|
||||||
|
}, '计算中');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已完成但没有 recall@10 数据
|
||||||
|
if (record.status === 'completed') {
|
||||||
|
return h('a-tag', {
|
||||||
|
color: 'default'
|
||||||
|
}, '无数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他情况显示横线
|
||||||
|
return h('span', '-');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
@ -378,6 +435,17 @@ const currentBenchmark = computed(() => {
|
|||||||
return availableBenchmarks.value.find(b => b.benchmark_id === selectedBenchmarkId.value);
|
return availableBenchmarks.value.find(b => b.benchmark_id === selectedBenchmarkId.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 打开检索配置弹窗
|
||||||
|
const openSearchConfigModal = () => {
|
||||||
|
searchConfigModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理检索配置保存
|
||||||
|
const handleSearchConfigSave = (config) => {
|
||||||
|
console.log('RAG评估中的检索配置已更新:', config);
|
||||||
|
// 可以在这里添加配置更新后的处理逻辑
|
||||||
|
};
|
||||||
|
|
||||||
// 加载基准列表
|
// 加载基准列表
|
||||||
const loadBenchmarks = async () => {
|
const loadBenchmarks = async () => {
|
||||||
if (!props.databaseId) return;
|
if (!props.databaseId) return;
|
||||||
@ -389,12 +457,19 @@ const loadBenchmarks = async () => {
|
|||||||
if (response && response.message === 'success' && Array.isArray(response.data)) {
|
if (response && response.message === 'success' && Array.isArray(response.data)) {
|
||||||
availableBenchmarks.value = response.data;
|
availableBenchmarks.value = response.data;
|
||||||
|
|
||||||
|
// 如果没有选中的基准,且有可用基准,默认选中第一个
|
||||||
|
if (!selectedBenchmarkId.value && response.data.length > 0) {
|
||||||
|
selectedBenchmarkId.value = response.data[0].benchmark_id;
|
||||||
|
selectedBenchmark.value = response.data[0];
|
||||||
|
} else if (selectedBenchmarkId.value) {
|
||||||
// 如果之前有选中的基准,重新验证其有效性
|
// 如果之前有选中的基准,重新验证其有效性
|
||||||
if (selectedBenchmarkId.value) {
|
|
||||||
const exists = response.data.some(b => b.benchmark_id === selectedBenchmarkId.value);
|
const exists = response.data.some(b => b.benchmark_id === selectedBenchmarkId.value);
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
selectedBenchmarkId.value = null;
|
selectedBenchmarkId.value = null;
|
||||||
selectedBenchmark.value = null;
|
selectedBenchmark.value = null;
|
||||||
|
} else {
|
||||||
|
// 更新选中的基准对象
|
||||||
|
selectedBenchmark.value = response.data.find(b => b.benchmark_id === selectedBenchmarkId.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -415,6 +490,21 @@ const onBenchmarkChanged = (benchmarkId) => {
|
|||||||
selectedBenchmark.value = benchmark || null;
|
selectedBenchmark.value = benchmark || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 刷新历史评估记录
|
||||||
|
const refreshHistory = async () => {
|
||||||
|
refreshingHistory.value = true;
|
||||||
|
try {
|
||||||
|
await loadEvaluationHistory();
|
||||||
|
message.success('历史记录已刷新');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('刷新历史记录失败:', error);
|
||||||
|
message.error('刷新历史记录失败');
|
||||||
|
} finally {
|
||||||
|
refreshingHistory.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// 开始评估
|
// 开始评估
|
||||||
const startEvaluation = async () => {
|
const startEvaluation = async () => {
|
||||||
if (!selectedBenchmark.value) {
|
if (!selectedBenchmark.value) {
|
||||||
@ -464,6 +554,7 @@ const loadEvaluationHistory = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 计算评估统计信息
|
// 计算评估统计信息
|
||||||
const calculateEvaluationStats = (results) => {
|
const calculateEvaluationStats = (results) => {
|
||||||
if (!results || results.length === 0) {
|
if (!results || results.length === 0) {
|
||||||
@ -493,7 +584,7 @@ const calculateEvaluationStats = (results) => {
|
|||||||
// 检索指标统计
|
// 检索指标统计
|
||||||
if (item.metrics) {
|
if (item.metrics) {
|
||||||
Object.keys(item.metrics).forEach(key => {
|
Object.keys(item.metrics).forEach(key => {
|
||||||
if (key.startsWith('recall') || key.startsWith('f1') || key.startsWith('precision') || key === 'map' || key === 'ndcg') {
|
if (key.startsWith('recall') || key.startsWith('precision') || key === 'map' || key === 'ndcg') {
|
||||||
if (!metricSums[key]) {
|
if (!metricSums[key]) {
|
||||||
metricSums[key] = 0;
|
metricSums[key] = 0;
|
||||||
metricCounts[key] = 0;
|
metricCounts[key] = 0;
|
||||||
@ -643,7 +734,6 @@ const getMetricTitle = (key) => {
|
|||||||
const titles = {
|
const titles = {
|
||||||
precision: '精确率',
|
precision: '精确率',
|
||||||
recall: '召回率',
|
recall: '召回率',
|
||||||
f1: 'F1分数',
|
|
||||||
map: '平均精度',
|
map: '平均精度',
|
||||||
ndcg: 'NDCG',
|
ndcg: 'NDCG',
|
||||||
bleu: 'BLEU分数',
|
bleu: 'BLEU分数',
|
||||||
@ -653,9 +743,8 @@ const getMetricTitle = (key) => {
|
|||||||
reasoning: '理由',
|
reasoning: '理由',
|
||||||
overall_score: '综合评分'
|
overall_score: '综合评分'
|
||||||
};
|
};
|
||||||
// 处理 recall@k, f1@k
|
// 处理 recall@k
|
||||||
if (key.startsWith('recall@')) return `召回率 (${key.split('@')[1]})`;
|
if (key.startsWith('recall@')) return `召回率 (${key.split('@')[1]})`;
|
||||||
if (key.startsWith('f1@')) return `F1 (${key.split('@')[1]})`;
|
|
||||||
if (key.startsWith('precision@')) return `精确率 (${key.split('@')[1]})`;
|
if (key.startsWith('precision@')) return `精确率 (${key.split('@')[1]})`;
|
||||||
|
|
||||||
return titles[key] || key;
|
return titles[key] || key;
|
||||||
@ -665,7 +754,6 @@ const getMetricTitle = (key) => {
|
|||||||
const getMetricType = (key) => {
|
const getMetricType = (key) => {
|
||||||
if (key.startsWith('recall')) return 'recall';
|
if (key.startsWith('recall')) return 'recall';
|
||||||
if (key.startsWith('precision')) return 'precision';
|
if (key.startsWith('precision')) return 'precision';
|
||||||
if (key.startsWith('f1')) return 'f1';
|
|
||||||
if (key === 'map') return 'map';
|
if (key === 'map') return 'map';
|
||||||
if (key === 'ndcg') return 'ndcg';
|
if (key === 'ndcg') return 'ndcg';
|
||||||
return 'default';
|
return 'default';
|
||||||
@ -675,10 +763,8 @@ const getMetricType = (key) => {
|
|||||||
const getMetricShortName = (key) => {
|
const getMetricShortName = (key) => {
|
||||||
if (key.startsWith('recall@')) return `R@${key.split('@')[1]}`;
|
if (key.startsWith('recall@')) return `R@${key.split('@')[1]}`;
|
||||||
if (key.startsWith('precision@')) return `P@${key.split('@')[1]}`;
|
if (key.startsWith('precision@')) return `P@${key.split('@')[1]}`;
|
||||||
if (key.startsWith('f1@')) return `F1@${key.split('@')[1]}`;
|
|
||||||
if (key === 'precision') return 'Precision';
|
if (key === 'precision') return 'Precision';
|
||||||
if (key === 'recall') return 'Recall';
|
if (key === 'recall') return 'Recall';
|
||||||
if (key === 'f1') return 'F1';
|
|
||||||
if (key === 'map') return 'MAP';
|
if (key === 'map') return 'MAP';
|
||||||
if (key === 'ndcg') return 'NDCG';
|
if (key === 'ndcg') return 'NDCG';
|
||||||
return key;
|
return key;
|
||||||
@ -753,12 +839,9 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-right {
|
.toolbar-right {
|
||||||
.ant-btn {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1006,10 +1089,6 @@ onMounted(() => {
|
|||||||
color: var(--color-success-900);
|
color: var(--color-success-900);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.metric-f1 {
|
|
||||||
background-color: var(--color-warning-50);
|
|
||||||
color: var(--color-warning-900);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.metric-map,
|
&.metric-map,
|
||||||
&.metric-ndcg {
|
&.metric-ndcg {
|
||||||
@ -1128,12 +1207,43 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 历史评估记录区域
|
// 历史评估记录区域
|
||||||
.history-section {
|
.history-section {
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
margin: 0 0 12px 0;
|
margin: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--gray-700);
|
color: var(--gray-700);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
color: var(--gray-600);
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 4px 8px;
|
||||||
|
height: auto;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary-600);
|
||||||
|
background-color: var(--color-primary-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: var(--color-primary-700);
|
||||||
|
background-color: var(--color-primary-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.ant-table) {
|
:deep(.ant-table) {
|
||||||
border: 1px solid var(--gray-100);
|
border: 1px solid var(--gray-100);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="search-config-tab">
|
<a-modal
|
||||||
<div class="config-header">
|
:open="props.modelValue"
|
||||||
<p class="config-title">检索配置</p>
|
title="检索配置"
|
||||||
<div class="config-actions">
|
width="800px"
|
||||||
<a-button size="small" @click="resetToDefaults">重置默认</a-button>
|
:confirm-loading="loading"
|
||||||
<a-button size="small" type="primary" @click="saveConfig">保存配置</a-button>
|
@ok="handleSave"
|
||||||
</div>
|
@cancel="handleCancel"
|
||||||
</div>
|
ok-text="保存"
|
||||||
|
cancel-text="取消"
|
||||||
<div class="config-content">
|
>
|
||||||
|
<div class="search-config-modal">
|
||||||
<div v-if="loading" class="config-loading">
|
<div v-if="loading" class="config-loading">
|
||||||
<a-spin size="large" />
|
<a-spin size="large" />
|
||||||
<p>加载配置参数中...</p>
|
<p>加载配置参数中...</p>
|
||||||
@ -66,22 +67,28 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted } from 'vue';
|
import { ref, reactive, computed, watch } from 'vue';
|
||||||
import { useDatabaseStore } from '@/stores/database';
|
import { useDatabaseStore } from '@/stores/database';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { queryApi } from '@/apis/knowledge_api';
|
import { queryApi } from '@/apis/knowledge_api';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
databaseId: {
|
databaseId: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'save']);
|
||||||
|
|
||||||
const store = useDatabaseStore();
|
const store = useDatabaseStore();
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
@ -193,7 +200,7 @@ const resetToDefaults = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 保存配置
|
// 保存配置
|
||||||
const saveConfig = async () => {
|
const handleSave = async () => {
|
||||||
// 确保 include_distances 始终为 true
|
// 确保 include_distances 始终为 true
|
||||||
meta['include_distances'] = true;
|
meta['include_distances'] = true;
|
||||||
|
|
||||||
@ -204,6 +211,13 @@ const saveConfig = async () => {
|
|||||||
// 服务器保存成功后,再保存到 localStorage(兼容性)
|
// 服务器保存成功后,再保存到 localStorage(兼容性)
|
||||||
localStorage.setItem(`search-config-${props.databaseId}`, JSON.stringify(meta));
|
localStorage.setItem(`search-config-${props.databaseId}`, JSON.stringify(meta));
|
||||||
message.success('配置已保存');
|
message.success('配置已保存');
|
||||||
|
|
||||||
|
// 更新 store 中的配置
|
||||||
|
Object.assign(store.meta, meta);
|
||||||
|
|
||||||
|
// 触发保存事件
|
||||||
|
emit('save', { ...meta });
|
||||||
|
emit('update:modelValue', false);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.message || '保存失败');
|
throw new Error(response.message || '保存失败');
|
||||||
}
|
}
|
||||||
@ -211,49 +225,22 @@ const saveConfig = async () => {
|
|||||||
console.error('保存配置到知识库失败:', error);
|
console.error('保存配置到知识库失败:', error);
|
||||||
message.error('保存配置失败:' + error.message);
|
message.error('保存配置失败:' + error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 store 中的配置
|
|
||||||
Object.assign(store.meta, meta);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 组件挂载时加载数据
|
// 处理取消
|
||||||
onMounted(() => {
|
const handleCancel = () => {
|
||||||
|
emit('update:modelValue', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听弹窗显示状态,显示时加载数据
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
if (newVal && props.databaseId) {
|
||||||
loadQueryParams();
|
loadQueryParams();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.search-config-tab {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: var(--gray-0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 16px;
|
|
||||||
// border-bottom: 1px solid var(--gray-200);
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
.config-title {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--gray-800);
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-content {
|
|
||||||
flex: 1;
|
|
||||||
padding: 16px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-loading,
|
.config-loading,
|
||||||
.config-error {
|
.config-error {
|
||||||
@ -261,7 +248,7 @@ onMounted(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 300px;
|
||||||
color: var(--gray-500);
|
color: var(--gray-500);
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@ -271,10 +258,9 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.config-forms {
|
.config-forms {
|
||||||
max-width: 800px;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 表单样式优化
|
// 表单样式优化
|
||||||
:deep(.ant-form-item) {
|
:deep(.ant-form-item) {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@ -41,9 +41,6 @@
|
|||||||
ref="mindmapSectionRef"
|
ref="mindmapSectionRef"
|
||||||
/>
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="config" tab="检索配置">
|
|
||||||
<SearchConfigTab :database-id="databaseId" />
|
|
||||||
</a-tab-pane>
|
|
||||||
<a-tab-pane key="evaluation" tab="RAG评估(Beta)">
|
<a-tab-pane key="evaluation" tab="RAG评估(Beta)">
|
||||||
<RAGEvaluationTab
|
<RAGEvaluationTab
|
||||||
v-if="databaseId"
|
v-if="databaseId"
|
||||||
@ -84,7 +81,6 @@ import FileDetailModal from '@/components/FileDetailModal.vue';
|
|||||||
import FileUploadModal from '@/components/FileUploadModal.vue';
|
import FileUploadModal from '@/components/FileUploadModal.vue';
|
||||||
import KnowledgeGraphSection from '@/components/KnowledgeGraphSection.vue';
|
import KnowledgeGraphSection from '@/components/KnowledgeGraphSection.vue';
|
||||||
import QuerySection from '@/components/QuerySection.vue';
|
import QuerySection from '@/components/QuerySection.vue';
|
||||||
import SearchConfigTab from '@/components/SearchConfigTab.vue';
|
|
||||||
import MindMapSection from '@/components/MindMapSection.vue';
|
import MindMapSection from '@/components/MindMapSection.vue';
|
||||||
import RAGEvaluationTab from '@/components/RAGEvaluationTab.vue';
|
import RAGEvaluationTab from '@/components/RAGEvaluationTab.vue';
|
||||||
import EvaluationBenchmarks from '@/components/EvaluationBenchmarks.vue';
|
import EvaluationBenchmarks from '@/components/EvaluationBenchmarks.vue';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user