fix(kb): distinguish loading errors from empty states (#9578)

* fix(kb): distinguish loading errors from empty states

* fix(kb): reset loading errors before retries
This commit is contained in:
lxfight
2026-08-07 17:36:27 +08:00
committed by GitHub
parent de4cbbcbc1
commit 0024a5da46
12 changed files with 97 additions and 9 deletions
@@ -4,6 +4,10 @@
"breadcrumb": {
"list": "Knowledge Bases"
},
"states": {
"loadError": "Failed to load knowledge base details",
"retry": "Retry"
},
"tabs": {
"overview": "Overview",
"documents": "Documents",
@@ -1,6 +1,11 @@
{
"title": "Document Details",
"backToKB": "Back to Knowledge Base",
"states": {
"loadError": "Failed to load document details",
"chunksLoadError": "Failed to load chunks",
"retry": "Retry"
},
"info": {
"title": "Document Information",
"name": "Document Name",
@@ -11,7 +11,8 @@
"documents": "Documents",
"chunks": "Chunks",
"sessionConfig": "Session Config",
"initError": "Initialization Failed"
"initError": "Initialization Failed",
"retry": "Retry"
},
"card": {
"edit": "Edit",
@@ -4,6 +4,10 @@
"breadcrumb": {
"list": "Базы знаний"
},
"states": {
"loadError": "Не удалось загрузить базу знаний",
"retry": "Повторить"
},
"tabs": {
"overview": "Обзор",
"documents": "Документы",
@@ -1,6 +1,11 @@
{
"title": "Просмотр документа",
"backToKB": "К базе знаний",
"states": {
"loadError": "Не удалось загрузить документ",
"chunksLoadError": "Не удалось загрузить фрагменты",
"retry": "Повторить"
},
"info": {
"title": "Информация о документе",
"name": "Имя файла",
@@ -52,4 +57,4 @@
"vecDocId": "ID вектора",
"close": "Закрыть"
}
}
}
@@ -11,7 +11,8 @@
"documents": "док.",
"chunks": "фрагм.",
"sessionConfig": "Профиль",
"initError": "Ошибка инициализации"
"initError": "Ошибка инициализации",
"retry": "Повторить"
},
"card": {
"edit": "Изменить",
@@ -4,6 +4,10 @@
"breadcrumb": {
"list": "知识库"
},
"states": {
"loadError": "知识库详情加载失败",
"retry": "重试"
},
"tabs": {
"overview": "概览",
"documents": "文档管理",
@@ -1,6 +1,11 @@
{
"title": "文档详情",
"backToKB": "返回知识库",
"states": {
"loadError": "文档详情加载失败",
"chunksLoadError": "分块列表加载失败",
"retry": "重试"
},
"info": {
"title": "文档信息",
"name": "文档名称",
@@ -11,7 +11,8 @@
"documents": "文档",
"chunks": "分块",
"sessionConfig": "会话配置",
"initError": "初始化失败"
"initError": "初始化失败",
"retry": "重试"
},
"card": {
"edit": "编辑",
@@ -18,6 +18,14 @@
<v-progress-circular indeterminate color="primary" size="64" />
</div>
<div v-else-if="loadError" class="loading-container">
<v-icon size="72" color="error">mdi-alert-circle-outline</v-icon>
<p class="mt-4 text-medium-emphasis">{{ t('states.loadError') }}</p>
<v-btn class="mt-4" prepend-icon="mdi-refresh" color="primary" variant="tonal" @click="loadDocument">
{{ t('states.retry') }}
</v-btn>
</div>
<!-- 主内容 -->
<div v-else class="document-content">
<!-- 文档信息卡片 -->
@@ -97,7 +105,16 @@
</v-card-title>
<v-card-text class="pa-0">
<v-alert v-if="chunksLoadError" type="error" variant="tonal" class="ma-4">
<div class="d-flex align-center justify-space-between ga-4">
<span>{{ t('states.chunksLoadError') }}</span>
<v-btn prepend-icon="mdi-refresh" variant="tonal" size="small" @click="loadChunks">
{{ t('states.retry') }}
</v-btn>
</div>
</v-alert>
<v-data-table
v-else
:headers="headers"
:items="filteredChunks"
:loading="loadingChunks"
@@ -150,7 +167,7 @@
<!-- 自定义分页器 -->
<div v-if="!searchQuery && totalChunks > 0" class="pa-4 d-flex align-center justify-space-between">
<div v-if="!chunksLoadError && !searchQuery && totalChunks > 0" class="pa-4 d-flex align-center justify-space-between">
<div class="text-caption text-medium-emphasis">
{{ t('chunks.showing') }} {{ (page - 1) * pageSize + 1 }} - {{ Math.min(page * pageSize, totalChunks) }} / {{ totalChunks }}
</div>
@@ -250,6 +267,8 @@ const docId = ref(route.params.docId as string)
// 状态
const loading = ref(true)
const loadingChunks = ref(false)
const loadError = ref(false)
const chunksLoadError = ref(false)
const document = ref<any>({})
const chunks = ref<any[]>([])
const searchQuery = ref('')
@@ -293,14 +312,20 @@ const filteredChunks = computed(() => {
// 加载文档详情
const loadDocument = async () => {
loading.value = true
loadError.value = false
try {
const response = await knowledgeApi.document(kbId.value, docId.value)
if (response.data.status === 'ok') {
document.value = response.data.data
loadError.value = false
} else {
loadError.value = true
showSnackbar(response.data.message || t('states.loadError'), 'error')
}
} catch (error) {
console.error('Failed to load document:', error)
showSnackbar('加载文档详情失败', 'error')
loadError.value = true
showSnackbar(t('states.loadError'), 'error')
} finally {
loading.value = false
}
@@ -309,6 +334,7 @@ const loadDocument = async () => {
// 加载分块列表
const loadChunks = async () => {
loadingChunks.value = true
chunksLoadError.value = false
try {
const response = await knowledgeApi.chunks(kbId.value, {
document_id: docId.value,
@@ -318,10 +344,15 @@ const loadChunks = async () => {
if (response.data.status === 'ok') {
chunks.value = response.data.data.items || []
totalChunks.value = response.data.data.total || 0
chunksLoadError.value = false
} else {
chunksLoadError.value = true
showSnackbar(response.data.message || t('states.chunksLoadError'), 'error')
}
} catch (error) {
console.error('Failed to load chunks:', error)
showSnackbar('加载分块列表失败', 'error')
chunksLoadError.value = true
showSnackbar(t('states.chunksLoadError'), 'error')
} finally {
loadingChunks.value = false
}
@@ -5,6 +5,14 @@
<v-progress-circular indeterminate color="primary" size="64" />
</div>
<div v-else-if="loadError" class="loading-container">
<v-icon size="72" color="error">mdi-alert-circle-outline</v-icon>
<p class="mt-4 text-medium-emphasis">{{ t('states.loadError') }}</p>
<v-btn class="mt-4" prepend-icon="mdi-refresh" color="primary" variant="tonal" @click="loadKB">
{{ t('states.retry') }}
</v-btn>
</div>
<!-- 主内容 -->
<div v-else class="kb-content">
<!-- 标签页 -->
@@ -173,6 +181,7 @@ const emit = defineEmits<{
const kbId = ref(route.params.kbId as string)
const loading = ref(true)
const loadError = ref(false)
const activeTab = ref('overview')
const kb = ref<any>({})
@@ -191,17 +200,21 @@ const showSnackbar = (text: string, color: string = 'success') => {
// 加载知识库详情
const loadKB = async () => {
loading.value = true
loadError.value = false
try {
const response = await knowledgeApi.get(kbId.value)
if (response.data.status === 'ok') {
kb.value = response.data.data
loadError.value = false
emit('title-change', kb.value.kb_name || '')
} else {
showSnackbar(response.data.message || '加载失败', 'error')
loadError.value = true
showSnackbar(response.data.message || t('states.loadError'), 'error')
}
} catch (error) {
console.error('Failed to load knowledge base:', error)
showSnackbar('加载知识库详情失败', 'error')
loadError.value = true
showSnackbar(t('states.loadError'), 'error')
} finally {
loading.value = false
}
@@ -5,6 +5,15 @@
<p class="mt-4 text-medium-emphasis">{{ t('list.loading') }}</p>
</div>
<div v-else-if="loadError && kbList.length === 0" class="empty-state">
<v-icon size="72" color="error">mdi-alert-circle-outline</v-icon>
<h2 class="mt-4">{{ t('messages.loadError') }}</h2>
<v-btn class="mt-6" prepend-icon="mdi-refresh" color="primary" variant="tonal"
@click="loadKnowledgeBases()">
{{ t('list.retry') }}
</v-btn>
</div>
<div v-else-if="kbList.length > 0" class="kb-list">
<OutlinedActionListItem
v-for="kb in kbList"
@@ -277,6 +286,7 @@ const router = useRouter()
// 状态
const loading = ref(false)
const loadError = ref(false)
const saving = ref(false)
const deleting = ref(false)
const kbList = ref<any[]>([])
@@ -337,6 +347,7 @@ const emojiCategories = [
// 加载知识库列表
const loadKnowledgeBases = async (refreshStats = false) => {
loading.value = true
loadError.value = false
try {
if (refreshStats) {
page.value = 1
@@ -350,11 +361,14 @@ const loadKnowledgeBases = async (refreshStats = false) => {
const data = response.data.data
kbList.value = data.items || []
total.value = data.total || 0
loadError.value = false
} else {
loadError.value = true
showSnackbar(response.data.message || t('messages.loadError'), 'error')
}
} catch (error) {
console.error('Failed to load knowledge bases:', error)
loadError.value = true
showSnackbar(t('messages.loadError'), 'error')
} finally {
loading.value = false