fix: update default GPU toggle, and simplify state (#5937)

This commit is contained in:
Faisal Amir 2025-07-27 14:36:08 +07:00 committed by GitHub
parent c3fa04fdd7
commit 54d44ce741
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 79 additions and 133 deletions

View File

@ -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))
})
})

View File

@ -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<string> // Track which devices are activated
// Actions
fetchDevices: () => Promise<void>
clearError: () => void
setDevices: (devices: DeviceList[]) => void
setDevices: (devices: (DeviceList & { activated: boolean })[]) => void
toggleDevice: (deviceId: string) => void
setActivatedDevices: (deviceIds: string[]) => void
}
export const useLlamacppDevices = create<LlamacppDevicesStore>((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<LlamacppDevicesStore>((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<LlamacppDevicesStore>((set, get) => ({
...setting,
controller_props: {
...setting.controller_props,
value: deviceString,
value: deviceString.length > 0 ? deviceString : 'none',
},
}
}
@ -78,7 +99,4 @@ export const useLlamacppDevices = create<LlamacppDevicesStore>((set, get) => ({
}
},
setActivatedDevices: (deviceIds: string[]) => {
set({ activatedDevices: new Set(deviceIds) })
},
}))

View File

@ -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() {
</span>
</div> */}
<Switch
checked={activatedDevices.has(device.id)}
checked={device.activated}
onCheckedChange={() => {
toggleDevice(device.id)
stopAllModels()

View File

@ -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()
}
}

View File

@ -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() {
</span>
<span
className={`text-sm px-2 py-1 rounded-md ${
activatedDevices.has(device.id)
device.activated
? 'bg-green-500/20 text-green-600 dark:text-green-400'
: 'hidden'
}`}
>
{activatedDevices.has(device.id)
{device.activated
? t('system-monitor:active')
: 'Inactive'}
</span>

View File

@ -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<DeviceList[]> => {
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()
}