diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 1b547259e..2f039b20c 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -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 diff --git a/web/screens/Settings/Hardware/index.tsx b/web/screens/Settings/Hardware/index.tsx index 4bd0e9fce..d82b2ce54 100644 --- a/web/screens/Settings/Hardware/index.tsx +++ b/web/screens/Settings/Hardware/index.tsx @@ -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('gpus', [], undefined, { +const orderGpusAtom = atomWithStorage('orderGpus', [], undefined, { getOnInit: true, }) const Hardware = () => { - const { hardware } = useGetHardwareInfo() + const { hardware, mutate } = useGetHardwareInfo() + const [isActivatingGpu, setIsActivatingGpu] = useState>(new Set()) + const [openPanels, setOpenPanels] = useState>({}) const cpuUsage = useAtomValue(cpuUsageAtom) @@ -40,7 +45,9 @@ const Hardware = () => { const usedRam = useAtomValue(usedRamAtom) const ramUtilitized = useAtomValue(ramUtilitizedAtom) - const [gpus, setGpus] = useAtom(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) => ( - - {(provided, snapshot) => ( -
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 ( + -
-
-
- -
-
{item.name}
-
-
- {item.activated && ( -
- ( +
1 && 'last:rounded-t-none', + snapshot.isDragging + ? 'border-b' + : 'border-b-0 last:border-b' + )} + onClick={() => togglePanel(i)} + > +
+
+
+ - - {Math.round( - ((Number(item.total_vram) - - Number(item.free_vram)) / - Number(item.total_vram)) * - 100 - ).toFixed()} - % +
+
{item.name}
+
+
+ {item.activated && ( +
+ + + {gpuUtilization}% + +
+ )} + +
+ {item.activated && ( + + {( + (Number(item.total_vram) - + Number(item.free_vram)) / + 1024 + ).toFixed(2)} + GB /{' '} + + )} + + {( + Number(item.total_vram) / 1024 + ).toFixed(2)} + GB + +
+ + + handleSwitchChange( + item.id, + e.target.checked + ) + } + /> + + {isLoading && ( +
+ )} + + +
+
+
+ + {openPanels[i] && ( +
+
+
+ Driver Version +
+ + { + item.additional_information + ?.driver_version + }
- )} - -
- {item.activated && ( +
+
+ Compute Capability +
- {( - (Number(item.total_vram) - - Number(item.free_vram)) / - 1024 - ).toFixed(2)} - GB /{' '} + { + item.additional_information + ?.compute_cap + } - )} - - {( - Number(item.total_vram) / 1024 - ).toFixed(2)} - GB - +
- - - handleSwitchChange( - item.id, - e.target.checked - ) - } - /> - - -
-
-
- - {openPanels[i] && ( -
-
-
- Driver Version -
- - { - item.additional_information - ?.driver_version - } - -
-
-
- Compute Capability -
- - {item.additional_information?.compute_cap} - -
+ )}
)} -
- )} - - ))} + + ) + })} {provided.placeholder}
)}