diff --git a/src/App.tsx b/src/App.tsx
index f0b156e..cf83898 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -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 LearningReplay = lazy(() => import('./pages/learning/ReplayPage'))
const BackupAdmin = lazy(() => import('./pages/BackupAdmin'))
+const KnowledgeSourcesPage = lazy(() => import('./pages/KnowledgeSources'))
const ReportingAdmin = lazy(() => import('./pages/ReportingAdmin'))
const queryClient = new QueryClient()
@@ -120,7 +121,7 @@ function App() {
}
/>
}>} />
- } />
+ }>} />
}>} />
}>} />
}>} />
diff --git a/src/pages/KnowledgeSources.tsx b/src/pages/KnowledgeSources.tsx
new file mode 100644
index 0000000..85e59cd
--- /dev/null
+++ b/src/pages/KnowledgeSources.tsx
@@ -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 = {
+ 'text/plain': '文本', 'text/markdown': 'Markdown',
+ 'application/pdf': 'PDF', 'application/epub+zip': 'EPUB',
+ 'image/jpeg': '图片', 'image/png': '图片', 'image/webp': '图片',
+}
+
+const indexStatusLabels: Record = {
+ pending: '待索引', indexing: '索引中', completed: '已索引', failed: '索引失败',
+}
+
+const parseStatusColors: Record = {
+ pending: 'default', processing: 'processing', completed: 'green', failed: 'red',
+}
+const indexStatusColors: Record = {
+ pending: 'default', indexing: 'processing', completed: 'green', failed: 'red',
+}
+
+export default function KnowledgeSourcesPage() {
+ const [page, setPage] = useState(1)
+ const [kbFilter, setKbFilter] = useState()
+ const [statusFilter, setStatusFilter] = useState()
+ 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(`/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('/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) => {parseStatusLabels[v] || v},
+ },
+ {
+ title: '索引', dataIndex: 'indexStatus', width: 90,
+ render: (v: string) => {indexStatusLabels[v] || v},
+ },
+ { title: '上传时间', dataIndex: 'createdAt', width: 140, render: (d: string) => d ? dayjs(d).format('MM-DD HH:mm') : '-' },
+ ]
+
+ return (
+
+
知识源列表
+
+ } placeholder="搜索文件名" allowClear value={search}
+ onChange={e => { setSearch(e.target.value); setPage(1) }} style={{ width: 200 }} />
+
+
+
+ )
+}
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 9150b97..485dd47 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -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 UserDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserDiagnosePage })))
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'))
export interface RouteConfig {
@@ -31,7 +32,7 @@ export const routeConfig: RouteConfig[] = [
{ path: '/users/members', title: '普通用户', element: MemberManagement },
{ path: '/membership', title: '会员与额度', element: UserManagement, requiredRole: 'ADMIN' },
{ path: '/knowledge/bases', title: '知识库列表', element: UserManagement },
- { path: '/knowledge/sources', title: '知识源列表', element: UserManagement },
+ { path: '/knowledge/sources', title: '知识源列表', element: KnowledgeSourcesPage },
{ path: '/imports', title: '文档导入', element: UserManagement },
{ path: '/ai-costs', title: 'AI 调用与成本', element: UserManagement },
{ path: '/files', title: '文件与 COS', element: UserManagement },