chore: handle hardware settings (#5041)
* chore: handle hardware settings * chore: activate GPUs * Update web-app/src/services/hardware.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
3e887deb3e
commit
81c4dc516b
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/web-app",
|
"name": "@janhq/web-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.5.18",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { persist, createJSONStorage } from 'zustand/middleware'
|
import { persist, createJSONStorage } from 'zustand/middleware'
|
||||||
import { localStorageKey } from '@/constants/localStorage'
|
import { localStorageKey } from '@/constants/localStorage'
|
||||||
|
import { setActiveGpus } from '@/services/hardware'
|
||||||
|
|
||||||
// Hardware data types
|
// Hardware data types
|
||||||
export interface CPU {
|
export interface CPU {
|
||||||
@ -171,7 +172,7 @@ export const useHardware = create<HardwareStore>()(
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
toggleGPUActivation: (index) =>
|
toggleGPUActivation: (index) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newGPUs = [...state.hardwareData.gpus]
|
const newGPUs = [...state.hardwareData.gpus]
|
||||||
if (index >= 0 && index < newGPUs.length) {
|
if (index >= 0 && index < newGPUs.length) {
|
||||||
@ -180,13 +181,17 @@ export const useHardware = create<HardwareStore>()(
|
|||||||
activated: !newGPUs[index].activated,
|
activated: !newGPUs[index].activated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setActiveGpus({
|
||||||
|
gpus: newGPUs.filter((e) => e.activated).map((e) => e.id as unknown as number),
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
hardwareData: {
|
hardwareData: {
|
||||||
...state.hardwareData,
|
...state.hardwareData,
|
||||||
gpus: newGPUs,
|
gpus: newGPUs,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
|
},
|
||||||
|
|
||||||
reorderGPUs: (oldIndex, newIndex) =>
|
reorderGPUs: (oldIndex, newIndex) =>
|
||||||
set((state) => {
|
set((state) => {
|
||||||
|
|||||||
@ -63,7 +63,7 @@ function General() {
|
|||||||
title="App Version"
|
title="App Version"
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<span className="text-main-view-fg/80">v16.0.0</span>
|
<span className="text-main-view-fg/80">v{VERSION}</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { Switch } from '@/components/ui/switch'
|
|||||||
import { Progress } from '@/components/ui/progress'
|
import { Progress } from '@/components/ui/progress'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useHardware } from '@/hooks/useHardware'
|
import { useHardware } from '@/hooks/useHardware'
|
||||||
import type { GPU } from '@/hooks/useHardware'
|
import type { GPU, HardwareData } from '@/hooks/useHardware'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
@ -25,84 +25,21 @@ import {
|
|||||||
} from '@dnd-kit/sortable'
|
} from '@dnd-kit/sortable'
|
||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
import { IconGripVertical } from '@tabler/icons-react'
|
import { IconGripVertical } from '@tabler/icons-react'
|
||||||
|
import { getHardwareInfo } from '@/services/hardware'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export const Route = createFileRoute(route.settings.hardware as any)({
|
export const Route = createFileRoute(route.settings.hardware as any)({
|
||||||
component: Hardware,
|
component: Hardware,
|
||||||
})
|
})
|
||||||
|
|
||||||
const fetchHardwareData = () => {
|
|
||||||
return {
|
|
||||||
cpu: {
|
|
||||||
arch: 'x86_64',
|
|
||||||
cores: 8,
|
|
||||||
instructions: ['SSE4.1', 'SSE4.2', 'AVX2'],
|
|
||||||
model: 'Apple M4 chip (10-core CPU, 10-core GPU)',
|
|
||||||
usage: Math.random() * 100, // Simulate changing CPU usage
|
|
||||||
},
|
|
||||||
gpus: [
|
|
||||||
{
|
|
||||||
activated: true,
|
|
||||||
additional_information: {
|
|
||||||
compute_cap: '7.5',
|
|
||||||
driver_version: '535.129.03',
|
|
||||||
},
|
|
||||||
free_vram: Math.floor(Math.random() * 4 * 1024 * 1024 * 1024), // Random free VRAM
|
|
||||||
id: '0',
|
|
||||||
name: 'NVIDIA GeForce RTX 3080',
|
|
||||||
total_vram: 10 * 1024 * 1024 * 1024, // 10GB in bytes
|
|
||||||
uuid: 'GPU-123456789-0',
|
|
||||||
version: '7.5',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
activated: true,
|
|
||||||
additional_information: {
|
|
||||||
compute_cap: '8.6',
|
|
||||||
driver_version: '535.129.03',
|
|
||||||
},
|
|
||||||
free_vram: Math.floor(Math.random() * 8 * 1024 * 1024 * 1024), // Random free VRAM
|
|
||||||
id: '1',
|
|
||||||
name: 'NVIDIA GeForce RTX 4070',
|
|
||||||
total_vram: 12 * 1024 * 1024 * 1024, // 12GB in bytes
|
|
||||||
uuid: 'GPU-123456789-1',
|
|
||||||
version: '8.6',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
activated: false,
|
|
||||||
additional_information: {
|
|
||||||
compute_cap: '6.1',
|
|
||||||
driver_version: '535.129.03',
|
|
||||||
},
|
|
||||||
free_vram: Math.floor(Math.random() * 6 * 1024 * 1024 * 1024), // Random free VRAM
|
|
||||||
id: '2',
|
|
||||||
name: 'NVIDIA GeForce GTX 1660 Ti',
|
|
||||||
total_vram: 6 * 1024 * 1024 * 1024, // 6GB in bytes
|
|
||||||
uuid: 'GPU-123456789-2',
|
|
||||||
version: '6.1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
os: {
|
|
||||||
name: 'macOS',
|
|
||||||
version: '14.0',
|
|
||||||
},
|
|
||||||
ram: {
|
|
||||||
available: Math.floor(Math.random() * 16 * 1024 * 1024 * 1024), // Random available RAM
|
|
||||||
total: 32 * 1024 * 1024 * 1024, // 32GB in bytes
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format bytes to a human-readable format
|
// Format bytes to a human-readable format
|
||||||
const formatBytes = (bytes: number, decimals = 2) => {
|
function formatMegaBytes(mb: number) {
|
||||||
if (bytes === 0) return '0 Bytes'
|
const tb = mb / (1024 * 1024)
|
||||||
|
if (tb >= 1) {
|
||||||
const k = 1024
|
return `${tb.toFixed(2)} TB`
|
||||||
const dm = decimals < 0 ? 0 : decimals
|
} else {
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
|
const gb = mb / 1024
|
||||||
|
return `${gb.toFixed(2)} GB`
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
}
|
||||||
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function SortableGPUItem({ gpu, index }: { gpu: GPU; index: number }) {
|
function SortableGPUItem({ gpu, index }: { gpu: GPU; index: number }) {
|
||||||
@ -154,7 +91,8 @@ function SortableGPUItem({ gpu, index }: { gpu: GPU; index: number }) {
|
|||||||
title="VRAM"
|
title="VRAM"
|
||||||
actions={
|
actions={
|
||||||
<span className="text-main-view-fg/80">
|
<span className="text-main-view-fg/80">
|
||||||
{formatBytes(gpu.free_vram)} free of {formatBytes(gpu.total_vram)}
|
{formatMegaBytes(gpu.free_vram)} free of{' '}
|
||||||
|
{formatMegaBytes(gpu.total_vram)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -189,6 +127,12 @@ function Hardware() {
|
|||||||
reorderGPUs,
|
reorderGPUs,
|
||||||
} = useHardware()
|
} = useHardware()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getHardwareInfo().then((data) =>
|
||||||
|
setHardwareData(data as unknown as HardwareData)
|
||||||
|
)
|
||||||
|
}, [setHardwareData])
|
||||||
|
|
||||||
// Set up DnD sensors
|
// Set up DnD sensors
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
useSensor(PointerSensor),
|
useSensor(PointerSensor),
|
||||||
@ -213,13 +157,12 @@ function Hardware() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data = fetchHardwareData()
|
|
||||||
setHardwareData(data)
|
|
||||||
|
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
const newData = fetchHardwareData()
|
getHardwareInfo().then((data) => {
|
||||||
updateCPUUsage(newData.cpu.usage)
|
setHardwareData(data as unknown as HardwareData)
|
||||||
updateRAMAvailable(newData.ram.available)
|
updateCPUUsage(data.cpu.usage)
|
||||||
|
updateRAMAvailable(data.ram.available)
|
||||||
|
})
|
||||||
}, 5000)
|
}, 5000)
|
||||||
|
|
||||||
return () => clearInterval(intervalId)
|
return () => clearInterval(intervalId)
|
||||||
@ -280,14 +223,16 @@ function Hardware() {
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<CardItem
|
{hardwareData.cpu.instructions.join(', ').length > 0 && (
|
||||||
title="Instructions"
|
<CardItem
|
||||||
actions={
|
title="Instructions"
|
||||||
<span className="text-main-view-fg/80">
|
actions={
|
||||||
{hardwareData.cpu.instructions.join(', ')}
|
<span className="text-main-view-fg/80">
|
||||||
</span>
|
{hardwareData.cpu.instructions.join(', ')}
|
||||||
}
|
</span>
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<CardItem
|
<CardItem
|
||||||
title="Usage"
|
title="Usage"
|
||||||
actions={
|
actions={
|
||||||
@ -310,7 +255,7 @@ function Hardware() {
|
|||||||
title="Total RAM"
|
title="Total RAM"
|
||||||
actions={
|
actions={
|
||||||
<span className="text-main-view-fg/80">
|
<span className="text-main-view-fg/80">
|
||||||
{formatBytes(hardwareData.ram.total)}
|
{formatMegaBytes(hardwareData.ram.total)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -318,7 +263,7 @@ function Hardware() {
|
|||||||
title="Available RAM"
|
title="Available RAM"
|
||||||
actions={
|
actions={
|
||||||
<span className="text-main-view-fg/80">
|
<span className="text-main-view-fg/80">
|
||||||
{formatBytes(hardwareData.ram.available)}
|
{formatMegaBytes(hardwareData.ram.available)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
45
web-app/src/services/hardware.ts
Normal file
45
web-app/src/services/hardware.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { ExtensionManager } from '@/lib/extension'
|
||||||
|
import { ExtensionTypeEnum, HardwareManagementExtension } from '@janhq/core'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get hardware information from the HardwareManagementExtension.
|
||||||
|
* @returns {Promise<HardwareInfo>} A promise that resolves to the hardware information.
|
||||||
|
*/
|
||||||
|
export const getHardwareInfo = async () => {
|
||||||
|
const extension =
|
||||||
|
ExtensionManager.getInstance().get<HardwareManagementExtension>(
|
||||||
|
ExtensionTypeEnum.Hardware
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!extension) throw new Error('Hardware extension not found')
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await extension?.getHardware()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to download model:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set gpus activate
|
||||||
|
* @returns A Promise that resolves set gpus activate.
|
||||||
|
*/
|
||||||
|
export const setActiveGpus = async (data: { gpus: number[] }) => {
|
||||||
|
const extension =
|
||||||
|
ExtensionManager.getInstance().get<HardwareManagementExtension>(
|
||||||
|
ExtensionTypeEnum.Hardware
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!extension) {
|
||||||
|
throw new Error('Extension is not available')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await extension.setAvtiveGpu(data)
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to install engine variant:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
1
web-app/src/types/global.d.ts
vendored
1
web-app/src/types/global.d.ts
vendored
@ -19,4 +19,5 @@ declare global {
|
|||||||
let IS_IOS: boolean
|
let IS_IOS: boolean
|
||||||
let IS_ANDROID: boolean
|
let IS_ANDROID: boolean
|
||||||
let PLATFORM: string
|
let PLATFORM: string
|
||||||
|
let VERSION: string
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ import tailwindcss from '@tailwindcss/vite'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
|
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
|
||||||
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
||||||
|
import packageJson from './package.json'
|
||||||
const host = process.env.TAURI_DEV_HOST
|
const host = process.env.TAURI_DEV_HOST
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
@ -23,12 +24,24 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
IS_TAURI: JSON.stringify(process.env.IS_TAURI),
|
IS_TAURI: JSON.stringify(process.env.IS_TAURI),
|
||||||
IS_MACOS: JSON.stringify(process.env.TAURI_ENV_PLATFORM?.includes('darwin') ?? 'false'),
|
IS_MACOS: JSON.stringify(
|
||||||
IS_WINDOWS: JSON.stringify(process.env.TAURI_ENV_PLATFORM?.includes('windows') ?? 'false'),
|
process.env.TAURI_ENV_PLATFORM?.includes('darwin') ?? 'false'
|
||||||
IS_LINUX: JSON.stringify(process.env.TAURI_ENV_PLATFORM?.includes('unix') ?? 'false'),
|
),
|
||||||
IS_IOS: JSON.stringify(process.env.TAURI_ENV_PLATFORM?.includes('ios') ?? 'false'),
|
IS_WINDOWS: JSON.stringify(
|
||||||
IS_ANDROID: JSON.stringify(process.env.TAURI_ENV_PLATFORM?.includes('android') ?? 'false'),
|
process.env.TAURI_ENV_PLATFORM?.includes('windows') ?? 'false'
|
||||||
|
),
|
||||||
|
IS_LINUX: JSON.stringify(
|
||||||
|
process.env.TAURI_ENV_PLATFORM?.includes('unix') ?? 'false'
|
||||||
|
),
|
||||||
|
IS_IOS: JSON.stringify(
|
||||||
|
process.env.TAURI_ENV_PLATFORM?.includes('ios') ?? 'false'
|
||||||
|
),
|
||||||
|
IS_ANDROID: JSON.stringify(
|
||||||
|
process.env.TAURI_ENV_PLATFORM?.includes('android') ?? 'false'
|
||||||
|
),
|
||||||
PLATFORM: JSON.stringify(process.env.TAURI_ENV_PLATFORM),
|
PLATFORM: JSON.stringify(process.env.TAURI_ENV_PLATFORM),
|
||||||
|
|
||||||
|
VERSION: JSON.stringify(packageJson.version),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user