feat(rag-store): 补充知识库文档概览接口

This commit is contained in:
zhiye.sun
2026-05-21 15:35:45 +08:00
parent 541c3ff455
commit 8532628171
11 changed files with 484 additions and 17 deletions

View File

@@ -0,0 +1,31 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
getRagStoreDocumentOverview,
getRagStoreOverview,
listRagStores,
} from '../ragStores';
import { get, post } from '../request';
vi.mock('../request', () => ({
get: vi.fn(),
post: vi.fn(),
}));
describe('rag stores api', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('loads overview and document overview endpoints', () => {
getRagStoreOverview();
getRagStoreDocumentOverview('1001');
listRagStores();
expect(get).toHaveBeenCalledWith('/rag/store/overview');
expect(get).toHaveBeenCalledWith('/rag/store/documentOverview', {
params: { storeId: '1001' },
});
expect(post).toHaveBeenCalledWith('/rag/store/list');
});
});

View File

@@ -11,6 +11,23 @@ export interface RagStore {
updateTime?: string | null;
}
export interface RagStoreOverview {
totalStores: number;
totalDocuments: number;
totalChunks?: number | null;
retrievableStores: number;
}
export interface RagStoreDocumentOverview {
storeId: string;
storeName?: string | null;
documentCount: number;
enabledDocumentCount: number;
parsedDocumentCount: number;
indexedDocumentCount: number;
lastUploadTime?: string | null;
}
export interface RagStoreQueryRequest {
storeCode?: string;
storeName?: string;
@@ -33,6 +50,16 @@ export function getRagStoreById(id: string) {
});
}
export function getRagStoreOverview() {
return get<RagStoreOverview>('/rag/store/overview');
}
export function getRagStoreDocumentOverview(storeId: string) {
return get<RagStoreDocumentOverview>('/rag/store/documentOverview', {
params: { storeId },
});
}
export function saveRagStore(data: RagStoreSaveRequest) {
return post<boolean, RagStoreSaveRequest>('/rag/store/save', data);
}

View File

@@ -6,8 +6,12 @@ import { computed, onMounted, reactive, ref } from 'vue';
import {
deleteRagStore,
getRagStoreById,
getRagStoreDocumentOverview,
getRagStoreOverview,
queryRagStores,
saveRagStore,
type RagStoreDocumentOverview,
type RagStoreOverview,
type RagStore,
} from '@/api/ragStores';
@@ -19,6 +23,8 @@ const submitting = ref(false);
const storeRows = ref<RagStore[]>([]);
const activeStoreId = ref<string | null>(null);
const activeStore = ref<RagStore | null>(null);
const pageOverview = ref<RagStoreOverview | null>(null);
const activeStoreDocumentOverview = ref<RagStoreDocumentOverview | null>(null);
const queryForm = reactive({
storeName: '',
@@ -45,17 +51,30 @@ const editForm = reactive({
});
const overviewCards = computed(() => {
const totalStores = storeRows.value.length;
const retrievableStores = storeRows.value.filter((row) => row.status === '启用').length;
const totalStores = pageOverview.value?.totalStores ?? storeRows.value.length;
const totalDocuments = pageOverview.value?.totalDocuments ?? '-';
const totalChunks = pageOverview.value?.totalChunks ?? '-';
const retrievableStores =
pageOverview.value?.retrievableStores
?? storeRows.value.filter((row) => row.status === '启用').length;
return [
{ label: '知识库总数', value: totalStores, hint: '当前已登记知识库' },
{ label: '文档总数', value: '-', hint: '待文档统计接口补充' },
{ label: '切片总数', value: '-', hint: '待切片统计接口补充' },
{ label: '可检索知识库数', value: retrievableStores, hint: '当前按启用状态暂代统计' },
{ label: '文档总数', value: totalDocuments, hint: '当前知识库已登记文档总量' },
{ label: '切片总数', value: totalChunks, hint: '当前未接入切片表,待后续能力补充' },
{ label: '可检索知识库数', value: retrievableStores, hint: '当前按启用状态统计' },
];
});
async function loadOverview() {
try {
const response = await getRagStoreOverview();
pageOverview.value = response.data ?? null;
} catch {
pageOverview.value = null;
}
}
async function loadStores(preferredStoreId?: string | null) {
loading.value = true;
try {
@@ -67,6 +86,7 @@ async function loadStores(preferredStoreId?: string | null) {
if (storeRows.value.length === 0) {
activeStoreId.value = null;
activeStore.value = null;
activeStoreDocumentOverview.value = null;
return;
}
@@ -80,6 +100,7 @@ async function loadStores(preferredStoreId?: string | null) {
if (!targetId) {
activeStoreId.value = null;
activeStore.value = null;
activeStoreDocumentOverview.value = null;
return;
}
await selectStore(targetId);
@@ -92,8 +113,12 @@ async function selectStore(storeId: string) {
activeStoreId.value = storeId;
detailLoading.value = true;
try {
const response = await getRagStoreById(storeId);
activeStore.value = response.data ?? null;
const [storeResponse, documentOverviewResponse] = await Promise.all([
getRagStoreById(storeId),
getRagStoreDocumentOverview(storeId),
]);
activeStore.value = storeResponse.data ?? null;
activeStoreDocumentOverview.value = documentOverviewResponse.data ?? null;
} finally {
detailLoading.value = false;
}
@@ -203,6 +228,7 @@ function getStatusTagType(status?: string | null) {
}
onMounted(() => {
loadOverview();
loadStores();
});
</script>
@@ -327,9 +353,25 @@ onMounted(() => {
<article class="detail-card detail-card--placeholder">
<div class="detail-card__header">
<h4>文档概览</h4>
<span>下一批接口补充</span>
<span>已对接后端聚合接口</span>
</div>
<el-empty description="文档数量、切片数量、最近上传时间待后端聚合接口补充" />
<el-descriptions :column="2" border>
<el-descriptions-item label="文档总数">
{{ activeStoreDocumentOverview?.documentCount ?? 0 }}
</el-descriptions-item>
<el-descriptions-item label="启用文档数">
{{ activeStoreDocumentOverview?.enabledDocumentCount ?? 0 }}
</el-descriptions-item>
<el-descriptions-item label="已解析文档数">
{{ activeStoreDocumentOverview?.parsedDocumentCount ?? 0 }}
</el-descriptions-item>
<el-descriptions-item label="已索引文档数">
{{ activeStoreDocumentOverview?.indexedDocumentCount ?? 0 }}
</el-descriptions-item>
<el-descriptions-item label="最近上传时间" :span="2">
{{ activeStoreDocumentOverview?.lastUploadTime || '-' }}
</el-descriptions-item>
</el-descriptions>
</article>
<article class="detail-card detail-card--placeholder">

View File

@@ -3,9 +3,27 @@ import ElementPlus from 'element-plus';
import { describe, expect, it, vi } from 'vitest';
import RagStoresPage from '../RagStoresPage.vue';
import { getRagStoreById, queryRagStores, saveRagStore } from '@/api/ragStores';
import {
getRagStoreById,
getRagStoreDocumentOverview,
getRagStoreOverview,
queryRagStores,
saveRagStore,
} from '@/api/ragStores';
vi.mock('@/api/ragStores', () => ({
getRagStoreOverview: vi.fn(() =>
Promise.resolve({
resultcode: '0',
message: null,
data: {
totalStores: 2,
totalDocuments: 12,
totalChunks: null,
retrievableStores: 1,
},
}),
),
queryRagStores: vi.fn((query?: { storeName?: string }) => {
const rows = [
{
@@ -60,6 +78,32 @@ vi.mock('@/api/ragStores', () => ({
},
}),
),
getRagStoreDocumentOverview: vi.fn((storeId: string) =>
Promise.resolve({
resultcode: '0',
message: null,
data:
storeId === '2'
? {
storeId: '2',
storeName: 'FAQ知识库',
documentCount: 3,
enabledDocumentCount: 1,
parsedDocumentCount: 1,
indexedDocumentCount: 1,
lastUploadTime: '2026-05-21 11:12:00',
}
: {
storeId: '1',
storeName: '产品制度库',
documentCount: 9,
enabledDocumentCount: 8,
parsedDocumentCount: 6,
indexedDocumentCount: 5,
lastUploadTime: '2026-05-21 16:40:00',
},
}),
),
saveRagStore: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: true })),
deleteRagStore: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: true })),
}));
@@ -75,10 +119,16 @@ describe('RagStoresPage', () => {
await flushPromises();
expect(wrapper.text()).toContain('知识库总数');
expect(wrapper.text()).toContain('12');
expect(wrapper.text()).toContain('产品制度库');
expect(wrapper.text()).toContain('核心制度库');
expect(wrapper.text()).toContain('文档总数');
expect(wrapper.text()).toContain('最近上传时间');
expect(wrapper.text()).toContain('2026-05-21 16:40:00');
expect(getRagStoreOverview).toHaveBeenCalled();
expect(queryRagStores).toHaveBeenCalled();
expect(getRagStoreById).toHaveBeenCalledWith('1');
expect(getRagStoreDocumentOverview).toHaveBeenCalledWith('1');
});
it('filters stores by name and updates detail when a store is selected', async () => {
@@ -103,7 +153,9 @@ describe('RagStoresPage', () => {
await flushPromises();
expect(getRagStoreById).toHaveBeenLastCalledWith('2');
expect(getRagStoreDocumentOverview).toHaveBeenLastCalledWith('2');
expect(wrapper.text()).toContain('FAQ 场景知识');
expect(wrapper.text()).toContain('3');
});
it('submits create form through backend api', async () => {