From 54d44ce74188d72cbdfcf4fa608d515572fb330a Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Sun, 27 Jul 2025 14:36:08 +0700 Subject: [PATCH] fix: update default GPU toggle, and simplify state (#5937) --- .../__tests__/useLlamacppDevices.test.ts | 57 +++++++++--------- web-app/src/hooks/useLlamacppDevices.ts | 58 ++++++++++++------- web-app/src/routes/settings/hardware.tsx | 40 +------------ .../settings/providers/$providerName.tsx | 5 +- web-app/src/routes/system-monitor.tsx | 43 +------------- web-app/src/services/hardware.ts | 9 ++- 6 files changed, 79 insertions(+), 133 deletions(-) diff --git a/web-app/src/hooks/__tests__/useLlamacppDevices.test.ts b/web-app/src/hooks/__tests__/useLlamacppDevices.test.ts index 15a0afb35..6c5639b48 100644 --- a/web-app/src/hooks/__tests__/useLlamacppDevices.test.ts +++ b/web-app/src/hooks/__tests__/useLlamacppDevices.test.ts @@ -31,15 +31,13 @@ describe('useLlamacppDevices', () => { expect(result.current.devices).toEqual([]) expect(result.current.loading).toBe(false) expect(result.current.error).toBeNull() - expect(result.current.activatedDevices).toEqual(new Set()) expect(typeof result.current.fetchDevices).toBe('function') expect(typeof result.current.clearError).toBe('function') expect(typeof result.current.setDevices).toBe('function') expect(typeof result.current.toggleDevice).toBe('function') - expect(typeof result.current.setActivatedDevices).toBe('function') }) - it('should fetch devices successfully', async () => { + it('should fetch devices successfully with activation status', async () => { const mockDevices = [ { id: 'CUDA0', name: 'NVIDIA GeForce RTX 4090', mem: 24576, free: 20480 }, { id: 'CUDA1', name: 'NVIDIA GeForce RTX 3080', mem: 10240, free: 8192 }, @@ -53,7 +51,11 @@ describe('useLlamacppDevices', () => { await result.current.fetchDevices() }) - expect(result.current.devices).toEqual(mockDevices) + // Should have devices with activated property (empty setting means all activated) + expect(result.current.devices).toEqual([ + { ...mockDevices[0], activated: true }, + { ...mockDevices[1], activated: true }, + ]) expect(result.current.loading).toBe(false) expect(result.current.error).toBeNull() expect(mockGetLlamacppDevices).toHaveBeenCalledOnce() @@ -89,44 +91,43 @@ describe('useLlamacppDevices', () => { expect(result.current.devices).toEqual(mockDevices) }) - it('should toggle device activation', () => { + it('should toggle device activation', async () => { const { result } = renderHook(() => useLlamacppDevices()) - // Initially no devices are activated - expect(result.current.activatedDevices).toEqual(new Set()) + // Set initial devices with activation status + act(() => { + result.current.setDevices([ + { id: 'CUDA0', name: 'NVIDIA GeForce RTX 4090', mem: 24576, free: 20480, activated: false }, + { id: 'CUDA1', name: 'NVIDIA GeForce RTX 3080', mem: 10240, free: 8192, activated: false }, + ]) + }) // Toggle a device on - act(() => { - result.current.toggleDevice('CUDA0') + await act(async () => { + await result.current.toggleDevice('CUDA0') }) - expect(result.current.activatedDevices).toEqual(new Set(['CUDA0'])) + expect(result.current.devices[0].activated).toBe(true) + expect(result.current.devices[1].activated).toBe(false) // Toggle the same device off - act(() => { - result.current.toggleDevice('CUDA0') + await act(async () => { + await result.current.toggleDevice('CUDA0') }) - expect(result.current.activatedDevices).toEqual(new Set()) + expect(result.current.devices[0].activated).toBe(false) + expect(result.current.devices[1].activated).toBe(false) // Toggle multiple devices - act(() => { - result.current.toggleDevice('CUDA0') - result.current.toggleDevice('CUDA1') + await act(async () => { + await result.current.toggleDevice('CUDA0') + }) + await act(async () => { + await result.current.toggleDevice('CUDA1') }) - expect(result.current.activatedDevices).toEqual(new Set(['CUDA0', 'CUDA1'])) + expect(result.current.devices[0].activated).toBe(true) + expect(result.current.devices[1].activated).toBe(true) }) - it('should set activated devices', () => { - const { result } = renderHook(() => useLlamacppDevices()) - - const deviceIds = ['CUDA0', 'CUDA1', 'Vulkan0'] - - act(() => { - result.current.setActivatedDevices(deviceIds) - }) - - expect(result.current.activatedDevices).toEqual(new Set(deviceIds)) - }) }) \ No newline at end of file diff --git a/web-app/src/hooks/useLlamacppDevices.ts b/web-app/src/hooks/useLlamacppDevices.ts index 38e33ee18..245bcc60f 100644 --- a/web-app/src/hooks/useLlamacppDevices.ts +++ b/web-app/src/hooks/useLlamacppDevices.ts @@ -4,31 +4,48 @@ import { updateSettings } from '@/services/providers' import { useModelProvider } from './useModelProvider' interface LlamacppDevicesStore { - devices: DeviceList[] + devices: (DeviceList & { activated: boolean })[] loading: boolean error: string | null - activatedDevices: Set // Track which devices are activated // Actions fetchDevices: () => Promise clearError: () => void - setDevices: (devices: DeviceList[]) => void + setDevices: (devices: (DeviceList & { activated: boolean })[]) => void toggleDevice: (deviceId: string) => void - setActivatedDevices: (deviceIds: string[]) => void } export const useLlamacppDevices = create((set, get) => ({ devices: [], loading: false, error: null, - activatedDevices: new Set(), fetchDevices: async () => { set({ loading: true, error: null }) try { const devices = await getLlamacppDevices() - set({ devices, loading: false }) + + // Check current device setting from provider + const { getProviderByName } = useModelProvider.getState() + const llamacppProvider = getProviderByName('llamacpp') + const currentDeviceSetting = llamacppProvider?.settings.find( + (s) => s.key === 'device' + )?.controller_props.value as string + + // Parse device setting from extension which represents activated devices + const activatedDevices = currentDeviceSetting + ? currentDeviceSetting.split(',').map(d => d.trim()).filter(Boolean) + : [] + + const devicesWithActivation = devices.map((device) => ({ + ...device, + activated: + // Empty device setting means all devices are activated + !currentDeviceSetting || currentDeviceSetting === '' || activatedDevices.includes(device.id), + })) + + set({ devices: devicesWithActivation, loading: false }) } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Failed to fetch devices' @@ -41,22 +58,26 @@ export const useLlamacppDevices = create((set, get) => ({ setDevices: (devices) => set({ devices }), toggleDevice: async (deviceId: string) => { - set((state) => { - const newActivatedDevices = new Set(state.activatedDevices) - if (newActivatedDevices.has(deviceId)) { - newActivatedDevices.delete(deviceId) - } else { - newActivatedDevices.add(deviceId) - } - return { activatedDevices: newActivatedDevices } - }) + // Toggle device activation in the local state + set((state) => ({ + devices: state.devices.map((device) => + device.id === deviceId + ? { ...device, activated: !device.activated } + : device + ), + })) // Update llamacpp provider settings const { getProviderByName, updateProvider } = useModelProvider.getState() const llamacppProvider = getProviderByName('llamacpp') if (llamacppProvider) { - const deviceString = Array.from(get().activatedDevices).join(',') + // Get activated devices after toggle + const activatedDeviceIds = get().devices + .filter((device) => device.activated) + .map((device) => device.id) + + const deviceString = activatedDeviceIds.join(',') const updatedSettings = llamacppProvider.settings.map((setting) => { if (setting.key === 'device') { @@ -64,7 +85,7 @@ export const useLlamacppDevices = create((set, get) => ({ ...setting, controller_props: { ...setting.controller_props, - value: deviceString, + value: deviceString.length > 0 ? deviceString : 'none', }, } } @@ -78,7 +99,4 @@ export const useLlamacppDevices = create((set, get) => ({ } }, - setActivatedDevices: (deviceIds: string[]) => { - set({ activatedDevices: new Set(deviceIds) }) - }, })) diff --git a/web-app/src/routes/settings/hardware.tsx b/web-app/src/routes/settings/hardware.tsx index de2ac2de4..fc3987743 100644 --- a/web-app/src/routes/settings/hardware.tsx +++ b/web-app/src/routes/settings/hardware.tsx @@ -45,7 +45,6 @@ function Hardware() { devices: llamacppDevices, loading: llamacppDevicesLoading, error: llamacppDevicesError, - activatedDevices, toggleDevice, fetchDevices, } = IS_MACOS @@ -53,7 +52,6 @@ function Hardware() { devices: [], loading: false, error: null, - activatedDevices: new Set(), toggleDevice: () => {}, fetchDevices: () => {}, } @@ -87,43 +85,7 @@ function Hardware() { }) }, [setHardwareData, updateSystemUsage]) - const { getProviderByName } = useModelProvider() - // Initialize llamacpp device activations from provider settings - useEffect(() => { - if (llamacppDevices.length > 0 && activatedDevices.size === 0) { - const llamacppProvider = getProviderByName('llamacpp') - const currentDeviceSetting = llamacppProvider?.settings.find( - (s) => s.key === 'device' - )?.controller_props.value as string - - if (currentDeviceSetting) { - const deviceIds = currentDeviceSetting - .split(',') - .map((device) => device.trim()) - .filter((device) => device.length > 0) - - // Find matching devices by ID - const matchingDeviceIds = deviceIds.filter((deviceId) => - llamacppDevices.some((device) => device.id === deviceId) - ) - - if (matchingDeviceIds.length > 0) { - console.log( - `Initializing llamacpp device activations from device setting: "${currentDeviceSetting}"` - ) - // Update the activatedDevices in the hook - const { setActivatedDevices } = useLlamacppDevices.getState() - setActivatedDevices(matchingDeviceIds) - } - } - } - }, [ - llamacppDevices.length, - activatedDevices.size, - getProviderByName, - llamacppDevices, - ]) useEffect(() => { if (pollingPaused) return @@ -361,7 +323,7 @@ function Hardware() { */} { toggleDevice(device.id) stopAllModels() diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx index 41d2b0217..e3dc7af6c 100644 --- a/web-app/src/routes/settings/providers/$providerName.tsx +++ b/web-app/src/routes/settings/providers/$providerName.tsx @@ -367,9 +367,10 @@ function ProviderDetail() { // Reset llamacpp device activations when backend version changes if (providerName === 'llamacpp') { - const { setActivatedDevices } = + // Refresh devices to update activation status from provider settings + const { fetchDevices } = useLlamacppDevices.getState() - setActivatedDevices([]) + fetchDevices() } } diff --git a/web-app/src/routes/system-monitor.tsx b/web-app/src/routes/system-monitor.tsx index 094ab42b8..e7eac9dad 100644 --- a/web-app/src/routes/system-monitor.tsx +++ b/web-app/src/routes/system-monitor.tsx @@ -9,7 +9,6 @@ import { IconDeviceDesktopAnalytics } from '@tabler/icons-react' import { useTranslation } from '@/i18n/react-i18next-compat' import { toNumber } from '@/utils/number' import { useLlamacppDevices } from '@/hooks/useLlamacppDevices' -import { useModelProvider } from '@/hooks/useModelProvider' import { getSystemUsage } from '@/services/hardware' export const Route = createFileRoute(route.systemMonitor as any)({ @@ -22,11 +21,8 @@ function SystemMonitor() { const { devices: llamacppDevices, - activatedDevices, fetchDevices, - setActivatedDevices, } = useLlamacppDevices() - const { getProviderByName } = useModelProvider() const [isInitialized, setIsInitialized] = useState(false) @@ -57,41 +53,6 @@ function SystemMonitor() { } }, [hardwareData.gpus.length, isInitialized]) - // Initialize llamacpp device activations from provider settings - useEffect(() => { - if (llamacppDevices.length > 0 && activatedDevices.size === 0) { - const llamacppProvider = getProviderByName('llamacpp') - const currentDeviceSetting = llamacppProvider?.settings.find( - (s) => s.key === 'device' - )?.controller_props.value as string - - if (currentDeviceSetting) { - const deviceIds = currentDeviceSetting - .split(',') - .map((device) => device.trim()) - .filter((device) => device.length > 0) - - // Find matching devices by ID - const matchingDeviceIds = deviceIds.filter((deviceId) => - llamacppDevices.some((device) => device.id === deviceId) - ) - - if (matchingDeviceIds.length > 0) { - console.log( - `Initializing llamacpp device activations from device setting: "${currentDeviceSetting}"` - ) - // Update the activatedDevices in the hook - setActivatedDevices(matchingDeviceIds) - } - } - } - }, [ - llamacppDevices.length, - activatedDevices.size, - getProviderByName, - llamacppDevices, - setActivatedDevices, - ]) // Calculate RAM usage percentage const ramUsagePercentage = @@ -209,12 +170,12 @@ function SystemMonitor() { - {activatedDevices.has(device.id) + {device.activated ? t('system-monitor:active') : 'Inactive'} diff --git a/web-app/src/services/hardware.ts b/web-app/src/services/hardware.ts index 700db5485..79bc7b1c3 100644 --- a/web-app/src/services/hardware.ts +++ b/web-app/src/services/hardware.ts @@ -7,6 +7,7 @@ export interface DeviceList { name: string mem: number free: number + activated: boolean } /** @@ -31,12 +32,14 @@ export const getSystemUsage = async () => { */ export const getLlamacppDevices = async (): Promise => { const extensionManager = window.core.extensionManager - const llamacppExtension = extensionManager.getByName('@janhq/llamacpp-extension') - + const llamacppExtension = extensionManager.getByName( + '@janhq/llamacpp-extension' + ) + if (!llamacppExtension) { throw new Error('llamacpp extension not found') } - + return llamacppExtension.getDevices() }