feat: M2-08 admin — KnowledgeOps page (candidates, chunks, RAG debug)
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 9s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
237913468c
commit
1c864f0de1
@ -30,6 +30,7 @@ const Dashboard = lazy(() => import('./pages/Dashboard'))
|
|||||||
const UserManagement = lazy(() => import('./pages/UserManagement'))
|
const UserManagement = lazy(() => import('./pages/UserManagement'))
|
||||||
const MemberManagement = lazy(() => import('./pages/MemberManagement'))
|
const MemberManagement = lazy(() => import('./pages/MemberManagement'))
|
||||||
const ImportMonitorPage = lazy(() => import('./pages/ImportMonitor'))
|
const ImportMonitorPage = lazy(() => import('./pages/ImportMonitor'))
|
||||||
|
const KnowledgeOpsPage = lazy(() => import('./pages/KnowledgeOps'))
|
||||||
const HermesSettings = lazy(() => import('./pages/HermesSettings'))
|
const HermesSettings = lazy(() => import('./pages/HermesSettings'))
|
||||||
const TaskAssistant = lazy(() => import('./pages/TaskAssistant'))
|
const TaskAssistant = lazy(() => import('./pages/TaskAssistant'))
|
||||||
const Placeholder = lazy(() => import('./pages/Placeholder'))
|
const Placeholder = lazy(() => import('./pages/Placeholder'))
|
||||||
@ -100,6 +101,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={<Placeholder title="知识源列表" />} />
|
||||||
|
<Route path="knowledge/ops" element={<Suspense fallback={<PageLoading />}><KnowledgeOpsPage /></Suspense>} />
|
||||||
<Route path="imports" element={<Suspense fallback={<PageLoading />}><ImportMonitorPage /></Suspense>} />
|
<Route path="imports" element={<Suspense fallback={<PageLoading />}><ImportMonitorPage /></Suspense>} />
|
||||||
<Route path="files" element={<Suspense fallback={<PageLoading />}><FilesAdminPage /></Suspense>} />
|
<Route path="files" element={<Suspense fallback={<PageLoading />}><FilesAdminPage /></Suspense>} />
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export const adminMenuItems: AdminMenuItem[] = [
|
|||||||
{ path: '/knowledge', name: '知识库管理', icon: <BookOutlined />, children: [
|
{ path: '/knowledge', name: '知识库管理', icon: <BookOutlined />, children: [
|
||||||
{ path: '/knowledge/bases', name: '知识库列表' },
|
{ path: '/knowledge/bases', name: '知识库列表' },
|
||||||
{ path: '/knowledge/sources', name: '知识源列表' },
|
{ path: '/knowledge/sources', name: '知识源列表' },
|
||||||
|
{ path: '/knowledge/ops', name: '知识运维' },
|
||||||
]},
|
]},
|
||||||
{ path: '/imports', name: '文档导入', icon: <ImportOutlined /> },
|
{ path: '/imports', name: '文档导入', icon: <ImportOutlined /> },
|
||||||
{ path: '/files', name: '文件与 COS', icon: <FileOutlined /> },
|
{ path: '/files', name: '文件与 COS', icon: <FileOutlined /> },
|
||||||
|
|||||||
90
src/pages/KnowledgeOps.tsx
Normal file
90
src/pages/KnowledgeOps.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import { Table, Tag, Typography, Button, App, Tabs, Input, Card, Space } from 'antd'
|
||||||
|
import { ReloadOutlined, SearchOutlined, ExperimentOutlined } from '@ant-design/icons'
|
||||||
|
import { api } from '@/services/http-client'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
const { Title, Text } = Typography
|
||||||
|
|
||||||
|
export default function KnowledgeOpsPage() {
|
||||||
|
const qc = useQueryClient()
|
||||||
|
const { message } = App.useApp()
|
||||||
|
const [sourceId, setSourceId] = useState('')
|
||||||
|
const [debugQuery, setDebugQuery] = useState('')
|
||||||
|
|
||||||
|
const { data: candidates } = useQuery({
|
||||||
|
queryKey: ['ops', 'candidates'],
|
||||||
|
queryFn: (): Promise<any> => api.get('/admin-api/knowledge-bases/candidates?limit=50'),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { data: chunks } = useQuery({
|
||||||
|
queryKey: ['ops', 'chunks', sourceId],
|
||||||
|
queryFn: (): Promise<any> => api.get(`/admin-api/knowledge-bases/chunks?sourceId=${sourceId}&limit=50`),
|
||||||
|
enabled: !!sourceId,
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleDebugSearch = async () => {
|
||||||
|
if (!debugQuery) return
|
||||||
|
const res: any = await api.post('/admin-api/vector/debug-search', { query: debugQuery })
|
||||||
|
message.info(JSON.stringify(res.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidateCols = [
|
||||||
|
{ title: '标题', dataIndex: 'title', width: 200, ellipsis: true },
|
||||||
|
{ title: '用户', dataIndex: 'userId', width: 120, ellipsis: true },
|
||||||
|
{ title: '状态', dataIndex: 'status', width: 80, render: (s: string) => <Tag color={s==='PENDING'?'gold':s==='ACCEPTED'?'green':'red'}>{s}</Tag> },
|
||||||
|
{ title: '置信度', dataIndex: 'confidence', width: 80, align: 'center' as const, render: (v: any) => v ? `${(Number(v)*100).toFixed(0)}%` : '-' },
|
||||||
|
{ title: '难度', dataIndex: 'difficulty', width: 70 },
|
||||||
|
{ title: '时间', dataIndex: 'createdAt', width: 130, render: (d: string) => dayjs(d).format('MM-DD HH:mm') },
|
||||||
|
]
|
||||||
|
|
||||||
|
const chunkCols = [
|
||||||
|
{ title: '#', dataIndex: 'chunkIndex', width: 50 },
|
||||||
|
{ title: '内容', dataIndex: 'content', ellipsis: true, render: (v: string) => <Text style={{ fontSize: 12 }}>{v?.slice(0, 150)}</Text> },
|
||||||
|
{ title: '页', dataIndex: 'pageNumber', width: 50 },
|
||||||
|
{ title: '章节', dataIndex: 'sectionTitle', width: 150, ellipsis: true },
|
||||||
|
{ title: 'Tokens', dataIndex: 'tokenCount', width: 70, align: 'center' as const },
|
||||||
|
{ title: 'Embedding', dataIndex: 'embeddingStatus', width: 80, render: (s: string) => <Tag color={s==='done'?'green':'default'}>{s}</Tag> },
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
|
<Title level={5} style={{ margin: 0 }}><ExperimentOutlined /> 知识运维</Title>
|
||||||
|
<Button icon={<ReloadOutlined />} onClick={() => qc.invalidateQueries({ queryKey: ['ops'] })}>刷新</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs items={[
|
||||||
|
{
|
||||||
|
key: 'candidates', label: '候选巡检',
|
||||||
|
children: <Table dataSource={candidates || []} columns={candidateCols} rowKey="id" pagination={{ pageSize: 20 }} size="small" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'chunks', label: 'Chunk 管理',
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Space style={{ marginBottom: 16 }}>
|
||||||
|
<Input.Search placeholder="输入 Source ID" value={sourceId} onChange={e => setSourceId(e.target.value)} onSearch={v => setSourceId(v)} enterButton={<SearchOutlined />} style={{ width: 300 }} />
|
||||||
|
</Space>
|
||||||
|
{sourceId && <Table dataSource={chunks || []} columns={chunkCols} rowKey="id" pagination={{ pageSize: 20 }} size="small" />}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'rag-debug', label: 'RAG 调试',
|
||||||
|
children: (
|
||||||
|
<Card size="small" title="检索调试">
|
||||||
|
<Space>
|
||||||
|
<Input.Search placeholder="输入查询文本" value={debugQuery} onChange={e => setDebugQuery(e.target.value)} onSearch={handleDebugSearch} enterButton="检索" style={{ width: 400 }} />
|
||||||
|
</Space>
|
||||||
|
<div style={{ marginTop: 8 }}>
|
||||||
|
<Text type="secondary">输入查询文本后,将显示检索参数和结果(完整 RAG 管道在 M3 阶段完善)。</Text>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user