fix: gpu hardware state (#4650)
* fix: gpu hardware state * chore: cleanup state * chore: clear state
This commit is contained in:
parent
7a6890bd7f
commit
dde260e723
@ -509,61 +509,61 @@ __metadata:
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0
|
||||
checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0
|
||||
checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0
|
||||
checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0
|
||||
checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0
|
||||
checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension":
|
||||
version: 0.1.10
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
|
||||
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
|
||||
dependencies:
|
||||
rxjs: "npm:^7.8.1"
|
||||
ulidx: "npm:^2.3.0"
|
||||
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0
|
||||
checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
|
||||
import { Gpu } from '@janhq/core'
|
||||
|
||||
import { Progress, ScrollArea, Switch } from '@janhq/joi'
|
||||
import { useAtom, useAtomValue } from 'jotai'
|
||||
import { atomWithStorage } from 'jotai/utils'
|
||||
@ -20,19 +20,24 @@ import {
|
||||
|
||||
import { toGibibytes } from '@/utils/converter'
|
||||
|
||||
import { utilizedMemory } from '@/utils/memory'
|
||||
|
||||
import {
|
||||
cpuUsageAtom,
|
||||
ramUtilitizedAtom,
|
||||
totalRamAtom,
|
||||
usedRamAtom,
|
||||
gpusAtom,
|
||||
} from '@/helpers/atoms/SystemBar.atom'
|
||||
|
||||
const gpusAtom = atomWithStorage<Gpu[]>('gpus', [], undefined, {
|
||||
const orderGpusAtom = atomWithStorage<any>('orderGpus', [], undefined, {
|
||||
getOnInit: true,
|
||||
})
|
||||
|
||||
const Hardware = () => {
|
||||
const { hardware } = useGetHardwareInfo()
|
||||
const { hardware, mutate } = useGetHardwareInfo()
|
||||
const [isActivatingGpu, setIsActivatingGpu] = useState<Set<string>>(new Set())
|
||||
|
||||
const [openPanels, setOpenPanels] = useState<Record<number, boolean>>({})
|
||||
|
||||
const cpuUsage = useAtomValue(cpuUsageAtom)
|
||||
@ -40,7 +45,9 @@ const Hardware = () => {
|
||||
const usedRam = useAtomValue(usedRamAtom)
|
||||
const ramUtilitized = useAtomValue(ramUtilitizedAtom)
|
||||
|
||||
const [gpus, setGpus] = useAtom<Gpu[]>(gpusAtom)
|
||||
const [gpus, setGpus] = useAtom(gpusAtom)
|
||||
|
||||
const [orderGpus, setOrderGpus] = useAtom(orderGpusAtom)
|
||||
|
||||
const togglePanel = (index: number) => {
|
||||
setOpenPanels((prev) => ({
|
||||
@ -51,16 +58,18 @@ const Hardware = () => {
|
||||
|
||||
// Handle switch toggle for GPU activation
|
||||
const handleSwitchChange = async (id: string, isActive: boolean) => {
|
||||
setIsActivatingGpu((prev) => new Set(prev).add(id))
|
||||
|
||||
const updatedGpus = gpus.map((gpu) =>
|
||||
gpu.id === id ? { ...gpu, activated: isActive } : gpu
|
||||
)
|
||||
setGpus(updatedGpus)
|
||||
// Call the API to update the active GPUs
|
||||
try {
|
||||
const activeGpuIds = updatedGpus
|
||||
.filter((gpu) => gpu.activated)
|
||||
.map((gpu) => Number(gpu.id))
|
||||
.filter((gpu: any) => gpu.activated)
|
||||
.map((gpu: any) => Number(gpu.id))
|
||||
await setActiveGpus({ gpus: activeGpuIds })
|
||||
mutate()
|
||||
} catch (error) {
|
||||
console.error('Failed to update active GPUs:', error)
|
||||
}
|
||||
@ -71,23 +80,36 @@ const Hardware = () => {
|
||||
const reorderedGpus = Array.from(gpus)
|
||||
const [movedGpu] = reorderedGpus.splice(result.source.index, 1)
|
||||
reorderedGpus.splice(result.destination.index, 0, movedGpu)
|
||||
setGpus(reorderedGpus) // Update the atom, which persists to localStorage
|
||||
|
||||
setGpus(reorderedGpus)
|
||||
setOrderGpus(reorderedGpus.map((gpu) => gpu.id))
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (hardware?.gpus) {
|
||||
setGpus((prevGpus) => {
|
||||
setGpus((prevGpus: any) => {
|
||||
// Create a map of existing GPUs by UUID for quick lookup
|
||||
const gpuMap = new Map(prevGpus.map((gpu) => [gpu.uuid, gpu]))
|
||||
const gpuMap = new Map(prevGpus.map((gpu: any) => [gpu.uuid, gpu]))
|
||||
|
||||
// Update existing GPUs or add new ones
|
||||
const updatedGpus = hardware.gpus.map((newGpu) => {
|
||||
const existingGpu = gpuMap.get(newGpu.uuid)
|
||||
const existingGpu: any = gpuMap.get(newGpu.uuid)
|
||||
|
||||
if (existingGpu) {
|
||||
// Update the GPU properties while keeping the original order
|
||||
|
||||
if (existingGpu.activated !== newGpu.activated) {
|
||||
setIsActivatingGpu((prev) => {
|
||||
const updated = new Set(prev)
|
||||
updated.delete(existingGpu.id)
|
||||
updated.clear()
|
||||
return updated
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...existingGpu,
|
||||
activated: newGpu.activated,
|
||||
free_vram: newGpu.free_vram,
|
||||
total_vram: newGpu.total_vram,
|
||||
}
|
||||
@ -100,7 +122,8 @@ const Hardware = () => {
|
||||
// Append GPUs from the previous state that are not in the hardware.gpus
|
||||
// This preserves user-reordered GPUs that aren't present in the new data
|
||||
const remainingGpus = prevGpus.filter(
|
||||
(prevGpu) => !hardware.gpus?.some((gpu) => gpu.uuid === prevGpu.uuid)
|
||||
(prevGpu: any) =>
|
||||
!hardware.gpus?.some((gpu) => gpu.uuid === prevGpu.uuid)
|
||||
)
|
||||
|
||||
return [...updatedGpus, ...remainingGpus]
|
||||
@ -202,133 +225,154 @@ const Hardware = () => {
|
||||
ref={provided.innerRef}
|
||||
className="mt-4"
|
||||
>
|
||||
{gpus.map((item, i) => (
|
||||
<Draggable key={i} draggableId={String(i)} index={i}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
className={twMerge(
|
||||
'cursor-pointer border border-[hsla(var(--app-border))] bg-[hsla(var(--tertiary-bg))] p-4 first:rounded-t-lg last:rounded-b-lg',
|
||||
gpus.length > 1 && 'last:rounded-t-none',
|
||||
snapshot.isDragging
|
||||
? 'border-b'
|
||||
: 'border-b-0 last:border-b'
|
||||
)}
|
||||
onClick={() => togglePanel(i)}
|
||||
{gpus
|
||||
.sort((a, b) => {
|
||||
const orderA = orderGpus.indexOf(a.id)
|
||||
const orderB = orderGpus.indexOf(b.id)
|
||||
return orderA - orderB
|
||||
})
|
||||
.map((item: any, i) => {
|
||||
const gpuUtilization = utilizedMemory(
|
||||
item.free_vram,
|
||||
item.total_vram
|
||||
)
|
||||
const isLoading = isActivatingGpu.has(item.id)
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
key={i}
|
||||
draggableId={String(i)}
|
||||
index={i}
|
||||
>
|
||||
<div className="flex flex-col items-start justify-start gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex h-full flex-shrink-0 items-center gap-2">
|
||||
<GripVerticalIcon
|
||||
size={14}
|
||||
className="text-[hsla(var(--text-tertiary))]"
|
||||
/>
|
||||
<div
|
||||
className={twMerge(
|
||||
'h-2 w-2 rounded-full',
|
||||
item.activated
|
||||
? 'bg-green-400'
|
||||
: 'bg-neutral-300'
|
||||
)}
|
||||
/>
|
||||
<h6 title={item.name}>{item.name}</h6>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-end gap-4">
|
||||
{item.activated && (
|
||||
<div className="flex w-40 items-center gap-3">
|
||||
<Progress
|
||||
value={Math.round(
|
||||
((Number(item.total_vram) -
|
||||
Number(item.free_vram)) /
|
||||
Number(item.total_vram)) *
|
||||
100
|
||||
)}
|
||||
size="small"
|
||||
className="w-full"
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
className={twMerge(
|
||||
'cursor-pointer border border-[hsla(var(--app-border))] bg-[hsla(var(--tertiary-bg))] p-4 first:rounded-t-lg last:rounded-b-lg',
|
||||
gpus.length > 1 && 'last:rounded-t-none',
|
||||
snapshot.isDragging
|
||||
? 'border-b'
|
||||
: 'border-b-0 last:border-b'
|
||||
)}
|
||||
onClick={() => togglePanel(i)}
|
||||
>
|
||||
<div className="flex flex-col items-start justify-start gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex h-full flex-shrink-0 items-center gap-2">
|
||||
<GripVerticalIcon
|
||||
size={14}
|
||||
className="text-[hsla(var(--text-tertiary))]"
|
||||
/>
|
||||
<span className="font-medium">
|
||||
{Math.round(
|
||||
((Number(item.total_vram) -
|
||||
Number(item.free_vram)) /
|
||||
Number(item.total_vram)) *
|
||||
100
|
||||
).toFixed()}
|
||||
%
|
||||
<div
|
||||
className={twMerge(
|
||||
'h-2 w-2 rounded-full',
|
||||
item.activated
|
||||
? 'bg-green-400'
|
||||
: 'bg-neutral-300'
|
||||
)}
|
||||
/>
|
||||
<h6 title={item.name}>{item.name}</h6>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-end gap-4">
|
||||
{item.activated && (
|
||||
<div className="flex w-40 items-center gap-3">
|
||||
<Progress
|
||||
value={gpuUtilization}
|
||||
size="small"
|
||||
className="w-full"
|
||||
/>
|
||||
<span className="font-medium">
|
||||
{gpuUtilization}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-2 text-xs text-[hsla(var(--text-secondary))]">
|
||||
{item.activated && (
|
||||
<span>
|
||||
{(
|
||||
(Number(item.total_vram) -
|
||||
Number(item.free_vram)) /
|
||||
1024
|
||||
).toFixed(2)}
|
||||
GB /{' '}
|
||||
</span>
|
||||
)}
|
||||
<span>
|
||||
{(
|
||||
Number(item.total_vram) / 1024
|
||||
).toFixed(2)}
|
||||
GB
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={item.activated}
|
||||
className={twMerge(
|
||||
isLoading && 'pointer-events-none'
|
||||
)}
|
||||
disabled={
|
||||
Boolean(isActivatingGpu.size) &&
|
||||
!isLoading
|
||||
}
|
||||
onChange={(e) =>
|
||||
handleSwitchChange(
|
||||
item.id,
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{isLoading && (
|
||||
<div className="ml-2 h-4 w-4 animate-spin rounded-full border-t-2 border-solid border-blue-500" />
|
||||
)}
|
||||
|
||||
<ChevronDownIcon
|
||||
size={14}
|
||||
className={twMerge(
|
||||
'relative z-10 transform cursor-pointer transition-transform',
|
||||
openPanels[i]
|
||||
? 'rotate-180'
|
||||
: 'rotate-0'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{openPanels[i] && (
|
||||
<div className="space-y-4 p-4 pb-0 text-[hsla(var(--text-secondary))]">
|
||||
<div className="flex">
|
||||
<div className="w-[200px]">
|
||||
Driver Version
|
||||
</div>
|
||||
<span>
|
||||
{
|
||||
item.additional_information
|
||||
?.driver_version
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-2 text-xs text-[hsla(var(--text-secondary))]">
|
||||
{item.activated && (
|
||||
<div className="flex">
|
||||
<div className="w-[200px]">
|
||||
Compute Capability
|
||||
</div>
|
||||
<span>
|
||||
{(
|
||||
(Number(item.total_vram) -
|
||||
Number(item.free_vram)) /
|
||||
1024
|
||||
).toFixed(2)}
|
||||
GB /{' '}
|
||||
{
|
||||
item.additional_information
|
||||
?.compute_cap
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
<span>
|
||||
{(
|
||||
Number(item.total_vram) / 1024
|
||||
).toFixed(2)}
|
||||
GB
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={item.activated}
|
||||
onChange={(e) =>
|
||||
handleSwitchChange(
|
||||
item.id,
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<ChevronDownIcon
|
||||
size={14}
|
||||
className={twMerge(
|
||||
'relative z-10 transform cursor-pointer transition-transform',
|
||||
openPanels[i]
|
||||
? 'rotate-180'
|
||||
: 'rotate-0'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{openPanels[i] && (
|
||||
<div className="space-y-4 p-4 pb-0 text-[hsla(var(--text-secondary))]">
|
||||
<div className="flex">
|
||||
<div className="w-[200px]">
|
||||
Driver Version
|
||||
</div>
|
||||
<span>
|
||||
{
|
||||
item.additional_information
|
||||
?.driver_version
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="w-[200px]">
|
||||
Compute Capability
|
||||
</div>
|
||||
<span>
|
||||
{item.additional_information?.compute_cap}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
</Draggable>
|
||||
)
|
||||
})}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user