fix: 修复 CI 构建 49 个 TypeScript 错误
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 10s

- routes/index.tsx: DataPages → 独立文件 import
- 导出 CostItem、CostSummary、Conversation、HealthService 类型
- ServerInfo、ProcessInfo、ServerHealth 类型补全嵌套字段
- 修复 useRef 缺初始值、string|null、unused imports 等

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-07-01 22:50:22 +08:00
parent 8663ceba66
commit f291f39049
13 changed files with 40 additions and 46 deletions

View File

@ -1,9 +1,10 @@
import { api } from '@/lib/api-client' import { api } from '@/lib/api-client'
export interface CostItem { id: string; currency: string; amount: number; [key: string]: any } export interface CostItem { id: string; name: string; currency: string; amount: number; purchaseDate?: string; expiryDate?: string; [key: string]: any }
export interface CostSummary { totalMonthly: number; totalYearly: number; totalOneTime: number; items: CostItem[]; byMonth: any[]; expiringSoon: any[] }
export const costsAPI = { export const costsAPI = {
summary() { return api.get<{ totalMonthly: number; byProvider: Record<string, number> }>('/admin-api/costs/summary') }, summary() { return api.get<CostSummary>('/admin-api/costs/summary') },
list() { return api.get<CostItem[]>('/admin-api/costs') }, list() { return api.get<CostItem[]>('/admin-api/costs') },
create(data: Omit<CostItem, 'id' | 'currency'> & { currency?: string }) { return api.post<CostItem>('/admin-api/costs', data) }, create(data: Omit<CostItem, 'id' | 'currency'> & { currency?: string }) { return api.post<CostItem>('/admin-api/costs', data) },
update(id: string, data: Partial<CostItem>) { return api.patch<CostItem>(`/admin-api/costs/${id}`, data) }, update(id: string, data: Partial<CostItem>) { return api.patch<CostItem>(`/admin-api/costs/${id}`, data) },

View File

@ -3,12 +3,12 @@ export { usersAPI } from './users'
export { dashboardAPI } from './dashboard' export { dashboardAPI } from './dashboard'
export { auditAPI } from './audit' export { auditAPI } from './audit'
export { billingAPI, type BillingInfo } from './billing' export { billingAPI, type BillingInfo } from './billing'
export { costsAPI } from './costs' export { costsAPI, type CostItem, type CostSummary } from './costs'
export { configAPI } from './config' export { configAPI } from './config'
export { conversationsAPI } from './conversations' export { conversationsAPI, type Conversation } from './conversations'
export { eventsQueueAPI } from './events-queue' export { eventsQueueAPI } from './events-queue'
export { knowledgeAPI } from './knowledge' export { knowledgeAPI } from './knowledge'
export { serversAPI } from './servers' export { serversAPI, type ServerInfo, type ProcessInfo, type ServerHealth, type HealthService } from './servers'
export { learningAPI } from './learning' export { learningAPI } from './learning'
export { aiAPI } from './ai' export { aiAPI } from './ai'

View File

@ -1,8 +1,18 @@
import { api } from '@/lib/api-client' import { api } from '@/lib/api-client'
export interface ServerInfo { hostname: string; cpu: number; memory: number; [key: string]: any } export interface ServerInfo {
export interface ServerHealth { name: string; status: string; [key: string]: any } hostname: string
export interface WorkerInfo { name: string; status: string; [key: string]: any } cpu: { usagePercent: number; cores: number; model?: string }
memory: { percent: number; used: string; total: string }
network: { domains: string[]; privateIp?: string; publicIp?: string }
disks: { mount: string; size: string; used: string; percent: number }[]
[key: string]: any
}
export interface ProcessInfo { pid: number; name: string; cpu: number; memory: number; desc?: string; command?: string; [key: string]: any }
export interface HealthService { serviceName: string; status: string; message?: string }
export interface ServerHealth { name: string; status: string; uptime?: string; pid?: number; services?: HealthService[]; queues?: string[]; [key: string]: any }
export interface WorkerInfo { name: string; status: string; queues?: string[]; [key: string]: any }
export const serversAPI = { export const serversAPI = {
metrics() { return api.get<{ servers: ServerInfo[] }>('/admin-api/servers') }, metrics() { return api.get<{ servers: ServerInfo[] }>('/admin-api/servers') },

View File

@ -2,7 +2,7 @@ import { useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query' import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Card, Row, Col, Statistic, Button, Tag, Space, Typography, App, Table, Modal, Form, Input, Select, DatePicker, InputNumber, Tabs } from 'antd' import { Card, Row, Col, Statistic, Button, Tag, Space, Typography, App, Table, Modal, Form, Input, Select, DatePicker, InputNumber, Tabs } from 'antd'
import { DollarOutlined, ReloadOutlined, LinkOutlined, PlusOutlined, DeleteOutlined, EditOutlined, CloudOutlined, DownloadOutlined } from '@ant-design/icons' import { DollarOutlined, ReloadOutlined, LinkOutlined, PlusOutlined, DeleteOutlined, EditOutlined, CloudOutlined, DownloadOutlined } from '@ant-design/icons'
import { billingAPI, costsAPI, type BillingInfo } from '@/api' import { billingAPI, costsAPI, type BillingInfo, type CostItem, type CostSummary } from '@/api'
import dayjs from 'dayjs' import dayjs from 'dayjs'
const { Text, Title } = Typography const { Text, Title } = Typography

View File

@ -30,7 +30,7 @@ export default function ImportMonitorPage() {
// 知识库远程搜索(防抖) // 知识库远程搜索(防抖)
const [kbSearch, setKbSearch] = useState('') const [kbSearch, setKbSearch] = useState('')
const kbSearchTimer = useRef<ReturnType<typeof setTimeout>>() const kbSearchTimer = useRef<ReturnType<typeof setTimeout>>(undefined)
const handleKbSearch = useCallback((val: string) => { const handleKbSearch = useCallback((val: string) => {
clearTimeout(kbSearchTimer.current) clearTimeout(kbSearchTimer.current)

View File

@ -30,7 +30,7 @@ function KBPage() {
const [refSourceId, setRefSourceId] = useState<string | null>(null) const [refSourceId, setRefSourceId] = useState<string | null>(null)
const { data: references } = useQuery({ const { data: references } = useQuery({
queryKey: ['source-refs', refSourceId], queryKey: ['source-refs', refSourceId],
queryFn: (): Promise<any> => knowledgeAPI.sourceReferences(refSourceId), queryFn: (): Promise<any> => knowledgeAPI.sourceReferences(refSourceId || ''),
enabled: !!refSourceId, enabled: !!refSourceId,
}) })

View File

@ -26,23 +26,6 @@ interface KnowledgeSourceItem {
kbName?: string kbName?: string
} }
interface PaginatedSources {
items: KnowledgeSourceItem[]
total: number
page: number
limit: number
}
interface KnowledgeBaseItem {
id: string
title?: string
name?: string
}
interface PaginatedKnowledgeBases {
items: KnowledgeBaseItem[]
}
// ── Labels ── // ── Labels ──
const mimeLabels: Record<string, string> = { const mimeLabels: Record<string, string> = {

View File

@ -24,7 +24,7 @@ export default function ReportingAdmin() {
const download = async (type: string) => { const download = async (type: string) => {
try { try {
const text = await reportingAPI.exportCSV(type, days) const text = await reportingAPI.exportCSV(type, String(days))
downloadBlob(text as string, `${type}-report-${days}d.csv`) downloadBlob(text as string, `${type}-report-${days}d.csv`)
} catch { } catch {
message.error('导出失败') message.error('导出失败')

View File

@ -2,7 +2,7 @@ import { useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query' import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Card, Row, Col, Progress, Table, Tag, Typography, Button, Space, Tooltip, App } from 'antd' import { Card, Row, Col, Progress, Table, Tag, Typography, Button, Space, Tooltip, App } from 'antd'
import { CloudServerOutlined, ReloadOutlined, CopyOutlined, GlobalOutlined } from '@ant-design/icons' import { CloudServerOutlined, ReloadOutlined, CopyOutlined, GlobalOutlined } from '@ant-design/icons'
import { serversAPI, type ServerInfo, type ServerHealth } from '@/api/servers' import { serversAPI, type ServerInfo, type ProcessInfo, type ServerHealth } from '@/api/servers'
const { Text, Title } = Typography const { Text, Title } = Typography
@ -28,8 +28,8 @@ function ServerCard({ server }: { server: ServerInfo }) {
{/* IP & domains — fixed height */} {/* IP & domains — fixed height */}
<div style={{ marginBottom: 12, height: 54, overflow: 'hidden' }}> <div style={{ marginBottom: 12, height: 54, overflow: 'hidden' }}>
<Space wrap size={[4, 4]}> <Space wrap size={[4, 4]}>
<CopyTag text={server.network.publicIp} icon="🌐 " color="cyan" /> <CopyTag text={server.network.publicIp || ''} icon="🌐 " color="cyan" />
<CopyTag text={server.network.privateIp} icon="🔒 " /> <CopyTag text={server.network.privateIp || ''} icon="🔒 " />
{server.network.domains.map(d => ( {server.network.domains.map(d => (
<CopyTag key={d} text={d} icon={<GlobalOutlined style={{ marginRight: 2 }} />} color="blue" /> <CopyTag key={d} text={d} icon={<GlobalOutlined style={{ marginRight: 2 }} />} color="blue" />
))} ))}
@ -186,7 +186,7 @@ function ServersContent() {
{(health as ServerHealth[]).map(h => ( {(health as ServerHealth[]).map(h => (
<Card key={h.serverName} title={h.serverName} size="small" style={{ marginBottom: 8 }}> <Card key={h.serverName} title={h.serverName} size="small" style={{ marginBottom: 8 }}>
<Space wrap> <Space wrap>
{h.services.map(s => ( {(h.services ?? []).map(s => (
<Tag key={s.serviceName} color={s.status === 'healthy' ? 'green' : 'red'}> <Tag key={s.serviceName} color={s.status === 'healthy' ? 'green' : 'red'}>
{s.serviceName} · {s.message} {s.serviceName} · {s.message}
</Tag> </Tag>

View File

@ -1,7 +1,7 @@
import { useState, useRef, useEffect, useCallback } from 'react' import { useState, useRef, useEffect, useCallback } from 'react'
import { Input, Button, Typography, App, message as antMsg } from 'antd' import { Input, Button, Typography, App, message as antMsg } from 'antd'
import { PlusOutlined, ToolOutlined, DeleteOutlined, MessageOutlined, ArrowUpOutlined, CopyOutlined } from '@ant-design/icons' import { PlusOutlined, ToolOutlined, DeleteOutlined, MessageOutlined, ArrowUpOutlined, CopyOutlined } from '@ant-design/icons'
import { aiAPI, conversationsAPI } from '@/api' import { aiAPI, conversationsAPI, type Conversation } from '@/api'
import { streamChat, type StreamEvent } from '@/services/ai-chat' import { streamChat, type StreamEvent } from '@/services/ai-chat'
import Markdown from '@/components/Markdown' import Markdown from '@/components/Markdown'
@ -37,7 +37,7 @@ function ChatPage() {
const switchConversation = useCallback(async (id: string) => { const switchConversation = useCallback(async (id: string) => {
if (streaming) { abortRef.current?.abort(); setStreaming(false); setWaitingApproval(false) } if (streaming) { abortRef.current?.abort(); setStreaming(false); setWaitingApproval(false) }
setActiveId(id); setMessages([]) setActiveId(id); setMessages([])
try { const records = await conversationsAPI.messages(id); setMessages(records.map(m => ({ id: m.id, role: m.role, content: m.content, timestamp: new Date(m.createdAt).getTime() }))) } catch {} try { const records = await conversationsAPI.messages(id); setMessages(records.map(m => ({ id: m.id, role: m.role as 'user' | 'assistant', content: m.content, timestamp: new Date(m.createdAt).getTime() }))) } catch {}
}, [streaming]) }, [streaming])
const handleNew = async () => { const handleNew = async () => {

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { Table, Input, Select, Space, Button, Drawer, Descriptions, DatePicker } from 'antd'; import { Table, Input, Space, Button, Drawer, Descriptions, DatePicker } from 'antd';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { learningAPI } from '@/api'; import { learningAPI } from '@/api';
import { PageWrapper } from './PageWrapper'; import { PageWrapper } from './PageWrapper';

View File

@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { Table, Input, Select, Space, Tag, Button, Tabs, Drawer, Descriptions, DatePicker, Timeline, Typography, Popconfirm } from 'antd'; import { Table, Input, Select, Space, Tag, Button, Drawer, Descriptions, Popconfirm } from 'antd';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { learningAPI } from '@/api'; import { learningAPI } from '@/api';
import { sessionStatusLabels, targetTypeLabels, sessionModeLabels } from '@/constants/labels'; import { sessionStatusLabels, targetTypeLabels, sessionModeLabels } from '@/constants/labels';

View File

@ -6,15 +6,15 @@ const UserManagement = lazy(() => import('@/pages/UserManagement'))
const MemberManagement = lazy(() => import('@/pages/MemberManagement')) const MemberManagement = lazy(() => import('@/pages/MemberManagement'))
const LearningDashboard = lazy(() => import('@/pages/learning/Dashboard')) const LearningDashboard = lazy(() => import('@/pages/learning/Dashboard'))
const ReadingEventPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.ReadingEventPage }))) const ReadingEventPage = lazy(() => import('@/pages/learning/ReadingEventPage'))
const SessionPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.SessionPage }))) const SessionPage = lazy(() => import('@/pages/learning/SessionPage'))
const ProgressPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.ProgressPage }))) const ProgressPage = lazy(() => import('@/pages/learning/ProgressPage'))
const DailyActivityPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.DailyActivityPage }))) const DailyActivityPage = lazy(() => import('@/pages/learning/DailyActivityPage'))
const RecordPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.RecordPage }))) const RecordPage = lazy(() => import('@/pages/learning/RecordPage'))
const AnomalyPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.AnomalyPage }))) const AnomalyPage = lazy(() => import('@/pages/learning/AnomalyPage'))
const UserTimelinePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserTimelinePage }))) const UserTimelinePage = lazy(() => import('@/pages/learning/UserTimelinePage'))
const UserDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserDiagnosePage }))) const UserDiagnosePage = lazy(() => import('@/pages/learning/UserDiagnosePage'))
const MaterialDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage }))) const MaterialDiagnosePage = lazy(() => import('@/pages/learning/MaterialDiagnosePage'))
const ServersPage = lazy(() => import('@/pages/Servers')) const ServersPage = lazy(() => import('@/pages/Servers'))
const KnowledgeSourcesPage = lazy(() => import('@/pages/KnowledgeSources')) const KnowledgeSourcesPage = lazy(() => import('@/pages/KnowledgeSources'))
const LearningReplay = lazy(() => import('@/pages/learning/ReplayPage')) const LearningReplay = lazy(() => import('@/pages/learning/ReplayPage'))