fix: 统一知识能力空状态
This commit is contained in:
parent
742048e9eb
commit
9bcfb0e1f4
@ -23,11 +23,24 @@
|
|||||||
|
|
||||||
<!-- 基准列表 -->
|
<!-- 基准列表 -->
|
||||||
<div class="benchmarks-list">
|
<div class="benchmarks-list">
|
||||||
<div v-if="!loading && benchmarks.length === 0" class="empty-state">
|
<ResourceEmptyState
|
||||||
<div class="empty-icon">📋</div>
|
v-if="!loading && benchmarks.length === 0"
|
||||||
<div class="empty-title">暂无评估基准</div>
|
title="暂无评估基准"
|
||||||
<div class="empty-description">上传或生成评估基准开始使用</div>
|
description="上传数据集,或从当前知识库自动生成评估问题。"
|
||||||
</div>
|
:icon="ClipboardList"
|
||||||
|
size="compact"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<a-button type="primary" class="lucide-icon-btn" @click="showUploadModal">
|
||||||
|
<template #icon><Upload :size="16" /></template>
|
||||||
|
上传基准
|
||||||
|
</a-button>
|
||||||
|
<a-button class="lucide-icon-btn" @click="showGenerateModal">
|
||||||
|
<template #icon><Bot :size="16" /></template>
|
||||||
|
自动生成
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</ResourceEmptyState>
|
||||||
|
|
||||||
<div v-else-if="loading" class="loading-state">
|
<div v-else-if="loading" class="loading-state">
|
||||||
<a-spin size="large" />
|
<a-spin size="large" />
|
||||||
@ -272,9 +285,19 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, onUnmounted, computed, watch, h } from 'vue'
|
import { ref, reactive, onMounted, onUnmounted, computed, watch, h } from 'vue'
|
||||||
import { message, Modal } from 'ant-design-vue'
|
import { message, Modal } from 'ant-design-vue'
|
||||||
import { Bot, Download, MoreVertical, RefreshCw, Trash2, Upload, X } from 'lucide-vue-next'
|
import {
|
||||||
|
Bot,
|
||||||
|
ClipboardList,
|
||||||
|
Download,
|
||||||
|
MoreVertical,
|
||||||
|
RefreshCw,
|
||||||
|
Trash2,
|
||||||
|
Upload,
|
||||||
|
X
|
||||||
|
} from 'lucide-vue-next'
|
||||||
import { evaluationApi } from '@/apis/knowledge_api'
|
import { evaluationApi } from '@/apis/knowledge_api'
|
||||||
import { useTaskerStore } from '@/stores/tasker'
|
import { useTaskerStore } from '@/stores/tasker'
|
||||||
|
import ResourceEmptyState from '@/components/shared/ResourceEmptyState.vue'
|
||||||
import BenchmarkUploadModal from './modals/BenchmarkUploadModal.vue'
|
import BenchmarkUploadModal from './modals/BenchmarkUploadModal.vue'
|
||||||
import BenchmarkGenerateModal from './modals/BenchmarkGenerateModal.vue'
|
import BenchmarkGenerateModal from './modals/BenchmarkGenerateModal.vue'
|
||||||
|
|
||||||
@ -914,33 +937,6 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 300px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
font-size: 48px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--gray-900);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-description {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--gray-600);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-state {
|
.loading-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -65,14 +65,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</GraphCanvas>
|
</GraphCanvas>
|
||||||
<div
|
<ResourceEmptyState
|
||||||
v-if="isMilvus && !graphBuildStatus?.locked && !graphBuildLoading"
|
v-if="showGraphConfigEmpty"
|
||||||
class="graph-config-empty"
|
class="graph-empty-state"
|
||||||
|
title="暂无知识图谱"
|
||||||
|
description="配置抽取器后,才能从当前知识库构建实体与关系。"
|
||||||
|
:icon="Network"
|
||||||
|
full-height
|
||||||
>
|
>
|
||||||
<div class="empty-title">尚未配置图谱抽取器</div>
|
<template #actions>
|
||||||
<div class="empty-description">配置抽取器后才能从知识库 Chunk 构建实体与关系。</div>
|
<a-button type="primary" class="lucide-icon-btn" @click="openGraphConfig">
|
||||||
<a-button type="primary" @click="openGraphConfig"> 配置抽取器 </a-button>
|
<Settings :size="16" />
|
||||||
</div>
|
配置抽取器
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</ResourceEmptyState>
|
||||||
|
<ResourceEmptyState
|
||||||
|
v-else-if="showGraphDataEmpty"
|
||||||
|
class="graph-empty-state"
|
||||||
|
:title="graphDataEmptyTitle"
|
||||||
|
:description="graphDataEmptyDescription"
|
||||||
|
:icon="Network"
|
||||||
|
full-height
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<a-button v-if="searchInput.trim()" class="lucide-icon-btn" @click="clearGraphSearch">
|
||||||
|
<Search :size="16" />
|
||||||
|
清空搜索
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
v-else-if="hasPendingGraphChunks && !isBuildActive"
|
||||||
|
type="primary"
|
||||||
|
class="lucide-icon-btn"
|
||||||
|
@click="startGraphBuild"
|
||||||
|
>
|
||||||
|
<Database :size="16" />
|
||||||
|
开始索引
|
||||||
|
</a-button>
|
||||||
|
<a-button v-else class="lucide-icon-btn" @click="loadGraph">
|
||||||
|
<RefreshCw :size="16" :class="{ spin: graph.fetching }" />
|
||||||
|
刷新图谱
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</ResourceEmptyState>
|
||||||
|
|
||||||
<!-- 详情浮动卡片 -->
|
<!-- 详情浮动卡片 -->
|
||||||
<GraphDetailPanel
|
<GraphDetailPanel
|
||||||
@ -320,11 +355,13 @@ import {
|
|||||||
Search,
|
Search,
|
||||||
Loader2,
|
Loader2,
|
||||||
Database,
|
Database,
|
||||||
|
Network,
|
||||||
BrainCircuit,
|
BrainCircuit,
|
||||||
ScanText
|
ScanText
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import GraphCanvas from '@/components/GraphCanvas.vue'
|
import GraphCanvas from '@/components/GraphCanvas.vue'
|
||||||
import GraphDetailPanel from '@/components/GraphDetailPanel.vue'
|
import GraphDetailPanel from '@/components/GraphDetailPanel.vue'
|
||||||
|
import ResourceEmptyState from '@/components/shared/ResourceEmptyState.vue'
|
||||||
import { getKbTypeLabel } from '@/utils/kb_utils'
|
import { getKbTypeLabel } from '@/utils/kb_utils'
|
||||||
import { unifiedApi } from '@/apis/graph_api'
|
import { unifiedApi } from '@/apis/graph_api'
|
||||||
import { graphBuildApi } from '@/apis/knowledge_api'
|
import { graphBuildApi } from '@/apis/knowledge_api'
|
||||||
@ -472,9 +509,31 @@ const graphConfigForm = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const graph = reactive(useGraph(graphRef))
|
const graph = reactive(useGraph(graphRef))
|
||||||
|
const graphLoaded = ref(false)
|
||||||
|
|
||||||
// 计算属性:是否支持知识图谱
|
// 计算属性:是否支持知识图谱
|
||||||
const isGraphSupported = computed(() => GRAPH_SUPPORTED_KB_TYPES.has(kbType.value?.toLowerCase()))
|
const isGraphSupported = computed(() => GRAPH_SUPPORTED_KB_TYPES.has(kbType.value?.toLowerCase()))
|
||||||
|
const hasGraphNodes = computed(() => graph.graphData.nodes.length > 0)
|
||||||
|
const showGraphConfigEmpty = computed(
|
||||||
|
() => isMilvus.value && !graphBuildStatus.value?.locked && !graphBuildLoading.value
|
||||||
|
)
|
||||||
|
const showGraphDataEmpty = computed(
|
||||||
|
() =>
|
||||||
|
isMilvus.value &&
|
||||||
|
Boolean(graphBuildStatus.value?.locked) &&
|
||||||
|
graphLoaded.value &&
|
||||||
|
!graph.fetching &&
|
||||||
|
!hasGraphNodes.value
|
||||||
|
)
|
||||||
|
const graphDataEmptyTitle = computed(() =>
|
||||||
|
searchInput.value.trim() ? '未找到匹配实体' : '暂无知识图谱'
|
||||||
|
)
|
||||||
|
const graphDataEmptyDescription = computed(() => {
|
||||||
|
if (searchInput.value.trim()) return '换个关键词或调整图谱设置后再搜索。'
|
||||||
|
if (isBuildActive.value) return '图谱索引正在运行,完成后会展示实体与关系。'
|
||||||
|
if (hasPendingGraphChunks.value) return '当前还有待索引 Chunk,完成索引后会展示实体与关系。'
|
||||||
|
return '当前知识库还没有可展示的实体与关系。'
|
||||||
|
})
|
||||||
|
|
||||||
let pendingLoadTimer = null
|
let pendingLoadTimer = null
|
||||||
let graphStatusRequestSeq = 0
|
let graphStatusRequestSeq = 0
|
||||||
@ -604,6 +663,7 @@ const resetGraphBuild = async () => {
|
|||||||
clear_config: true
|
clear_config: true
|
||||||
})
|
})
|
||||||
message.success('图谱构建状态已重置')
|
message.success('图谱构建状态已重置')
|
||||||
|
graphLoaded.value = false
|
||||||
graph.clearGraph()
|
graph.clearGraph()
|
||||||
await loadGraphBuildStatus()
|
await loadGraphBuildStatus()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -618,6 +678,9 @@ const loadGraph = async () => {
|
|||||||
const requestSeq = ++graphLoadRequestSeq
|
const requestSeq = ++graphLoadRequestSeq
|
||||||
const currentDatabaseId = kbId.value
|
const currentDatabaseId = kbId.value
|
||||||
graph.fetching = true
|
graph.fetching = true
|
||||||
|
if (!hasGraphNodes.value) {
|
||||||
|
graphLoaded.value = false
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const res = await unifiedApi.getSubgraph({
|
const res = await unifiedApi.getSubgraph({
|
||||||
kb_id: currentDatabaseId,
|
kb_id: currentDatabaseId,
|
||||||
@ -641,6 +704,7 @@ const loadGraph = async () => {
|
|||||||
} finally {
|
} finally {
|
||||||
if (requestSeq === graphLoadRequestSeq) {
|
if (requestSeq === graphLoadRequestSeq) {
|
||||||
graph.fetching = false
|
graph.fetching = false
|
||||||
|
graphLoaded.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -654,6 +718,11 @@ const onSearch = () => {
|
|||||||
loadGraph()
|
loadGraph()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearGraphSearch = () => {
|
||||||
|
searchInput.value = ''
|
||||||
|
loadGraph()
|
||||||
|
}
|
||||||
|
|
||||||
const scheduleGraphLoad = (delay = 200) => {
|
const scheduleGraphLoad = (delay = 200) => {
|
||||||
if (!props.active || !isGraphSupported.value || !kbId.value) {
|
if (!props.active || !isGraphSupported.value || !kbId.value) {
|
||||||
return
|
return
|
||||||
@ -687,6 +756,7 @@ watch(
|
|||||||
watch(kbId, () => {
|
watch(kbId, () => {
|
||||||
graphStatusRequestSeq += 1
|
graphStatusRequestSeq += 1
|
||||||
graphLoadRequestSeq += 1
|
graphLoadRequestSeq += 1
|
||||||
|
graphLoaded.value = false
|
||||||
graph.clearGraph()
|
graph.clearGraph()
|
||||||
graphBuildStatus.value = null
|
graphBuildStatus.value = null
|
||||||
if (isMilvus.value) {
|
if (isMilvus.value) {
|
||||||
@ -699,6 +769,7 @@ watch(kbId, () => {
|
|||||||
|
|
||||||
watch(isGraphSupported, (supported) => {
|
watch(isGraphSupported, (supported) => {
|
||||||
if (!supported) {
|
if (!supported) {
|
||||||
|
graphLoaded.value = false
|
||||||
graph.clearGraph()
|
graph.clearGraph()
|
||||||
graphBuildStatus.value = null
|
graphBuildStatus.value = null
|
||||||
return
|
return
|
||||||
@ -741,30 +812,13 @@ onUnmounted(() => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph-config-empty {
|
.graph-empty-state {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.empty-title {
|
:deep(.resource-empty-state__actions) {
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--gray-1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-description {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--gray-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.ant-btn) {
|
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,19 @@
|
|||||||
<!-- 评估结果区域 -->
|
<!-- 评估结果区域 -->
|
||||||
<div class="evaluation-results">
|
<div class="evaluation-results">
|
||||||
<template v-if="!selectedDataset">
|
<template v-if="!selectedDataset">
|
||||||
<div class="empty-state">
|
<ResourceEmptyState
|
||||||
<a-empty description="暂无可用评估基准,请前往基准管理">
|
class="rag-evaluation-empty"
|
||||||
<a-space>
|
title="暂无可用评估基准"
|
||||||
<a-button @click="$emit('switch-to-benchmarks')"> 前往基准管理 </a-button>
|
description="先上传或生成评估基准,再运行 RAG 评估。"
|
||||||
</a-space>
|
:icon="BarChart3"
|
||||||
</a-empty>
|
>
|
||||||
</div>
|
<template #actions>
|
||||||
|
<a-button type="primary" class="lucide-icon-btn" @click="$emit('switch-to-benchmarks')">
|
||||||
|
<ClipboardList :size="16" />
|
||||||
|
前往基准管理
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</ResourceEmptyState>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="last-evaluation-section">
|
<div class="last-evaluation-section">
|
||||||
@ -445,7 +451,8 @@ import { ref, reactive, onMounted, onUnmounted, computed, watch, 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 { ChevronDown, RefreshCw, X } from 'lucide-vue-next'
|
import { BarChart3, ChevronDown, ClipboardList, RefreshCw, X } from 'lucide-vue-next'
|
||||||
|
import ResourceEmptyState from '@/components/shared/ResourceEmptyState.vue'
|
||||||
import { useTaskerStore } from '@/stores/tasker'
|
import { useTaskerStore } from '@/stores/tasker'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -1349,14 +1356,8 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.rag-evaluation-empty {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: var(--gray-0);
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid var(--gray-200);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-info {
|
.progress-info {
|
||||||
|
|||||||
130
web/src/components/shared/ResourceEmptyState.vue
Normal file
130
web/src/components/shared/ResourceEmptyState.vue
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="resource-empty-state"
|
||||||
|
:class="{
|
||||||
|
'resource-empty-state--compact': size === 'compact',
|
||||||
|
'resource-empty-state--full-height': fullHeight
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div v-if="icon" class="resource-empty-state__icon" aria-hidden="true">
|
||||||
|
<component :is="icon" :size="iconSize" :stroke-width="1.8" />
|
||||||
|
</div>
|
||||||
|
<h3 class="resource-empty-state__title">{{ title }}</h3>
|
||||||
|
<p v-if="description" class="resource-empty-state__description">{{ description }}</p>
|
||||||
|
<div v-if="$slots.actions" class="resource-empty-state__actions">
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { Inbox } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: { type: String, required: true },
|
||||||
|
description: { type: String, default: '' },
|
||||||
|
icon: { type: [Object, Function, String], default: () => Inbox },
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'default',
|
||||||
|
validator: (value) => ['default', 'compact'].includes(value)
|
||||||
|
},
|
||||||
|
fullHeight: { type: Boolean, default: false }
|
||||||
|
})
|
||||||
|
|
||||||
|
const iconSize = computed(() => (props.size === 'compact' ? 22 : 24))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.resource-empty-state {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 300px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 72px 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&--compact {
|
||||||
|
min-height: 240px;
|
||||||
|
padding: 48px 16px;
|
||||||
|
|
||||||
|
.resource-empty-state__icon {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-empty-state__title {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--full-height {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 32px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
display: flex;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid var(--gray-150);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--main-30);
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--gray-900);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.35;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
max-width: 360px;
|
||||||
|
margin: 8px 0 0;
|
||||||
|
color: var(--gray-600);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.resource-empty-state {
|
||||||
|
min-height: 260px;
|
||||||
|
padding: 56px 16px;
|
||||||
|
|
||||||
|
&--compact {
|
||||||
|
min-height: 220px;
|
||||||
|
padding: 40px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--full-height {
|
||||||
|
min-height: 0;
|
||||||
|
padding: 28px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -27,10 +27,11 @@
|
|||||||
<template #actions>
|
<template #actions>
|
||||||
<a-button
|
<a-button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
class="lucide-icon-btn"
|
||||||
:disabled="!kbTypes.length"
|
:disabled="!kbTypes.length"
|
||||||
@click="state.openNewDatabaseModel = true"
|
@click="state.openNewDatabaseModel = true"
|
||||||
>
|
>
|
||||||
<PlusOutlined /> 新建知识库
|
<Plus :size="16" /> 新建知识库
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
</PageShoulder>
|
</PageShoulder>
|
||||||
@ -182,21 +183,27 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 空状态显示 -->
|
<!-- 空状态显示 -->
|
||||||
<div v-else-if="!databases || databases.length === 0" class="empty-state">
|
<ResourceEmptyState
|
||||||
<h3 class="empty-title">暂无知识库</h3>
|
v-else-if="!databases || databases.length === 0"
|
||||||
<p class="empty-description">创建您的第一个知识库,开始管理文档和知识</p>
|
title="暂无知识库"
|
||||||
<a-button
|
description="创建知识库后,可以上传文件并配置检索、图谱和评估能力。"
|
||||||
type="primary"
|
:icon="getKbTypeIcon('milvus')"
|
||||||
size="large"
|
>
|
||||||
:disabled="!kbTypes.length"
|
<template #actions>
|
||||||
@click="state.openNewDatabaseModel = true"
|
<a-button
|
||||||
>
|
type="primary"
|
||||||
<template #icon>
|
size="large"
|
||||||
<PlusOutlined />
|
class="lucide-icon-btn"
|
||||||
</template>
|
:disabled="!kbTypes.length"
|
||||||
创建知识库
|
@click="state.openNewDatabaseModel = true"
|
||||||
</a-button>
|
>
|
||||||
</div>
|
<template #icon>
|
||||||
|
<Plus :size="16" />
|
||||||
|
</template>
|
||||||
|
创建知识库
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</ResourceEmptyState>
|
||||||
|
|
||||||
<!-- 数据库列表 -->
|
<!-- 数据库列表 -->
|
||||||
<ExtensionCardGrid v-else>
|
<ExtensionCardGrid v-else>
|
||||||
@ -224,11 +231,13 @@ import { useRouter, useRoute } from 'vue-router'
|
|||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useConfigStore } from '@/stores/config'
|
import { useConfigStore } from '@/stores/config'
|
||||||
import { useDatabaseStore } from '@/stores/database'
|
import { useDatabaseStore } from '@/stores/database'
|
||||||
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons-vue'
|
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
|
||||||
|
import { Plus } from 'lucide-vue-next'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { typeApi } from '@/apis/knowledge_api'
|
import { typeApi } from '@/apis/knowledge_api'
|
||||||
import PageHeader from '@/components/shared/PageHeader.vue'
|
import PageHeader from '@/components/shared/PageHeader.vue'
|
||||||
import PageShoulder from '@/components/shared/PageShoulder.vue'
|
import PageShoulder from '@/components/shared/PageShoulder.vue'
|
||||||
|
import ResourceEmptyState from '@/components/shared/ResourceEmptyState.vue'
|
||||||
import EmbeddingModelSelector from '@/components/EmbeddingModelSelector.vue'
|
import EmbeddingModelSelector from '@/components/EmbeddingModelSelector.vue'
|
||||||
import ShareConfigForm from '@/components/ShareConfigForm.vue'
|
import ShareConfigForm from '@/components/ShareConfigForm.vue'
|
||||||
import ExtensionCardGrid from '@/components/extensions/ExtensionCardGrid.vue'
|
import ExtensionCardGrid from '@/components/extensions/ExtensionCardGrid.vue'
|
||||||
@ -736,38 +745,6 @@ defineExpose({
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 100px 20px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.empty-title {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--gray-900);
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-description {
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--gray-600);
|
|
||||||
margin: 0 0 32px 0;
|
|
||||||
line-height: 1.5;
|
|
||||||
max-width: 320px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-btn {
|
|
||||||
height: 44px;
|
|
||||||
padding: 0 24px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
.loading-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user