mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-17 17:51:20 +08:00
feat: 添加知识库检索功能,支持根据知识库 ID 列出相关会话;更新相关界面和国际化文本
This commit is contained in:
@@ -238,10 +238,13 @@ class KnowledgeBaseManager:
|
||||
)
|
||||
|
||||
if len(embedding_providers) > 1 and not configured_provider_id:
|
||||
logger.warning(
|
||||
provider = embedding_providers[0]
|
||||
provider_id = provider.meta().id
|
||||
logger.info(
|
||||
f"检测到 {len(embedding_providers)} 个 Embedding Provider,"
|
||||
f"但未指定使用哪个,将默认使用第一个"
|
||||
f"未在配置文件中指定 embedding_provider_id,将使用第一个: {provider_id}"
|
||||
)
|
||||
return provider
|
||||
|
||||
provider = embedding_providers[0]
|
||||
provider_id = provider.meta().id
|
||||
|
||||
@@ -57,6 +57,7 @@ class KnowledgeBaseRoute(Route):
|
||||
"/kb/session/config/set": ("POST", self.set_session_config),
|
||||
"/kb/session/config/delete": ("POST", self.delete_session_config),
|
||||
"/kb/session/config/list": ("GET", self.list_session_configs),
|
||||
"/kb/session/config/list_by_kb": ("GET", self.list_sessions_by_kb),
|
||||
}
|
||||
self.register_routes()
|
||||
|
||||
@@ -1241,3 +1242,51 @@ class KnowledgeBaseRoute(Route):
|
||||
logger.error(f"获取会话配置列表失败: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
return Response().error(f"获取会话配置列表失败: {str(e)}").__dict__
|
||||
|
||||
async def list_sessions_by_kb(self):
|
||||
"""获取使用特定知识库的会话列表
|
||||
|
||||
Query 参数:
|
||||
- kb_id: 知识库 ID (必填)
|
||||
"""
|
||||
try:
|
||||
kb_db = self.kb_db if self.kb_db else self._get_kb_manager() and self.kb_db
|
||||
kb_id = request.args.get("kb_id")
|
||||
|
||||
if not kb_id:
|
||||
return Response().error("缺少参数 kb_id").__dict__
|
||||
|
||||
# 获取所有会话配置
|
||||
configs = await kb_db.list_all_session_configs(offset=0, limit=1000)
|
||||
|
||||
import json
|
||||
|
||||
# 筛选包含该知识库的会话
|
||||
session_list = []
|
||||
for config in configs:
|
||||
kb_ids = json.loads(config.kb_ids)
|
||||
if kb_id in kb_ids:
|
||||
session_dict = {
|
||||
"config_id": config.config_id,
|
||||
"scope": config.scope,
|
||||
"scope_id": config.scope_id,
|
||||
"kb_ids": kb_ids,
|
||||
"top_k": config.top_k,
|
||||
"enable_rerank": config.enable_rerank,
|
||||
"created_at": config.created_at.isoformat(),
|
||||
"updated_at": config.updated_at.isoformat(),
|
||||
}
|
||||
session_list.append(session_dict)
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok({"sessions": session_list, "total": len(session_list), "kb_id": kb_id})
|
||||
.__dict__
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
return Response().error(str(e)).__dict__
|
||||
except Exception as e:
|
||||
logger.error(f"获取知识库会话列表失败: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
return Response().error(f"获取知识库会话列表失败: {str(e)}").__dict__
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"tabs": {
|
||||
"overview": "概览",
|
||||
"documents": "文档管理",
|
||||
"sessions": "会话配置",
|
||||
"retrieval": "知识库检索",
|
||||
"sessions": "使用会话",
|
||||
"settings": "设置"
|
||||
},
|
||||
"overview": {
|
||||
@@ -57,24 +58,44 @@
|
||||
"fileRequired": "请选择要上传的文件"
|
||||
},
|
||||
"sessions": {
|
||||
"title": "会话配置",
|
||||
"subtitle": "配置哪些会话可以使用此知识库",
|
||||
"empty": "暂无会话配置",
|
||||
"add": "添加配置",
|
||||
"title": "使用该知识库的会话",
|
||||
"subtitle": "以下会话正在使用此知识库",
|
||||
"empty": "暂无会话使用此知识库",
|
||||
"refresh": "刷新",
|
||||
"scope": "范围",
|
||||
"scopeId": "标识",
|
||||
"scopeId": "会话标识",
|
||||
"topK": "返回结果数",
|
||||
"enableRerank": "启用重排序",
|
||||
"actions": "操作",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"scopeSession": "会话级别",
|
||||
"scopePlatform": "平台级别",
|
||||
"deleteConfirm": "确定要删除此配置吗?",
|
||||
"addSuccess": "配置添加成功",
|
||||
"addFailed": "配置添加失败",
|
||||
"deleteSuccess": "配置删除成功",
|
||||
"deleteFailed": "配置删除失败"
|
||||
"viewInSessionManagement": "在会话管理中查看",
|
||||
"goToSessionManagement": "前往会话管理",
|
||||
"loadFailed": "加载会话列表失败"
|
||||
},
|
||||
"retrieval": {
|
||||
"title": "知识库检索",
|
||||
"subtitle": "使用稠密检索和稀疏检索测试知识库内容",
|
||||
"query": "检索查询",
|
||||
"queryPlaceholder": "输入要检索的内容...",
|
||||
"search": "检索",
|
||||
"searching": "检索中...",
|
||||
"results": "检索结果",
|
||||
"noResults": "没有找到相关内容",
|
||||
"tryDifferentQuery": "尝试使用不同的查询词",
|
||||
"settings": "检索设置",
|
||||
"topK": "返回结果数量",
|
||||
"topKHint": "最多返回多少条检索结果",
|
||||
"enableRerank": "启用重排序",
|
||||
"enableRerankHint": "使用重排序模型提高检索质量",
|
||||
"score": "相关度分数",
|
||||
"document": "所属文档",
|
||||
"chunk": "文本块 #{index}",
|
||||
"content": "内容",
|
||||
"charCount": "{count} 字符",
|
||||
"searchSuccess": "检索完成,找到 {count} 条结果",
|
||||
"searchFailed": "检索失败",
|
||||
"queryRequired": "请输入检索查询"
|
||||
},
|
||||
"settings": {
|
||||
"title": "知识库设置",
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
{{ t('tabs.documents') }}
|
||||
<v-chip class="ml-2" size="small" variant="tonal">{{ kb.doc_count || 0 }}</v-chip>
|
||||
</v-tab>
|
||||
<v-tab value="retrieval">
|
||||
<v-icon start>mdi-magnify</v-icon>
|
||||
{{ t('tabs.retrieval') }}
|
||||
</v-tab>
|
||||
<v-tab value="sessions">
|
||||
<v-icon start>mdi-account-multiple</v-icon>
|
||||
{{ t('tabs.sessions') }}
|
||||
@@ -157,7 +161,12 @@
|
||||
<DocumentsTab :kb-id="kbId" :kb="kb" @refresh="loadKB" />
|
||||
</v-window-item>
|
||||
|
||||
<!-- 会话配置 -->
|
||||
<!-- 知识库检索 -->
|
||||
<v-window-item value="retrieval">
|
||||
<RetrievalTab :kb-id="kbId" />
|
||||
</v-window-item>
|
||||
|
||||
<!-- 使用会话 -->
|
||||
<v-window-item value="sessions">
|
||||
<SessionsTab :kb-id="kbId" />
|
||||
</v-window-item>
|
||||
@@ -182,6 +191,7 @@ import { useRoute } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import { useModuleI18n } from '@/i18n/composables'
|
||||
import DocumentsTab from './components/DocumentsTab.vue'
|
||||
import RetrievalTab from './components/RetrievalTab.vue'
|
||||
import SessionsTab from './components/SessionsTab.vue'
|
||||
import SettingsTab from './components/SettingsTab.vue'
|
||||
|
||||
|
||||
243
dashboard/src/views/knowledge-base/components/RetrievalTab.vue
Normal file
243
dashboard/src/views/knowledge-base/components/RetrievalTab.vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<template>
|
||||
<div class="retrieval-tab">
|
||||
<v-card elevation="2">
|
||||
<v-card-title class="pa-4">{{ t('retrieval.title') }}</v-card-title>
|
||||
<v-card-subtitle class="px-4 pb-4">
|
||||
{{ t('retrieval.subtitle') }}
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pa-6">
|
||||
<!-- 查询输入区域 -->
|
||||
<v-row>
|
||||
<v-col cols="12" md="8">
|
||||
<v-textarea
|
||||
v-model="query"
|
||||
:label="t('retrieval.query')"
|
||||
:placeholder="t('retrieval.queryPlaceholder')"
|
||||
variant="outlined"
|
||||
rows="3"
|
||||
auto-grow
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
<v-card variant="outlined" class="pa-4">
|
||||
<h4 class="text-subtitle-2 mb-3">{{ t('retrieval.settings') }}</h4>
|
||||
|
||||
<v-text-field
|
||||
v-model.number="topK"
|
||||
:label="t('retrieval.topK')"
|
||||
:hint="t('retrieval.topKHint')"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
persistent-hint
|
||||
class="mb-3"
|
||||
/>
|
||||
|
||||
<v-checkbox
|
||||
v-model="enableRerank"
|
||||
:label="t('retrieval.enableRerank')"
|
||||
:hint="t('retrieval.enableRerankHint')"
|
||||
color="primary"
|
||||
density="compact"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<div class="d-flex justify-end mb-4">
|
||||
<v-btn
|
||||
prepend-icon="mdi-magnify"
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
@click="performRetrieval"
|
||||
:loading="loading"
|
||||
:disabled="!query || query.trim() === ''"
|
||||
>
|
||||
{{ loading ? t('retrieval.searching') : t('retrieval.search') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- 检索结果 -->
|
||||
<div v-if="hasSearched" class="results-section">
|
||||
<v-divider class="mb-4" />
|
||||
|
||||
<div class="d-flex align-center mb-4">
|
||||
<h3 class="text-h6">{{ t('retrieval.results') }}</h3>
|
||||
<v-chip class="ml-3" color="primary" variant="tonal">
|
||||
{{ results.length }} {{ t('retrieval.results') }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- 结果列表 -->
|
||||
<div v-if="results.length > 0" class="results-list">
|
||||
<v-card
|
||||
v-for="(result, index) in results"
|
||||
:key="result.chunk_id"
|
||||
variant="outlined"
|
||||
class="mb-4"
|
||||
>
|
||||
<v-card-title class="d-flex align-center pa-4">
|
||||
<v-chip size="small" color="primary" class="mr-2">
|
||||
#{{ index + 1 }}
|
||||
</v-chip>
|
||||
<span class="text-subtitle-1">
|
||||
{{ t('retrieval.chunk', { index: result.chunk_index }) }}
|
||||
</span>
|
||||
<v-spacer />
|
||||
<v-chip size="small" :color="getScoreColor(result.score)">
|
||||
{{ t('retrieval.score') }}: {{ result.score.toFixed(4) }}
|
||||
</v-chip>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<div class="mb-3">
|
||||
<v-chip size="small" variant="tonal" class="mr-2">
|
||||
<v-icon start size="small">mdi-file-document</v-icon>
|
||||
{{ result.doc_name }}
|
||||
</v-chip>
|
||||
<v-chip size="small" variant="tonal">
|
||||
<v-icon start size="small">mdi-text</v-icon>
|
||||
{{ t('retrieval.charCount', { count: result.char_count }) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<div class="content-box">
|
||||
{{ result.content }}
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<!-- 空结果 -->
|
||||
<div v-else class="text-center py-12">
|
||||
<v-icon size="80" color="grey-lighten-2">mdi-text-box-search-outline</v-icon>
|
||||
<p class="text-h6 mt-4 text-medium-emphasis">{{ t('retrieval.noResults') }}</p>
|
||||
<p class="text-body-2 text-medium-emphasis">{{ t('retrieval.tryDifferentQuery') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 消息提示 -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color">
|
||||
{{ snackbar.text }}
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { useModuleI18n } from '@/i18n/composables'
|
||||
|
||||
const { tm: t } = useModuleI18n('features/knowledge-base/detail')
|
||||
|
||||
const props = defineProps<{
|
||||
kbId: string
|
||||
}>()
|
||||
|
||||
// 状态
|
||||
const loading = ref(false)
|
||||
const query = ref('')
|
||||
const topK = ref(5)
|
||||
const enableRerank = ref(true)
|
||||
const results = ref<any[]>([])
|
||||
const hasSearched = ref(false)
|
||||
|
||||
const snackbar = ref({
|
||||
show: false,
|
||||
text: '',
|
||||
color: 'success'
|
||||
})
|
||||
|
||||
const showSnackbar = (text: string, color: string = 'success') => {
|
||||
snackbar.value.text = text
|
||||
snackbar.value.color = color
|
||||
snackbar.value.show = true
|
||||
}
|
||||
|
||||
// 执行检索
|
||||
const performRetrieval = async () => {
|
||||
if (!query.value || query.value.trim() === '') {
|
||||
showSnackbar(t('retrieval.queryRequired'), 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
hasSearched.value = false
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/kb/retrieve', {
|
||||
query: query.value,
|
||||
kb_ids: [props.kbId],
|
||||
top_k: topK.value,
|
||||
enable_rerank: enableRerank.value
|
||||
})
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
results.value = response.data.data.results || []
|
||||
hasSearched.value = true
|
||||
showSnackbar(t('retrieval.searchSuccess', { count: results.value.length }))
|
||||
} else {
|
||||
showSnackbar(response.data.message || t('retrieval.searchFailed'), 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Retrieval failed:', error)
|
||||
showSnackbar(t('retrieval.searchFailed'), 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 根据分数获取颜色
|
||||
const getScoreColor = (score: number) => {
|
||||
if (score >= 0.8) return 'success'
|
||||
if (score >= 0.6) return 'info'
|
||||
if (score >= 0.4) return 'warning'
|
||||
return 'error'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.retrieval-tab {
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.results-section {
|
||||
animation: slideUp 0.4s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.content-box {
|
||||
background: rgba(var(--v-theme-surface-variant), 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
@@ -5,13 +5,13 @@
|
||||
<span>{{ t('sessions.title') }}</span>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
prepend-icon="mdi-plus"
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-refresh"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
@click="showAddDialog = true"
|
||||
@click="loadSessions"
|
||||
:loading="loading"
|
||||
>
|
||||
{{ t('sessions.add') }}
|
||||
{{ t('sessions.refresh') }}
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
@@ -41,11 +41,12 @@
|
||||
|
||||
<template #item.actions="{ item }">
|
||||
<v-btn
|
||||
icon="mdi-delete"
|
||||
icon="mdi-open-in-new"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="error"
|
||||
@click="confirmDelete(item)"
|
||||
color="primary"
|
||||
@click="goToSessionManagement(item)"
|
||||
:title="t('sessions.viewInSessionManagement')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -53,99 +54,21 @@
|
||||
<div class="text-center py-8">
|
||||
<v-icon size="64" color="grey-lighten-2">mdi-account-multiple-outline</v-icon>
|
||||
<p class="mt-4 text-medium-emphasis">{{ t('sessions.empty') }}</p>
|
||||
<v-btn
|
||||
class="mt-4"
|
||||
prepend-icon="mdi-cog"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
@click="goToSessionManagement()"
|
||||
>
|
||||
{{ t('sessions.goToSessionManagement') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 添加配置对话框 -->
|
||||
<v-dialog v-model="showAddDialog" max-width="500px" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="pa-4">
|
||||
<span class="text-h6">{{ t('sessions.add') }}</span>
|
||||
<v-spacer />
|
||||
<v-btn icon="mdi-close" variant="text" @click="closeAddDialog" />
|
||||
</v-card-title>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pa-6">
|
||||
<v-form ref="formRef">
|
||||
<v-select
|
||||
v-model="formData.scope"
|
||||
:items="scopeOptions"
|
||||
:label="t('sessions.scope')"
|
||||
variant="outlined"
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="formData.scope_id"
|
||||
:label="t('sessions.scopeId')"
|
||||
:placeholder="formData.scope === 'session' ? 'platform:xxx:session_id' : 'platform_id'"
|
||||
variant="outlined"
|
||||
required
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model.number="formData.top_k"
|
||||
:label="t('sessions.topK')"
|
||||
type="number"
|
||||
variant="outlined"
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<v-checkbox
|
||||
v-model="formData.enable_rerank"
|
||||
:label="t('sessions.enableRerank')"
|
||||
color="primary"
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="closeAddDialog">取消</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
@click="addSession"
|
||||
:loading="saving"
|
||||
>
|
||||
添加
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<v-dialog v-model="showDeleteDialog" max-width="400px">
|
||||
<v-card>
|
||||
<v-card-title class="pa-4 text-h6">确认删除</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text class="pa-6">
|
||||
<p>{{ t('sessions.deleteConfirm') }}</p>
|
||||
</v-card-text>
|
||||
<v-divider />
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="showDeleteDialog = false">取消</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="elevated"
|
||||
@click="deleteSession"
|
||||
:loading="deleting"
|
||||
>
|
||||
删除
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 消息提示 -->
|
||||
<v-snackbar v-model="snackbar.show" :color="snackbar.color">
|
||||
{{ snackbar.text }}
|
||||
@@ -155,10 +78,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import { useModuleI18n } from '@/i18n/composables'
|
||||
|
||||
const { tm: t } = useModuleI18n('features/knowledge-base/detail')
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps<{
|
||||
kbId: string
|
||||
@@ -166,13 +91,7 @@ const props = defineProps<{
|
||||
|
||||
// 状态
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const deleting = ref(false)
|
||||
const sessions = ref<any[]>([])
|
||||
const showAddDialog = ref(false)
|
||||
const showDeleteDialog = ref(false)
|
||||
const deleteTarget = ref<any>(null)
|
||||
const formRef = ref()
|
||||
|
||||
const snackbar = ref({
|
||||
show: false,
|
||||
@@ -186,14 +105,6 @@ const showSnackbar = (text: string, color: string = 'success') => {
|
||||
snackbar.value.show = true
|
||||
}
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
scope: 'session',
|
||||
scope_id: '',
|
||||
top_k: 5,
|
||||
enable_rerank: true
|
||||
})
|
||||
|
||||
// 表格列
|
||||
const headers = [
|
||||
{ title: t('sessions.scope'), key: 'scope' },
|
||||
@@ -203,104 +114,46 @@ const headers = [
|
||||
{ title: t('sessions.actions'), key: 'actions', sortable: false, align: 'end' }
|
||||
]
|
||||
|
||||
// 范围选项
|
||||
const scopeOptions = [
|
||||
{ title: t('sessions.scopeSession'), value: 'session' },
|
||||
{ title: t('sessions.scopePlatform'), value: 'platform' }
|
||||
]
|
||||
|
||||
// 加载会话配置
|
||||
// 加载使用该知识库的会话
|
||||
const loadSessions = async () => {
|
||||
loading.value = true
|
||||
console.log('[SessionsTab] 开始加载会话列表, kb_id:', props.kbId)
|
||||
|
||||
try {
|
||||
const response = await axios.get('/api/kb/session/config/list')
|
||||
const url = '/api/kb/session/config/list_by_kb'
|
||||
const params = { kb_id: props.kbId }
|
||||
console.log('[SessionsTab] 请求URL:', url, '参数:', params)
|
||||
|
||||
const response = await axios.get(url, { params })
|
||||
console.log('[SessionsTab] 响应状态:', response.status)
|
||||
console.log('[SessionsTab] 响应数据:', response.data)
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
// 过滤出使用当前知识库的配置
|
||||
sessions.value = response.data.data.items.filter((item: any) => {
|
||||
const kbIds = JSON.parse(item.kb_ids || '[]')
|
||||
return kbIds.includes(props.kbId)
|
||||
})
|
||||
sessions.value = response.data.data.sessions
|
||||
console.log('[SessionsTab] 成功加载会话列表, 数量:', sessions.value.length)
|
||||
} else {
|
||||
console.error('[SessionsTab] API返回错误:', response.data.message)
|
||||
showSnackbar(response.data.message || t('sessions.loadFailed'), 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load session configs:', error)
|
||||
showSnackbar('加载会话配置失败', 'error')
|
||||
console.error('[SessionsTab] 请求失败:', error)
|
||||
if (error.response) {
|
||||
console.error('[SessionsTab] 错误响应状态:', error.response.status)
|
||||
console.error('[SessionsTab] 错误响应数据:', error.response.data)
|
||||
} else if (error.request) {
|
||||
console.error('[SessionsTab] 请求未收到响应:', error.request)
|
||||
} else {
|
||||
console.error('[SessionsTab] 请求配置错误:', error.message)
|
||||
}
|
||||
showSnackbar(t('sessions.loadFailed'), 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 添加会话配置
|
||||
const addSession = async () => {
|
||||
const { valid } = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
const response = await axios.post('/api/kb/session/config', {
|
||||
scope: formData.value.scope,
|
||||
scope_id: formData.value.scope_id,
|
||||
kb_ids: [props.kbId],
|
||||
top_k: formData.value.top_k,
|
||||
enable_rerank: formData.value.enable_rerank
|
||||
})
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
showSnackbar(t('sessions.addSuccess'))
|
||||
closeAddDialog()
|
||||
await loadSessions()
|
||||
} else {
|
||||
showSnackbar(response.data.message || t('sessions.addFailed'), 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to add session config:', error)
|
||||
showSnackbar(t('sessions.addFailed'), 'error')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 确认删除
|
||||
const confirmDelete = (session: any) => {
|
||||
deleteTarget.value = session
|
||||
showDeleteDialog.value = true
|
||||
}
|
||||
|
||||
// 删除会话配置
|
||||
const deleteSession = async () => {
|
||||
if (!deleteTarget.value) return
|
||||
|
||||
deleting.value = true
|
||||
try {
|
||||
const response = await axios.post('/api/kb/session/config/delete', {
|
||||
scope: deleteTarget.value.scope,
|
||||
scope_id: deleteTarget.value.scope_id
|
||||
})
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
showSnackbar(t('sessions.deleteSuccess'))
|
||||
showDeleteDialog.value = false
|
||||
await loadSessions()
|
||||
} else {
|
||||
showSnackbar(response.data.message || t('sessions.deleteFailed'), 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete session config:', error)
|
||||
showSnackbar(t('sessions.deleteFailed'), 'error')
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭添加对话框
|
||||
const closeAddDialog = () => {
|
||||
showAddDialog.value = false
|
||||
formData.value = {
|
||||
scope: 'session',
|
||||
scope_id: '',
|
||||
top_k: 5,
|
||||
enable_rerank: true
|
||||
}
|
||||
formRef.value?.reset()
|
||||
// 跳转到会话管理页面
|
||||
const goToSessionManagement = (session?: any) => {
|
||||
router.push({ name: 'SessionManagement' })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
Reference in New Issue
Block a user