Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 10s
- HTTP 层:安装 axios,创建 lib/api-client.ts 统一拦截器 - API 层:创建 33 个 api/*.ts 文件,所有页面迁移完成 - DataPages.tsx 拆分为 10 个独立文件 - message 导入统一为 App.useApp() - 新增 StaticAntdProvider 全局消息/通知 - 全局 HTTP 错误/成功提示(notification.error/success) - learning 路由 /learning → /learning/dashboard 修复选中 bug - 学习会话页面优化(移除 ID 列、加批量删除、后端排序) - 文档导入页面重构(知识库筛选、批量重新解析) - 删除旧 service 文件 10 个(admin-api、billing-api 等) - antd App 包裹根组件 Co-Authored-By: Claude <noreply@anthropic.com>
108 lines
5.2 KiB
TypeScript
108 lines
5.2 KiB
TypeScript
import { useState } from 'react'
|
|
import { Table, Tabs, Tag, Space, Input, Typography } from 'antd'
|
|
import { SearchOutlined } from '@ant-design/icons'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
import { learningAPI } from '@/api'
|
|
import { sessionStatusLabels, sessionModeLabels } from '@/constants/labels'
|
|
|
|
const { Title } = Typography
|
|
|
|
const statusColors: Record<string, string> = { active: 'blue', completed: 'green', cancelled: 'red' }
|
|
|
|
export default function LearningData() {
|
|
const [tab, setTab] = useState('sessions')
|
|
const [search, setSearch] = useState('')
|
|
|
|
const { data: sessions, isLoading: sLoading } = useQuery({
|
|
queryKey: ['admin', 'learning-sessions', search],
|
|
queryFn: () => {
|
|
const p = new URLSearchParams()
|
|
if (search) p.set('userId', search)
|
|
return learningAPI.getSessionsRaw(p.toString())
|
|
},
|
|
enabled: tab === 'sessions',
|
|
})
|
|
|
|
const { data: analysis, isLoading: aLoading } = useQuery({
|
|
queryKey: ['admin', 'analysis-results', search],
|
|
queryFn: () => {
|
|
const p = new URLSearchParams()
|
|
if (search) p.set('userId', search)
|
|
return learningAPI.getAnalysis(p.toString())
|
|
},
|
|
enabled: tab === 'analysis',
|
|
})
|
|
|
|
const { data: aiUsage, isLoading: uLoading } = useQuery({
|
|
queryKey: ['admin', 'ai-usage', search],
|
|
queryFn: () => {
|
|
const p = new URLSearchParams()
|
|
if (search) p.set('userId', search)
|
|
return learningAPI.getAiUsage(p.toString())
|
|
},
|
|
enabled: tab === 'ai-usage',
|
|
})
|
|
|
|
const sessionColumns = [
|
|
{ title: 'ID', dataIndex: 'id', width: 100, ellipsis: true },
|
|
{ title: '用户', dataIndex: 'userId', width: 100, ellipsis: true, render: (_: any, r: any) => r.userName || r.userId },
|
|
{ title: '知识库', dataIndex: 'knowledgeBaseId', width: 100, ellipsis: true, render: (_: any, r: any) => r.kbName || r.knowledgeBaseId },
|
|
{ title: '模式', dataIndex: 'mode', width: 80, render: (v: string) => sessionModeLabels[v] || v },
|
|
{ title: '状态', dataIndex: 'status', width: 80, render: (s: string) => <Tag color={statusColors[s] || 'default'}>{sessionStatusLabels[s] || s}</Tag> },
|
|
{ title: '时长(分)', dataIndex: 'durationSeconds', width: 80, render: (s: number) => s ? Math.round(s / 60) : '-' },
|
|
{ title: '开始时间', dataIndex: 'startedAt', width: 120, render: (d: string) => d ? new Date(d).toLocaleString() : '-' },
|
|
{ title: '结束时间', dataIndex: 'endedAt', width: 120, render: (d: string) => d ? new Date(d).toLocaleString() : '-' },
|
|
]
|
|
|
|
const analysisColumns = [
|
|
{ title: 'ID', dataIndex: 'id', width: 100, ellipsis: true },
|
|
{ title: '用户', dataIndex: 'userId', width: 100, ellipsis: true, render: (_: any, r: any) => r.userName || r.userId },
|
|
{ title: '摘要', dataIndex: 'summary', width: 200, ellipsis: true },
|
|
{ title: '掌握度', dataIndex: 'masteryScore', width: 80, render: (s: number | null) => s != null ? `${s}%` : '-' },
|
|
{ title: '薄弱点', dataIndex: 'weaknesses', width: 200, render: (w: string[]) => (w || []).join('、') || '-' },
|
|
{ title: '时间', dataIndex: 'createdAt', width: 120, render: (d: string) => new Date(d).toLocaleString() },
|
|
]
|
|
|
|
const aiUsageColumns = [
|
|
{ title: 'ID', dataIndex: 'id', width: 100, ellipsis: true },
|
|
{ title: '用户', dataIndex: 'userId', width: 100, ellipsis: true, render: (_: any, r: any) => r.userName || r.userId },
|
|
{ title: '模型', dataIndex: 'model', width: 140, ellipsis: true },
|
|
{ title: '服务商', dataIndex: 'provider', width: 80 },
|
|
{ title: '输入 Token', dataIndex: 'inputTokens', width: 90 },
|
|
{ title: '输出 Token', dataIndex: 'outputTokens', width: 90 },
|
|
{ title: '费用', dataIndex: 'estimatedCost', width: 70, render: (c: number) => c != null ? `¥${c}` : '-' },
|
|
{ title: '成功', dataIndex: 'success', width: 60, render: (s: boolean) => <Tag color={s ? 'green' : 'red'}>{s ? '是' : '否'}</Tag> },
|
|
{ title: '时间', dataIndex: 'createdAt', width: 120, render: (d: string) => new Date(d).toLocaleString() },
|
|
]
|
|
|
|
return (
|
|
<div>
|
|
<Title level={4}>学习数据</Title>
|
|
<Space style={{ marginBottom: 16 }}>
|
|
<Input
|
|
placeholder="按用户 ID 搜索"
|
|
prefix={<SearchOutlined />}
|
|
value={search}
|
|
onChange={e => setSearch(e.target.value)}
|
|
allowClear
|
|
style={{ width: 240 }}
|
|
/>
|
|
</Space>
|
|
<Tabs activeKey={tab} onChange={setTab} items={[
|
|
{
|
|
key: 'sessions', label: '学习会话',
|
|
children: <Table dataSource={sessions?.items || []} columns={sessionColumns} rowKey="id" loading={sLoading} pagination={{ defaultPageSize: 20, total: sessions?.total || 0 }} size="small" scroll={{ x: 900 }} />,
|
|
},
|
|
{
|
|
key: 'analysis', label: 'AI 分析结果',
|
|
children: <Table dataSource={analysis?.items || []} columns={analysisColumns} rowKey="id" loading={aLoading} pagination={{ defaultPageSize: 20, total: analysis?.total || 0 }} size="small" scroll={{ x: 900 }} />,
|
|
},
|
|
{
|
|
key: 'ai-usage', label: 'AI 调用日志',
|
|
children: <Table dataSource={aiUsage?.items || []} columns={aiUsageColumns} rowKey="id" loading={uLoading} pagination={{ defaultPageSize: 20, total: aiUsage?.total || 0 }} size="small" scroll={{ x: 1000 }} />,
|
|
},
|
|
]} />
|
|
</div>
|
|
)
|
|
}
|