import { create } from 'zustand' import { persist, createJSONStorage } from 'zustand/middleware' import { localStorageKey } from '@/constants/localStorage' // Hardware data types export interface CPU { arch: string core_count: number extensions: string[] name: string usage: number } export interface GPUAdditionalInfo { compute_cap: string driver_version: string } export interface GPU { name: string total_memory: number vendor: string uuid: string driver_version: string activated?: boolean nvidia_info: { index: number compute_capability: string } vulkan_info: { index: number device_id: number device_type: string api_version: string } } export interface OS { name: string version: string } export interface RAM { available: number total: number } export interface HardwareData { cpu: CPU gpus: GPU[] os_type: string os_name: string total_memory: number } export interface SystemUsage { cpu: number used_memory: number total_memory: number gpus: { uuid: string used_memory: number total_memory: number }[] } // Default values const defaultHardwareData: HardwareData = { cpu: { arch: '', core_count: 0, extensions: [], name: '', usage: 0, }, gpus: [], os_type: '', os_name: '', total_memory: 0, } const defaultSystemUsage: SystemUsage = { cpu: 0, used_memory: 0, total_memory: 0, gpus: [], } interface HardwareStore { // Hardware data hardwareData: HardwareData systemUsage: SystemUsage // Update functions setCPU: (cpu: CPU) => void setGPUs: (gpus: GPU[]) => void setOS: (os: OS) => void setRAM: (ram: RAM) => void // Update entire hardware data at once setHardwareData: (data: HardwareData) => void // Update hardware data while preserving GPU order updateHardwareDataPreservingGpuOrder: (data: HardwareData) => void // Update individual GPU updateGPU: (index: number, gpu: GPU) => void // Update RAM available updateSystemUsage: (usage: SystemUsage) => void // Toggle GPU activation (async, with loading) toggleGPUActivation: (index: number) => Promise // GPU loading state gpuLoading: { [index: number]: boolean } setGpuLoading: (index: number, loading: boolean) => void // Polling control pollingPaused: boolean pausePolling: () => void resumePolling: () => void // Reorder GPUs reorderGPUs: (oldIndex: number, newIndex: number) => void // Get activated GPU device string getActivatedDeviceString: (backendType?: string) => string // Update GPU activation states from device string updateGPUActivationFromDeviceString: (deviceString: string) => void } export const useHardware = create()( persist( (set, get) => ({ hardwareData: defaultHardwareData, systemUsage: defaultSystemUsage, gpuLoading: {}, pollingPaused: false, setGpuLoading: (index, loading) => set((state) => ({ gpuLoading: { ...state.gpuLoading, [state.hardwareData.gpus[index].uuid]: loading, }, })), pausePolling: () => set({ pollingPaused: true }), resumePolling: () => set({ pollingPaused: false }), setCPU: (cpu) => set((state) => ({ hardwareData: { ...state.hardwareData, cpu, }, })), setGPUs: (gpus) => set((state) => ({ hardwareData: { ...state.hardwareData, gpus, }, })), setOS: (os) => set((state) => ({ hardwareData: { ...state.hardwareData, os, }, })), setRAM: (ram) => set((state) => ({ hardwareData: { ...state.hardwareData, ram, }, })), setHardwareData: (data) => set({ hardwareData: { ...data, gpus: data.gpus.map(gpu => ({ ...gpu, activated: gpu.activated ?? false })) }, }), updateHardwareDataPreservingGpuOrder: (data) => set((state) => { // If we have existing GPU data, preserve the order and activation state if (state.hardwareData.gpus.length > 0) { // Reorder fresh GPU data to match existing order, adding new GPUs at the end const reorderedGpus: GPU[] = [] const processedUuids = new Set() // First, add existing GPUs in their current order, preserving activation state state.hardwareData.gpus.forEach(existingGpu => { const freshGpu = data.gpus.find(gpu => gpu.uuid === existingGpu.uuid) if (freshGpu) { reorderedGpus.push({ ...freshGpu, activated: existingGpu.activated ?? false }) processedUuids.add(freshGpu.uuid) } }) // Then, add any new GPUs that weren't in the existing order (default to inactive) data.gpus.forEach(freshGpu => { if (!processedUuids.has(freshGpu.uuid)) { reorderedGpus.push({ ...freshGpu, activated: false }) } }) return { hardwareData: { ...data, gpus: reorderedGpus } } } else { // No existing GPU data, initialize all GPUs as inactive return { hardwareData: { ...data, gpus: data.gpus.map(gpu => ({ ...gpu, activated: false })) } } } }), updateGPU: (index, gpu) => set((state) => { const newGPUs = [...state.hardwareData.gpus] if (index >= 0 && index < newGPUs.length) { newGPUs[index] = gpu } return { hardwareData: { ...state.hardwareData, gpus: newGPUs, }, } }), updateSystemUsage: (systemUsage) => set(() => ({ systemUsage, })), toggleGPUActivation: async (index) => { const { pausePolling, resumePolling, setGpuLoading } = get() pausePolling() setGpuLoading(index, true) try { await new Promise((resolve) => setTimeout(resolve, 200)) // Simulate async operation set((state) => { const newGPUs = [...state.hardwareData.gpus] if (index >= 0 && index < newGPUs.length) { newGPUs[index] = { ...newGPUs[index], activated: !newGPUs[index].activated, } } return { hardwareData: { ...state.hardwareData, gpus: newGPUs, }, } }) // Update the device setting after state change const updatedState = get() // Import and get backend type const { useModelProvider } = await import('./useModelProvider') const { updateProvider, getProviderByName } = useModelProvider.getState() const llamacppProvider = getProviderByName('llamacpp') const backendType = llamacppProvider?.settings.find(s => s.key === 'version_backend')?.controller_props.value as string const deviceString = updatedState.getActivatedDeviceString(backendType) console.log(`GPU ${index} activation toggled. Backend: "${backendType}", New device string: "${deviceString}"`) console.log('Activated GPUs:', updatedState.hardwareData.gpus.filter(gpu => gpu.activated).map((gpu, i) => ({ name: gpu.name, nvidia: gpu.nvidia_info?.index, vulkan: gpu.vulkan_info?.index, activated: gpu.activated }))) if (llamacppProvider) { const updatedSettings = llamacppProvider.settings.map(setting => { if (setting.key === 'device') { return { ...setting, controller_props: { ...setting.controller_props, value: deviceString } } } return setting }) updateProvider('llamacpp', { settings: updatedSettings }) console.log(`Updated llamacpp device setting to: "${deviceString}"`) } } finally { setGpuLoading(index, false) setTimeout(resumePolling, 1000) // Resume polling after 1s } }, reorderGPUs: (oldIndex, newIndex) => set((state) => { const newGPUs = [...state.hardwareData.gpus] // Move the GPU from oldIndex to newIndex if ( oldIndex >= 0 && oldIndex < newGPUs.length && newIndex >= 0 && newIndex < newGPUs.length ) { const [removed] = newGPUs.splice(oldIndex, 1) newGPUs.splice(newIndex, 0, removed) } return { hardwareData: { ...state.hardwareData, gpus: newGPUs, }, } }), getActivatedDeviceString: (backendType?: string) => { const { hardwareData } = get() // Get activated GPUs and generate appropriate device format based on backend const activatedDevices = hardwareData.gpus .filter(gpu => gpu.activated) .map(gpu => { const isCudaBackend = backendType?.includes('cuda') const isVulkanBackend = backendType?.includes('vulkan') // Handle different backend scenarios if (isCudaBackend && isVulkanBackend) { // Mixed backend - prefer CUDA for NVIDIA GPUs, Vulkan for others if (gpu.nvidia_info) { return `cuda:${gpu.nvidia_info.index}` } else if (gpu.vulkan_info) { return `vulkan:${gpu.vulkan_info.index}` } } else if (isCudaBackend && gpu.nvidia_info) { // CUDA backend - only use CUDA-compatible GPUs return `cuda:${gpu.nvidia_info.index}` } else if (isVulkanBackend && gpu.vulkan_info) { // Vulkan backend - only use Vulkan-compatible GPUs return `vulkan:${gpu.vulkan_info.index}` } else if (!backendType) { // No backend specified, use GPU's preferred type if (gpu.nvidia_info) { return `cuda:${gpu.nvidia_info.index}` } else if (gpu.vulkan_info) { return `vulkan:${gpu.vulkan_info.index}` } } return null }) .filter(device => device !== null) as string[] const deviceString = activatedDevices.join(',') return deviceString }, updateGPUActivationFromDeviceString: (deviceString: string) => { set((state) => { const newGPUs = [...state.hardwareData.gpus] // Parse device string to get active device indices const activeDevices = deviceString .split(',') .map(device => device.trim()) .filter(device => device.length > 0) .map(device => { const match = device.match(/^(cuda|vulkan):(\d+)$/) if (match) { return { type: match[1] as 'cuda' | 'vulkan', index: parseInt(match[2]) } } return null }) .filter(device => device !== null) as Array<{type: 'cuda' | 'vulkan', index: number}> // Update GPU activation states newGPUs.forEach((gpu, gpuIndex) => { const shouldBeActive = activeDevices.some(device => { if (device.type === 'cuda' && gpu.nvidia_info) { return gpu.nvidia_info.index === device.index } else if (device.type === 'vulkan' && gpu.vulkan_info) { return gpu.vulkan_info.index === device.index } return false }) newGPUs[gpuIndex] = { ...gpu, activated: shouldBeActive } }) return { hardwareData: { ...state.hardwareData, gpus: newGPUs } } }) }, }), { name: localStorageKey.settingHardware, storage: createJSONStorage(() => localStorage), } ) )