diff --git a/frontend/src/api/ragDocuments.ts b/frontend/src/api/ragDocuments.ts index be8f87b..600be32 100644 --- a/frontend/src/api/ragDocuments.ts +++ b/frontend/src/api/ragDocuments.ts @@ -65,6 +65,10 @@ export type RagChunkStrategy = (typeof RAG_CHUNK_STRATEGY)[keyof typeof RAG_CHUN export interface RagDocumentParseRequest { documentIds: string[]; +} + +export interface RagDocumentChunkRequest { + documentIds: string[]; chunkStrategy: RagChunkStrategy; chunkSize?: number; chunkOverlap?: number; @@ -123,3 +127,7 @@ export function batchUploadRagDocuments(data: RagDocumentBatchUploadRequest) { export function parseRagDocuments(data: RagDocumentParseRequest) { return post('/rag/documents/parse', data); } + +export function chunkRagDocuments(data: RagDocumentChunkRequest) { + return post('/rag/documents/chunk', data); +} diff --git a/frontend/src/components/rag/chunk/RagChunkTaskBoard.vue b/frontend/src/components/rag/chunk/RagChunkTaskBoard.vue new file mode 100644 index 0000000..8a46b61 --- /dev/null +++ b/frontend/src/components/rag/chunk/RagChunkTaskBoard.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/frontend/src/components/rag/document/RagFlowOverview.vue b/frontend/src/components/rag/document/RagFlowOverview.vue new file mode 100644 index 0000000..de3d336 --- /dev/null +++ b/frontend/src/components/rag/document/RagFlowOverview.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue index 2f2af95..fa5ebed 100644 --- a/frontend/src/layouts/AdminLayout.vue +++ b/frontend/src/layouts/AdminLayout.vue @@ -2,18 +2,18 @@ import { Box, Collection, - DataBoard, Document, - Files, Grid, + Histogram, + List, } from '@element-plus/icons-vue'; const menuItems = [ - { path: '/dashboard', label: '工作台', icon: DataBoard }, { path: '/system/enums', label: '系统枚举', icon: Grid }, - { path: '/system/attachments', label: '附件管理', icon: Files }, { path: '/rag/stores', label: '知识库', icon: Collection }, + { path: '/rag/workbench', label: 'RAG工作台', icon: Histogram }, { path: '/rag/documents', label: '知识文档', icon: Document }, + { path: '/rag/tasks/chunk', label: '切片任务', icon: List }, ]; diff --git a/frontend/src/pages/common/NotFoundPage.vue b/frontend/src/pages/common/NotFoundPage.vue index c35d5d2..6631ea6 100644 --- a/frontend/src/pages/common/NotFoundPage.vue +++ b/frontend/src/pages/common/NotFoundPage.vue @@ -1,6 +1,6 @@ diff --git a/frontend/src/pages/dashboard/DashboardPage.vue b/frontend/src/pages/dashboard/DashboardPage.vue deleted file mode 100644 index ff1f209..0000000 --- a/frontend/src/pages/dashboard/DashboardPage.vue +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/frontend/src/pages/rag/RagDocumentsPage.vue b/frontend/src/pages/rag/RagDocumentsPage.vue index 5056914..ee408c0 100644 --- a/frontend/src/pages/rag/RagDocumentsPage.vue +++ b/frontend/src/pages/rag/RagDocumentsPage.vue @@ -1,639 +1,7 @@ - - diff --git a/frontend/src/pages/rag/__tests__/RagDocumentsPage.spec.ts b/frontend/src/pages/rag/__tests__/RagDocumentsPage.spec.ts index 6f11c81..a83c642 100644 --- a/frontend/src/pages/rag/__tests__/RagDocumentsPage.spec.ts +++ b/frontend/src/pages/rag/__tests__/RagDocumentsPage.spec.ts @@ -3,7 +3,7 @@ import ElementPlus from 'element-plus'; import { describe, expect, it, vi } from 'vitest'; import RagDocumentsPage from '../RagDocumentsPage.vue'; -import { getRagDocumentById, parseRagDocuments, queryRagDocuments } from '@/api/ragDocuments'; +import { chunkRagDocuments, getRagDocumentById, parseRagDocuments, queryRagDocuments } from '@/api/ragDocuments'; import { queryRagStores } from '@/api/ragStores'; const routeQuery = vi.hoisted(() => ({ storeId: undefined as string | undefined })); @@ -85,6 +85,7 @@ vi.mock('@/api/ragDocuments', () => ({ saveRagDocument: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: true })), deleteRagDocument: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: true })), batchUploadRagDocuments: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: [] })), + chunkRagDocuments: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: true })), parseRagDocuments: vi.fn(() => Promise.resolve({ resultcode: '0', message: null, data: [] })), })); @@ -152,7 +153,7 @@ describe('RagDocumentsPage', () => { expect(getRagDocumentById).toHaveBeenCalledWith('22'); }); - it('opens parse dialog with chunk strategy options from row action', async () => { + it('opens chunk dialog with chunk strategy options from row action', async () => { routeQuery.storeId = undefined; const wrapper = mount(RagDocumentsPage, { global: { @@ -161,16 +162,16 @@ describe('RagDocumentsPage', () => { }); await flushPromises(); - await wrapper.get('[data-test="doc-parse-11"]').trigger('click'); + await wrapper.get('[data-test="doc-chunk-22"]').trigger('click'); await flushPromises(); - expect(wrapper.find('[data-test="document-parse-dialog"]').exists()).toBe(true); + expect(wrapper.find('[data-test="document-chunk-dialog"]').exists()).toBe(true); expect(wrapper.text()).toContain('固定长度切片'); expect(wrapper.text()).toContain('按分隔符切片'); expect(wrapper.text()).toContain('语义切片'); }); - it('submits parse request with selected chunk strategy', async () => { + it('submits chunk request with selected chunk strategy', async () => { routeQuery.storeId = undefined; const wrapper = mount(RagDocumentsPage, { global: { @@ -179,13 +180,13 @@ describe('RagDocumentsPage', () => { }); await flushPromises(); - await wrapper.get('[data-test="doc-parse-11"]').trigger('click'); + await wrapper.get('[data-test="doc-chunk-22"]').trigger('click'); await flushPromises(); - await wrapper.get('[data-test="document-parse-submit"]').trigger('click'); + await wrapper.get('[data-test="document-chunk-submit"]').trigger('click'); await flushPromises(); - expect(parseRagDocuments).toHaveBeenCalledWith({ - documentIds: ['11'], + expect(chunkRagDocuments).toHaveBeenCalledWith({ + documentIds: ['22'], chunkStrategy: 1, chunkSize: 800, chunkOverlap: 120, @@ -193,6 +194,39 @@ describe('RagDocumentsPage', () => { }); }); + it('retries parse for failed document', async () => { + routeQuery.storeId = undefined; + vi.mocked(queryRagDocuments).mockResolvedValueOnce({ + resultcode: '0', + message: null, + data: [ + { + id: '33', + storeId: '1', + attachmentId: '303', + documentTitle: '失败文档', + documentSummary: '失败摘要', + parseStatus: 'FAILED', + indexStatus: 'PENDING', + enabled: true, + remark: '失败', + createTime: '2026-05-21 11:00:00', + }, + ], + }); + const wrapper = mount(RagDocumentsPage, { + global: { + plugins: [ElementPlus], + }, + }); + + await flushPromises(); + await wrapper.get('[data-test="doc-retry-parse-33"]').trigger('click'); + await flushPromises(); + + expect(parseRagDocuments).toHaveBeenCalledWith({ documentIds: ['33'] }); + }); + it('renders reusable upload dialog with drag upload area', async () => { routeQuery.storeId = '1'; const wrapper = mount(RagDocumentsPage, { diff --git a/frontend/src/pages/rag/documents/RagDocumentsPage.vue b/frontend/src/pages/rag/documents/RagDocumentsPage.vue new file mode 100644 index 0000000..d9e1a81 --- /dev/null +++ b/frontend/src/pages/rag/documents/RagDocumentsPage.vue @@ -0,0 +1,708 @@ + + + + + diff --git a/frontend/src/pages/rag/tasks/RagChunkTasksPage.vue b/frontend/src/pages/rag/tasks/RagChunkTasksPage.vue new file mode 100644 index 0000000..7e71bb3 --- /dev/null +++ b/frontend/src/pages/rag/tasks/RagChunkTasksPage.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/frontend/src/pages/rag/workbench/RagWorkbenchPage.vue b/frontend/src/pages/rag/workbench/RagWorkbenchPage.vue new file mode 100644 index 0000000..ea1474d --- /dev/null +++ b/frontend/src/pages/rag/workbench/RagWorkbenchPage.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/frontend/src/pages/system/SystemAttachmentsPage.vue b/frontend/src/pages/system/SystemAttachmentsPage.vue deleted file mode 100644 index 97cf370..0000000 --- a/frontend/src/pages/system/SystemAttachmentsPage.vue +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/frontend/src/router/__tests__/router.spec.ts b/frontend/src/router/__tests__/router.spec.ts index 33a792b..f49dfce 100644 --- a/frontend/src/router/__tests__/router.spec.ts +++ b/frontend/src/router/__tests__/router.spec.ts @@ -7,11 +7,11 @@ describe('router', () => { const paths = routes.map((route) => route.path); expect(paths).toContain('/'); - expect(paths).toContain('/dashboard'); - expect(paths).toContain('/system/enums'); - expect(paths).toContain('/system/attachments'); expect(paths).toContain('/rag/stores'); + expect(paths).toContain('/rag/workbench'); expect(paths).toContain('/rag/documents'); + expect(paths).toContain('/rag/tasks/chunk'); + expect(paths).toContain('/system/enums'); expect(paths).toContain('/:pathMatch(.*)*'); }); }); diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 07c0f35..e15fb90 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,24 +1,18 @@ import type { RouteRecordRaw } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router'; -import DashboardPage from '@/pages/dashboard/DashboardPage.vue'; import NotFoundPage from '@/pages/common/NotFoundPage.vue'; import RagDocumentsPage from '@/pages/rag/RagDocumentsPage.vue'; import RagStoresPage from '@/pages/rag/RagStoresPage.vue'; -import SystemAttachmentsPage from '@/pages/system/SystemAttachmentsPage.vue'; +import RagChunkTasksPage from '@/pages/rag/tasks/RagChunkTasksPage.vue'; +import RagWorkbenchPage from '@/pages/rag/workbench/RagWorkbenchPage.vue'; import SystemEnumsPage from '@/pages/system/SystemEnumsPage.vue'; import AdminLayout from '@/layouts/AdminLayout.vue'; export const routes: RouteRecordRaw[] = [ { path: '/', - redirect: '/dashboard', - }, - { - path: '/dashboard', - name: 'dashboard', - component: DashboardPage, - meta: { title: '工作台' }, + redirect: '/rag/workbench', }, { path: '/system/enums', @@ -26,24 +20,30 @@ export const routes: RouteRecordRaw[] = [ component: SystemEnumsPage, meta: { title: '系统枚举' }, }, - { - path: '/system/attachments', - name: 'system-attachments', - component: SystemAttachmentsPage, - meta: { title: '附件管理' }, - }, { path: '/rag/stores', name: 'rag-stores', component: RagStoresPage, meta: { title: '知识库' }, }, + { + path: '/rag/workbench', + name: 'rag-workbench', + component: RagWorkbenchPage, + meta: { title: 'RAG工作台' }, + }, { path: '/rag/documents', name: 'rag-documents', component: RagDocumentsPage, meta: { title: '知识文档' }, }, + { + path: '/rag/tasks/chunk', + name: 'rag-chunk-tasks', + component: RagChunkTasksPage, + meta: { title: '切片任务' }, + }, { path: '/:pathMatch(.*)*', name: 'not-found', @@ -55,42 +55,42 @@ export const routes: RouteRecordRaw[] = [ const routerRoutes: RouteRecordRaw[] = [ { path: '/', - redirect: '/dashboard', + redirect: '/rag/workbench', }, { path: '/', component: AdminLayout, children: [ - { - path: 'dashboard', - name: 'dashboard', - component: DashboardPage, - meta: { title: '工作台' }, - }, { path: 'system/enums', name: 'system-enums', component: SystemEnumsPage, meta: { title: '系统枚举' }, }, - { - path: 'system/attachments', - name: 'system-attachments', - component: SystemAttachmentsPage, - meta: { title: '附件管理' }, - }, { path: 'rag/stores', name: 'rag-stores', component: RagStoresPage, meta: { title: '知识库' }, }, + { + path: 'rag/workbench', + name: 'rag-workbench', + component: RagWorkbenchPage, + meta: { title: 'RAG工作台' }, + }, { path: 'rag/documents', name: 'rag-documents', component: RagDocumentsPage, meta: { title: '知识文档' }, }, + { + path: 'rag/tasks/chunk', + name: 'rag-chunk-tasks', + component: RagChunkTasksPage, + meta: { title: '切片任务' }, + }, ], }, {