feat: implement knowledge sources admin page
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 9s
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 9s
This commit is contained in:
parent
de6b7e9b65
commit
f6f9ab2ef0
@ -56,6 +56,7 @@ const UserDiagnosePage = lazy(() => import('./pages/learning/DataPages').then(m
|
|||||||
const MaterialDiagnosePage = lazy(() => import('./pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage })))
|
const MaterialDiagnosePage = lazy(() => import('./pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage })))
|
||||||
const LearningReplay = lazy(() => import('./pages/learning/ReplayPage'))
|
const LearningReplay = lazy(() => import('./pages/learning/ReplayPage'))
|
||||||
const BackupAdmin = lazy(() => import('./pages/BackupAdmin'))
|
const BackupAdmin = lazy(() => import('./pages/BackupAdmin'))
|
||||||
|
const KnowledgeSourcesPage = lazy(() => import('./pages/KnowledgeSources'))
|
||||||
const ReportingAdmin = lazy(() => import('./pages/ReportingAdmin'))
|
const ReportingAdmin = lazy(() => import('./pages/ReportingAdmin'))
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
@ -120,7 +121,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="knowledge/bases" element={<Suspense fallback={<PageLoading />}><KnowledgeBasesPage /></Suspense>} />
|
<Route path="knowledge/bases" element={<Suspense fallback={<PageLoading />}><KnowledgeBasesPage /></Suspense>} />
|
||||||
<Route path="knowledge/sources" element={<Placeholder title="知识源列表" />} />
|
<Route path="knowledge/sources" element={<Suspense fallback={<PageLoading />}><KnowledgeSourcesPage /></Suspense>} />
|
||||||
<Route path="knowledge/ops" element={<Suspense fallback={<PageLoading />}><KnowledgeOpsPage /></Suspense>} />
|
<Route path="knowledge/ops" element={<Suspense fallback={<PageLoading />}><KnowledgeOpsPage /></Suspense>} />
|
||||||
<Route path="chat-logs" element={<Suspense fallback={<PageLoading />}><ChatLogsPage /></Suspense>} />
|
<Route path="chat-logs" element={<Suspense fallback={<PageLoading />}><ChatLogsPage /></Suspense>} />
|
||||||
<Route path="imports" element={<Suspense fallback={<PageLoading />}><ImportMonitorPage /></Suspense>} />
|
<Route path="imports" element={<Suspense fallback={<PageLoading />}><ImportMonitorPage /></Suspense>} />
|
||||||
|
|||||||
88
src/pages/KnowledgeSources.tsx
Normal file
88
src/pages/KnowledgeSources.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { Table, Tag, Space, Input, Select, Typography } from 'antd'
|
||||||
|
import { SearchOutlined } from '@ant-design/icons'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { api } from '@/services/http-client'
|
||||||
|
import { parseStatusLabels } from '@/constants/labels'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
const { Title } = Typography
|
||||||
|
|
||||||
|
const mimeLabels: Record<string, string> = {
|
||||||
|
'text/plain': '文本', 'text/markdown': 'Markdown',
|
||||||
|
'application/pdf': 'PDF', 'application/epub+zip': 'EPUB',
|
||||||
|
'image/jpeg': '图片', 'image/png': '图片', 'image/webp': '图片',
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexStatusLabels: Record<string, string> = {
|
||||||
|
pending: '待索引', indexing: '索引中', completed: '已索引', failed: '索引失败',
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseStatusColors: Record<string, string> = {
|
||||||
|
pending: 'default', processing: 'processing', completed: 'green', failed: 'red',
|
||||||
|
}
|
||||||
|
const indexStatusColors: Record<string, string> = {
|
||||||
|
pending: 'default', indexing: 'processing', completed: 'green', failed: 'red',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function KnowledgeSourcesPage() {
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [kbFilter, setKbFilter] = useState<string>()
|
||||||
|
const [statusFilter, setStatusFilter] = useState<string>()
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
|
||||||
|
const { data, isLoading } = useQuery({
|
||||||
|
queryKey: ['admin', 'knowledge-sources', page, kbFilter, statusFilter, search],
|
||||||
|
queryFn: () => {
|
||||||
|
const p = new URLSearchParams({ page: String(page), limit: '20' })
|
||||||
|
if (kbFilter) p.set('knowledgeBaseId', kbFilter)
|
||||||
|
if (statusFilter) p.set('parseStatus', statusFilter)
|
||||||
|
if (search) p.set('search', search)
|
||||||
|
return api.get<any>(`/admin-api/knowledge-bases/all-sources?${p.toString()}`)
|
||||||
|
},
|
||||||
|
staleTime: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// KB list for filter dropdown
|
||||||
|
const { data: kbList } = useQuery({
|
||||||
|
queryKey: ['admin', 'kb-list'],
|
||||||
|
queryFn: () => api.get<any[]>('/admin-api/knowledge-bases?limit=200').then(d => d?.items || []),
|
||||||
|
staleTime: 60_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: '文件名', dataIndex: 'originalFilename', width: 220, ellipsis: true, render: (v: string, r: any) => v || r.title || '-' },
|
||||||
|
{ title: '知识库', dataIndex: 'kbName', width: 140, ellipsis: true, render: (v: string, r: any) => v || r.knowledgeBaseId },
|
||||||
|
{ title: '类型', dataIndex: 'mimeType', width: 80, render: (v: string) => mimeLabels[v] || v?.split('/').pop() || '-' },
|
||||||
|
{ title: '大小', dataIndex: 'sizeBytes', width: 80, render: (v: number) => v ? (v / 1024).toFixed(1) + 'KB' : '-' },
|
||||||
|
{ title: '字数', dataIndex: 'textLength', width: 70, render: (v: number) => v || '-' },
|
||||||
|
{
|
||||||
|
title: '解析', dataIndex: 'parseStatus', width: 90,
|
||||||
|
render: (v: string) => <Tag color={parseStatusColors[v] || 'default'}>{parseStatusLabels[v] || v}</Tag>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '索引', dataIndex: 'indexStatus', width: 90,
|
||||||
|
render: (v: string) => <Tag color={indexStatusColors[v] || 'default'}>{indexStatusLabels[v] || v}</Tag>,
|
||||||
|
},
|
||||||
|
{ title: '上传时间', dataIndex: 'createdAt', width: 140, render: (d: string) => d ? dayjs(d).format('MM-DD HH:mm') : '-' },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Title level={4}>知识源列表</Title>
|
||||||
|
<Space style={{ marginBottom: 16 }} wrap>
|
||||||
|
<Input prefix={<SearchOutlined />} placeholder="搜索文件名" allowClear value={search}
|
||||||
|
onChange={e => { setSearch(e.target.value); setPage(1) }} style={{ width: 200 }} />
|
||||||
|
<Select placeholder="知识库筛选" allowClear value={kbFilter}
|
||||||
|
onChange={v => { setKbFilter(v); setPage(1) }} style={{ width: 160 }}
|
||||||
|
options={(kbList || []).map((kb: any) => ({ value: kb.id, label: kb.title || kb.name || kb.id }))} />
|
||||||
|
<Select placeholder="解析状态" allowClear value={statusFilter}
|
||||||
|
onChange={v => { setStatusFilter(v); setPage(1) }} style={{ width: 130 }}
|
||||||
|
options={Object.entries(parseStatusLabels).map(([v, l]) => ({ value: v, label: l }))} />
|
||||||
|
</Space>
|
||||||
|
<Table dataSource={data?.items || []} columns={columns} rowKey="id" loading={isLoading}
|
||||||
|
pagination={{ current: page, defaultPageSize: 20, total: data?.total || 0, onChange: setPage }}
|
||||||
|
size="small" scroll={{ x: 1000 }} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ const AnomalyPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({
|
|||||||
const UserTimelinePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserTimelinePage })))
|
const UserTimelinePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserTimelinePage })))
|
||||||
const UserDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserDiagnosePage })))
|
const UserDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserDiagnosePage })))
|
||||||
const MaterialDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage })))
|
const MaterialDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage })))
|
||||||
|
const KnowledgeSourcesPage = lazy(() => import('@/pages/KnowledgeSources'))
|
||||||
const LearningReplay = lazy(() => import('@/pages/learning/ReplayPage'))
|
const LearningReplay = lazy(() => import('@/pages/learning/ReplayPage'))
|
||||||
|
|
||||||
export interface RouteConfig {
|
export interface RouteConfig {
|
||||||
@ -31,7 +32,7 @@ export const routeConfig: RouteConfig[] = [
|
|||||||
{ path: '/users/members', title: '普通用户', element: MemberManagement },
|
{ path: '/users/members', title: '普通用户', element: MemberManagement },
|
||||||
{ path: '/membership', title: '会员与额度', element: UserManagement, requiredRole: 'ADMIN' },
|
{ path: '/membership', title: '会员与额度', element: UserManagement, requiredRole: 'ADMIN' },
|
||||||
{ path: '/knowledge/bases', title: '知识库列表', element: UserManagement },
|
{ path: '/knowledge/bases', title: '知识库列表', element: UserManagement },
|
||||||
{ path: '/knowledge/sources', title: '知识源列表', element: UserManagement },
|
{ path: '/knowledge/sources', title: '知识源列表', element: KnowledgeSourcesPage },
|
||||||
{ path: '/imports', title: '文档导入', element: UserManagement },
|
{ path: '/imports', title: '文档导入', element: UserManagement },
|
||||||
{ path: '/ai-costs', title: 'AI 调用与成本', element: UserManagement },
|
{ path: '/ai-costs', title: 'AI 调用与成本', element: UserManagement },
|
||||||
{ path: '/files', title: '文件与 COS', element: UserManagement },
|
{ path: '/files', title: '文件与 COS', element: UserManagement },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user