[feat] more hw info including gpu + refactor fpstest
This commit is contained in:
@@ -1,11 +1,5 @@
|
||||
"use client"
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
CardIcon,
|
||||
CardTool,
|
||||
} from "@/components/window/Card"
|
||||
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "@/components/window/Card"
|
||||
import { ToolButton } from "@/components/window/ToolButton"
|
||||
import { Chip, Skeleton } from "@heroui/react"
|
||||
import { Refresh, SettingConfig } from "@icon-park/react"
|
||||
@@ -43,12 +37,14 @@ export default function Page() {
|
||||
|
||||
function HardwareInfo() {
|
||||
const { mutate } = useSWRConfig()
|
||||
|
||||
|
||||
return (
|
||||
<ToolButton onClick={() => {
|
||||
// 使用 SWR 的 mutate 来刷新数据
|
||||
mutate("/api/hardware-info")
|
||||
}}>
|
||||
<ToolButton
|
||||
onClick={() => {
|
||||
// 使用 SWR 的 mutate 来刷新数据
|
||||
mutate("/api/hardware-info")
|
||||
}}
|
||||
>
|
||||
<Refresh /> 刷新
|
||||
</ToolButton>
|
||||
)
|
||||
@@ -60,27 +56,70 @@ interface ComputerInfo {
|
||||
BiosSMBIOSBIOSVersion?: string
|
||||
CsManufacturer?: string
|
||||
CsName?: string
|
||||
ReleaseId?: string
|
||||
}
|
||||
|
||||
interface GpuInfo {
|
||||
vendor: string
|
||||
model: string
|
||||
family: string
|
||||
device_id: string
|
||||
total_vram: number
|
||||
used_vram: number
|
||||
load_pct: number
|
||||
temperature: number
|
||||
}
|
||||
|
||||
interface MemoryInfo {
|
||||
manufacturer?: string
|
||||
part_number?: string
|
||||
speed?: number // MHz
|
||||
}
|
||||
|
||||
interface MonitorInfo {
|
||||
name?: string
|
||||
refresh_rate?: number // Hz
|
||||
resolution_width?: number
|
||||
resolution_height?: number
|
||||
}
|
||||
|
||||
interface HardwareData {
|
||||
allSysData: AllSystemInfo
|
||||
computerInfo: ComputerInfo
|
||||
gpuInfo: GpuInfo | null
|
||||
memoryInfo: MemoryInfo[]
|
||||
monitorInfo: MonitorInfo[]
|
||||
}
|
||||
|
||||
// 硬件信息 fetcher
|
||||
const hardwareInfoFetcher = async (): Promise<HardwareData> => {
|
||||
// 并行获取系统信息和 PowerShell 信息
|
||||
const [sys, computerInfoData] = await Promise.all([
|
||||
// 并行获取系统信息、PowerShell 信息、GPU 信息、内存信息和显示器信息
|
||||
const [sys, computerInfoData, gpuInfoData, memoryInfoData, monitorInfoData] = await Promise.all([
|
||||
allSysInfo(),
|
||||
invoke<ComputerInfo>("get_computer_info").catch((error) => {
|
||||
console.error("获取 PowerShell 信息失败:", error)
|
||||
return {} as ComputerInfo
|
||||
})
|
||||
}),
|
||||
invoke<GpuInfo | null>("get_gpu_info").catch((error) => {
|
||||
console.error("获取 GPU 信息失败:", error)
|
||||
return null
|
||||
}),
|
||||
invoke<MemoryInfo[]>("get_memory_info").catch((error) => {
|
||||
console.error("获取内存信息失败:", error)
|
||||
return [] as MemoryInfo[]
|
||||
}),
|
||||
invoke<MonitorInfo[]>("get_monitor_info").catch((error) => {
|
||||
console.error("获取显示器信息失败:", error)
|
||||
return [] as MonitorInfo[]
|
||||
}),
|
||||
])
|
||||
|
||||
|
||||
console.log("系统信息:", sys)
|
||||
console.log("PowerShell 信息:", computerInfoData)
|
||||
|
||||
console.log("GPU 信息:", gpuInfoData)
|
||||
console.log("内存信息:", memoryInfoData)
|
||||
console.log("显示器信息:", monitorInfoData)
|
||||
|
||||
if (sys?.cpus) {
|
||||
console.log("CPU数据:", sys.cpus)
|
||||
console.log("第一个CPU:", sys.cpus[0])
|
||||
@@ -88,26 +127,28 @@ const hardwareInfoFetcher = async (): Promise<HardwareData> => {
|
||||
console.log("CPU字段:", Object.keys(sys.cpus[0]))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
allSysData: sys,
|
||||
computerInfo: computerInfoData
|
||||
computerInfo: computerInfoData,
|
||||
gpuInfo: gpuInfoData,
|
||||
memoryInfo: memoryInfoData,
|
||||
monitorInfo: monitorInfoData,
|
||||
}
|
||||
}
|
||||
|
||||
function HardwareInfoContent() {
|
||||
const { data, isLoading } = useSWR<HardwareData>(
|
||||
"/api/hardware-info",
|
||||
hardwareInfoFetcher,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
dedupingInterval: 5 * 60 * 1000, // 5分钟内相同请求去重
|
||||
}
|
||||
)
|
||||
const { data, isLoading } = useSWR<HardwareData>("/api/hardware-info", hardwareInfoFetcher, {
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
dedupingInterval: 5 * 60 * 1000, // 5分钟内相同请求去重
|
||||
})
|
||||
|
||||
const allSysData = data?.allSysData
|
||||
const computerInfo = data?.computerInfo || {}
|
||||
const gpuInfo = data?.gpuInfo
|
||||
const memoryInfo = data?.memoryInfo || []
|
||||
const monitorInfo = data?.monitorInfo || []
|
||||
|
||||
const formatBytes = (bytes?: number) => {
|
||||
if (!bytes) return "未知"
|
||||
@@ -119,56 +160,72 @@ function HardwareInfoContent() {
|
||||
return `${mb.toFixed(2)}MB`
|
||||
}
|
||||
|
||||
// 如果 PowerShell 提供了 OSDisplayVersion,直接使用;否则尝试从 kernel_version 推断
|
||||
const windowsVersionCode = computerInfo.OSDisplayVersion || null
|
||||
const memoryUsagePercent = allSysData?.total_memory && allSysData?.used_memory !== undefined
|
||||
? Math.round((allSysData.used_memory / allSysData.total_memory) * 100)
|
||||
: null
|
||||
|
||||
// 格式化系统信息:Windows 11 (26200) 25H2
|
||||
const formatSystemInfo = () => {
|
||||
const osVersion = allSysData?.os_version || null
|
||||
// 使用 OSDisplayVersion 作为版本代码(如 "25H2")
|
||||
const osDisplayVersion = computerInfo.OSDisplayVersion || null
|
||||
|
||||
let systemStr = allSysData?.name || "未知"
|
||||
if (osVersion) {
|
||||
systemStr += ` ${osVersion}`
|
||||
}
|
||||
if (osDisplayVersion) {
|
||||
systemStr += ` ${osDisplayVersion}`
|
||||
}
|
||||
|
||||
return systemStr
|
||||
}
|
||||
const memoryUsagePercent =
|
||||
allSysData?.total_memory && allSysData?.used_memory !== undefined
|
||||
? Math.round((allSysData.used_memory / allSysData.total_memory) * 100)
|
||||
: null
|
||||
|
||||
// 计算所有CPU核心的平均频率(统一转换为GHz)
|
||||
const averageCpuFrequency = allSysData?.cpus && allSysData.cpus.length > 0
|
||||
? (() => {
|
||||
// 尝试多个可能的频率字段名
|
||||
const frequencies = allSysData.cpus
|
||||
.map(cpu => {
|
||||
// 尝试不同的字段名
|
||||
const freq = (cpu as any).frequency ?? (cpu as any).freq ?? (cpu as any).clock_speed
|
||||
return freq
|
||||
})
|
||||
.filter((freq): freq is number => {
|
||||
// 确保是有效的数字且大于0
|
||||
return typeof freq === 'number' && !isNaN(freq) && freq > 0
|
||||
})
|
||||
|
||||
if (frequencies.length === 0) {
|
||||
console.log("未找到有效的CPU频率数据,CPU对象:", allSysData.cpus[0])
|
||||
return null
|
||||
}
|
||||
|
||||
const sum = frequencies.reduce((acc, freq) => acc + freq, 0)
|
||||
const avg = sum / frequencies.length
|
||||
|
||||
// 判断单位并统一转换为GHz
|
||||
// 如果值在2-10范围,可能是GHz
|
||||
// 如果值在2000-10000范围,可能是MHz(需要除以1000)
|
||||
// 如果值在百万级别(2000000+),可能是Hz(需要除以1,000,000)
|
||||
let freqInGhz: number
|
||||
if (avg >= 1_000_000) {
|
||||
// Hz单位,转换为GHz
|
||||
freqInGhz = avg / 1_000_000
|
||||
} else if (avg >= 1000) {
|
||||
// MHz单位,转换为GHz
|
||||
freqInGhz = avg / 1000
|
||||
} else {
|
||||
// 已经是GHz单位
|
||||
freqInGhz = avg
|
||||
}
|
||||
|
||||
console.log("CPU频率数据:", frequencies, "原始平均值:", avg, "转换为GHz:", freqInGhz)
|
||||
|
||||
return freqInGhz
|
||||
})()
|
||||
: null
|
||||
const averageCpuFrequency =
|
||||
allSysData?.cpus && allSysData.cpus.length > 0
|
||||
? (() => {
|
||||
// 尝试多个可能的频率字段名
|
||||
const frequencies = allSysData.cpus
|
||||
.map((cpu) => {
|
||||
// 尝试不同的字段名
|
||||
const freq = (cpu as any).frequency ?? (cpu as any).freq ?? (cpu as any).clock_speed
|
||||
return freq
|
||||
})
|
||||
.filter((freq): freq is number => {
|
||||
// 确保是有效的数字且大于0
|
||||
return typeof freq === "number" && !isNaN(freq) && freq > 0
|
||||
})
|
||||
|
||||
if (frequencies.length === 0) {
|
||||
console.log("未找到有效的CPU频率数据,CPU对象:", allSysData.cpus[0])
|
||||
return null
|
||||
}
|
||||
|
||||
const sum = frequencies.reduce((acc, freq) => acc + freq, 0)
|
||||
const avg = sum / frequencies.length
|
||||
|
||||
// 判断单位并统一转换为GHz
|
||||
// 如果值在2-10范围,可能是GHz
|
||||
// 如果值在2000-10000范围,可能是MHz(需要除以1000)
|
||||
// 如果值在百万级别(2000000+),可能是Hz(需要除以1,000,000)
|
||||
let freqInGhz: number
|
||||
if (avg >= 1_000_000) {
|
||||
// Hz单位,转换为GHz
|
||||
freqInGhz = avg / 1_000_000
|
||||
} else if (avg >= 1000) {
|
||||
// MHz单位,转换为GHz
|
||||
freqInGhz = avg / 1000
|
||||
} else {
|
||||
// 已经是GHz单位
|
||||
freqInGhz = avg
|
||||
}
|
||||
|
||||
console.log("CPU频率数据:", frequencies, "原始平均值:", avg, "转换为GHz:", freqInGhz)
|
||||
|
||||
return freqInGhz
|
||||
})()
|
||||
: null
|
||||
|
||||
// 如果正在加载,显示 Skeleton 骨架屏
|
||||
if (isLoading) {
|
||||
@@ -176,47 +233,47 @@ function HardwareInfoContent() {
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* 系统信息 Skeleton */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-5 w-24 rounded" />
|
||||
<Skeleton className="w-24 h-5 rounded" />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Skeleton className="h-8 w-48 rounded-full" />
|
||||
<Skeleton className="h-8 w-32 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主板信息 Skeleton */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-5 w-16 rounded" />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Skeleton className="h-8 w-40 rounded-full" />
|
||||
<Skeleton className="h-8 w-36 rounded-full" />
|
||||
<Skeleton className="w-48 h-8 rounded-full" />
|
||||
<Skeleton className="w-32 h-8 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CPU 信息 Skeleton */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-5 w-20 rounded" />
|
||||
<Skeleton className="w-20 h-5 rounded" />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Skeleton className="h-8 w-56 rounded-full" />
|
||||
<Skeleton className="h-8 w-32 rounded-full" />
|
||||
<Skeleton className="h-8 w-28 rounded-full" />
|
||||
<Skeleton className="w-56 h-8 rounded-full" />
|
||||
<Skeleton className="w-32 h-8 rounded-full" />
|
||||
<Skeleton className="h-8 rounded-full w-28" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 内存信息 Skeleton */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-5 w-16 rounded" />
|
||||
<Skeleton className="w-16 h-5 rounded" />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Skeleton className="h-8 w-36 rounded-full" />
|
||||
<Skeleton className="h-8 w-40 rounded-full" />
|
||||
<Skeleton className="h-8 w-36 rounded-full" />
|
||||
<Skeleton className="h-8 rounded-full w-36" />
|
||||
<Skeleton className="w-40 h-8 rounded-full" />
|
||||
<Skeleton className="h-8 rounded-full w-36" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* GPU 信息 Skeleton */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-5 w-16 rounded" />
|
||||
<Skeleton className="w-16 h-5 rounded" />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Skeleton className="h-8 w-52 rounded-full" />
|
||||
<Skeleton className="h-8 rounded-full w-52" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主板信息 Skeleton */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="w-16 h-5 rounded" />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Skeleton className="w-40 h-8 rounded-full" />
|
||||
<Skeleton className="h-8 rounded-full w-36" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,27 +286,9 @@ function HardwareInfoContent() {
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-sm font-medium text-foreground-600">系统信息</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{computerInfo.OsName && (
|
||||
<Chip>
|
||||
<span className="font-medium">系统:</span> {computerInfo.OsName}
|
||||
{windowsVersionCode && (
|
||||
<span className="ml-1 text-primary">({windowsVersionCode})</span>
|
||||
)}
|
||||
</Chip>
|
||||
)}
|
||||
{!computerInfo.OsName && (
|
||||
<Chip>
|
||||
<span className="font-medium">系统:</span> {allSysData?.name || "未知"} {allSysData?.os_version || ""}
|
||||
{allSysData?.kernel_version && (
|
||||
<>
|
||||
{" "}{allSysData.kernel_version}
|
||||
{windowsVersionCode && (
|
||||
<span className="ml-1 text-primary">({windowsVersionCode})</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Chip>
|
||||
)}
|
||||
<Chip>
|
||||
<span className="font-medium">系统:</span> {formatSystemInfo()}
|
||||
</Chip>
|
||||
{computerInfo.CsName && (
|
||||
<Chip>
|
||||
<span className="font-medium">主机名:</span> {computerInfo.CsName}
|
||||
@@ -263,25 +302,6 @@ function HardwareInfoContent() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主板信息 */}
|
||||
{(computerInfo.CsManufacturer || computerInfo.BiosSMBIOSBIOSVersion) && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-sm font-medium text-foreground-600">主板</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{computerInfo.CsManufacturer && (
|
||||
<Chip>
|
||||
<span className="font-medium">制造商:</span> {computerInfo.CsManufacturer}
|
||||
</Chip>
|
||||
)}
|
||||
{computerInfo.BiosSMBIOSBIOSVersion && (
|
||||
<Chip>
|
||||
<span className="font-medium">BIOS版本:</span> {computerInfo.BiosSMBIOSBIOSVersion}
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CPU 信息 */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-sm font-medium text-foreground-600">处理器</div>
|
||||
@@ -318,7 +338,18 @@ function HardwareInfoContent() {
|
||||
)}
|
||||
{allSysData.total_memory !== undefined && allSysData.used_memory !== undefined && (
|
||||
<Chip>
|
||||
<span className="font-medium">可用:</span> {formatBytes(allSysData.total_memory - allSysData.used_memory)}
|
||||
<span className="font-medium">可用:</span>{" "}
|
||||
{formatBytes(allSysData.total_memory - allSysData.used_memory)}
|
||||
</Chip>
|
||||
)}
|
||||
{memoryInfo.length > 0 && memoryInfo[0].part_number && (
|
||||
<Chip>
|
||||
<span className="font-medium">型号:</span> {memoryInfo[0].part_number}
|
||||
</Chip>
|
||||
)}
|
||||
{memoryInfo.length > 0 && memoryInfo[0].speed && (
|
||||
<Chip>
|
||||
<span className="font-medium">频率:</span> {memoryInfo[0].speed} MHz
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
@@ -326,25 +357,49 @@ function HardwareInfoContent() {
|
||||
)}
|
||||
|
||||
{/* GPU 信息 */}
|
||||
{allSysData?.components && allSysData.components.length > 0 && (
|
||||
{gpuInfo ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-sm font-medium text-foreground-600">显卡</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Chip>
|
||||
<span className="font-medium">型号:</span> {gpuInfo.model}
|
||||
</Chip>
|
||||
<Chip>
|
||||
<span className="font-medium">总显存:</span> {formatBytes(gpuInfo.total_vram)}
|
||||
</Chip>
|
||||
<Chip>
|
||||
<span className="font-medium">已用显存:</span> {formatBytes(gpuInfo.used_vram)}
|
||||
</Chip>
|
||||
<Chip>
|
||||
<span className="font-medium">温度:</span> {gpuInfo.temperature.toFixed(2)}°C
|
||||
</Chip>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
allSysData?.components &&
|
||||
allSysData.components.length > 0 &&
|
||||
(() => {
|
||||
const gpuComponents = allSysData.components.filter((comp) =>
|
||||
comp.label?.toLowerCase().includes('gpu') ||
|
||||
comp.label?.toLowerCase().includes('graphics') ||
|
||||
comp.label?.toLowerCase().includes('显卡')
|
||||
const gpuComponents = allSysData.components.filter(
|
||||
(comp) =>
|
||||
comp.label?.toLowerCase().includes("gpu") ||
|
||||
comp.label?.toLowerCase().includes("graphics") ||
|
||||
comp.label?.toLowerCase().includes("显卡")
|
||||
)
|
||||
|
||||
|
||||
if (gpuComponents.length === 0) return null
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-sm font-medium text-foreground-600">显卡</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{gpuComponents.map((gpu, index) => (
|
||||
<Chip key={index}>
|
||||
<span className="font-medium">GPU{index > 0 ? ` ${index + 1}` : ""}:</span> {gpu.label || "未知"}
|
||||
<span className="font-medium">GPU{index > 0 ? ` ${index + 1}` : ""}:</span>{" "}
|
||||
{gpu.label || "未知"}
|
||||
{gpu.temperature !== undefined && (
|
||||
<span className="ml-1">({gpu.temperature}°C{gpu.max !== undefined ? ` / ${gpu.max}°C` : ""})</span>
|
||||
<span className="ml-1">
|
||||
({gpu.temperature}°C{gpu.max !== undefined ? ` / ${gpu.max}°C` : ""})
|
||||
</span>
|
||||
)}
|
||||
</Chip>
|
||||
))}
|
||||
@@ -354,6 +409,45 @@ function HardwareInfoContent() {
|
||||
})()
|
||||
)}
|
||||
|
||||
{/* 主板信息 */}
|
||||
{(computerInfo.CsManufacturer || computerInfo.BiosSMBIOSBIOSVersion) && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-sm font-medium text-foreground-600">主板</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{computerInfo.CsManufacturer && (
|
||||
<Chip>
|
||||
<span className="font-medium">制造商:</span> {computerInfo.CsManufacturer}
|
||||
</Chip>
|
||||
)}
|
||||
{computerInfo.BiosSMBIOSBIOSVersion && (
|
||||
<Chip>
|
||||
<span className="font-medium">BIOS版本:</span> {computerInfo.BiosSMBIOSBIOSVersion}
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 显示器信息 */}
|
||||
{monitorInfo.length > 0 && monitorInfo.some((m) => m.refresh_rate) && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-sm font-medium text-foreground-600">显示器</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{monitorInfo.map(
|
||||
(monitor, index) =>
|
||||
monitor.refresh_rate && (
|
||||
<Chip key={index}>
|
||||
<span className="font-medium">
|
||||
刷新率{monitorInfo.length > 1 ? ` ${index + 1}` : ""}:
|
||||
</span>{" "}
|
||||
{monitor.refresh_rate} Hz
|
||||
</Chip>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 电池信息 */}
|
||||
{allSysData?.batteries && allSysData.batteries.length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -365,7 +459,9 @@ function HardwareInfoContent() {
|
||||
{battery.state && `${battery.state} `}
|
||||
{battery.state_of_charge !== undefined && `${battery.state_of_charge}% `}
|
||||
{battery.energy_full !== undefined && battery.energy !== undefined && (
|
||||
<span>({formatBytes(battery.energy)} / {formatBytes(battery.energy_full)})</span>
|
||||
<span>
|
||||
({formatBytes(battery.energy)} / {formatBytes(battery.energy_full)})
|
||||
</span>
|
||||
)}
|
||||
</Chip>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user