import { createFileRoute } from '@tanstack/react-router' import { route } from '@/constants/routes' import SettingsMenu from '@/containers/SettingsMenu' import HeaderPage from '@/containers/HeaderPage' import { Card, CardItem } from '@/containers/Card' import { Switch } from '@/components/ui/switch' import { Progress } from '@/components/ui/progress' import { useTranslation } from '@/i18n/react-i18next-compat' import { useHardware } from '@/hooks/useHardware' // import { useVulkan } from '@/hooks/useVulkan' import type { GPU, HardwareData } from '@/hooks/useHardware' import { useEffect, useState } from 'react' import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent, } from '@dnd-kit/core' import { SortableContext, verticalListSortingStrategy, useSortable, } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import { IconGripVertical, IconDeviceDesktopAnalytics, } from '@tabler/icons-react' import { getHardwareInfo, getSystemUsage } from '@/services/hardware' import { WebviewWindow } from '@tauri-apps/api/webviewWindow' import { formatMegaBytes } from '@/lib/utils' import { windowKey } from '@/constants/windows' import { toNumber } from '@/utils/number' import { useModelProvider } from '@/hooks/useModelProvider' // eslint-disable-next-line @typescript-eslint/no-explicit-any export const Route = createFileRoute(route.settings.hardware as any)({ component: Hardware, }) function SortableGPUItem({ gpu, index, isCompatible, isActivated }: { gpu: GPU; index: number; isCompatible: boolean; isActivated: boolean }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: index }) const { t } = useTranslation() const { systemUsage, toggleGPUActivation, gpuLoading } = useHardware() const usage = systemUsage.gpus[index] const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, position: 'relative' as const, zIndex: isDragging ? 1 : 0, } return (
{gpu.name} {!isCompatible && ( Incompatible with current backend )}
} actions={
toggleGPUActivation(index)} />
} />
{formatMegaBytes(usage?.used_memory)}{' '} {t('settings:hardware.freeOf')}{' '} {formatMegaBytes(gpu.total_memory)} } /> {gpu.driver_version?.slice(0, 50) || '-'} } /> {gpu.nvidia_info?.compute_capability ?? gpu.vulkan_info?.api_version} } />
) } function Hardware() { const { t } = useTranslation() const { hardwareData, systemUsage, setHardwareData, updateHardwareDataPreservingGpuOrder, updateSystemUsage, reorderGPUs, pollingPaused, } = useHardware() // const { vulkanEnabled, setVulkanEnabled } = useVulkan() const { providers } = useModelProvider() const llamacpp = providers.find((p) => p.provider === 'llamacpp') const versionBackend = llamacpp?.settings.find((s) => s.key === "version_backend")?.controller_props.value // Determine backend type and filter GPUs accordingly const isCudaBackend = typeof versionBackend === 'string' && versionBackend.includes('cuda') const isVulkanBackend = typeof versionBackend === 'string' && versionBackend.includes('vulkan') // Filter and prepare GPUs based on backend const getFilteredGPUs = () => { // Always show all GPUs, but compatibility will be determined by isGPUActive return hardwareData.gpus } const filteredGPUs = getFilteredGPUs() // Check if GPU should be active based on backend compatibility const isGPUCompatible = (gpu: GPU) => { if (isCudaBackend) { return gpu.nvidia_info !== null } else if (isVulkanBackend) { return gpu.vulkan_info !== null } else { // No valid backend - all GPUs are inactive return false } } // Check if GPU is actually activated const isGPUActive = (gpu: GPU) => { return isGPUCompatible(gpu) && (gpu.activated ?? false) } useEffect(() => { getHardwareInfo().then((freshData) => { const data = freshData as unknown as HardwareData updateHardwareDataPreservingGpuOrder(data) }) }, [updateHardwareDataPreservingGpuOrder]) // Hardware and provider sync logic const { getActivatedDeviceString, updateGPUActivationFromDeviceString } = useHardware() const { updateProvider, getProviderByName } = useModelProvider() const [isInitialized, setIsInitialized] = useState(false) // Initialize GPU activations from device setting on first load useEffect(() => { if (hardwareData.gpus.length > 0 && !isInitialized) { const llamacppProvider = getProviderByName('llamacpp') const currentDeviceSetting = llamacppProvider?.settings.find(s => s.key === 'device')?.controller_props.value as string if (currentDeviceSetting) { console.log(`Initializing GPU activations from device setting: "${currentDeviceSetting}"`) updateGPUActivationFromDeviceString(currentDeviceSetting) } setIsInitialized(true) } }, [hardwareData.gpus.length, isInitialized, getProviderByName, updateGPUActivationFromDeviceString]) // Sync device setting when GPU activations change (only after initialization) const gpuActivationStates = hardwareData.gpus.map(gpu => gpu.activated) useEffect(() => { if (isInitialized && hardwareData.gpus.length > 0) { const llamacppProvider = getProviderByName('llamacpp') const backendType = llamacppProvider?.settings.find(s => s.key === 'version_backend')?.controller_props.value as string const deviceString = getActivatedDeviceString(backendType) if (llamacppProvider) { const currentDeviceSetting = llamacppProvider.settings.find(s => s.key === 'device') // Sync device string when GPU activations change (only after initialization) if (currentDeviceSetting && currentDeviceSetting.controller_props.value !== deviceString) { console.log(`Syncing device string from "${currentDeviceSetting.controller_props.value}" to "${deviceString}"`) 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 }) } } } }, [isInitialized, gpuActivationStates, versionBackend, getActivatedDeviceString, updateProvider, getProviderByName, hardwareData.gpus.length]) // Set up DnD sensors const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor) ) // Handle drag end event const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event if (over && active.id !== over.id) { // Find the actual indices in the original hardwareData.gpus array const activeGpu = filteredGPUs[active.id as number] const overGpu = filteredGPUs[over.id as number] const oldIndex = hardwareData.gpus.findIndex(gpu => gpu.uuid === activeGpu.uuid) const newIndex = hardwareData.gpus.findIndex(gpu => gpu.uuid === overGpu.uuid) if (oldIndex !== -1 && newIndex !== -1) { reorderGPUs(oldIndex, newIndex) } } } useEffect(() => { if (pollingPaused) return const intervalId = setInterval(() => { getSystemUsage().then((data) => { updateSystemUsage(data) }) }, 5000) return () => clearInterval(intervalId) }, [setHardwareData, updateSystemUsage, pollingPaused]) const handleClickSystemMonitor = async () => { try { // Check if system monitor window already exists const existingWindow = await WebviewWindow.getByLabel( windowKey.systemMonitorWindow ) if (existingWindow) { // If window exists, focus it await existingWindow.setFocus() console.log('Focused existing system monitor window') } else { // Create a new system monitor window const monitorWindow = new WebviewWindow(windowKey.systemMonitorWindow, { url: route.systemMonitor, title: 'System Monitor - Jan', width: 900, height: 600, resizable: true, center: true, }) // Listen for window creation monitorWindow.once('tauri://created', () => { console.log('System monitor window created') }) // Listen for window errors monitorWindow.once('tauri://error', (e) => { console.error('Error creating system monitor window:', e) }) } } catch (error) { console.error('Failed to open system monitor window:', error) } } return (

{t('common:settings')}

{t('settings:hardware.systemMonitor')}

{/* OS Information */} {hardwareData.os_type} } /> {hardwareData.os_name} } /> {/* CPU Information */} {hardwareData.cpu?.name} } /> {hardwareData.cpu?.arch} } /> {hardwareData.cpu?.core_count} } /> {hardwareData.cpu?.extensions?.join(', ').length > 0 && ( 6} actions={ {hardwareData.cpu?.extensions?.join(', ')} } /> )} {systemUsage.cpu > 0 && ( <> {systemUsage.cpu?.toFixed(2)}% )}
} /> {/* RAM Information */} {formatMegaBytes(hardwareData.total_memory)} } /> {formatMegaBytes( hardwareData.total_memory - systemUsage.used_memory )} } /> {hardwareData.total_memory > 0 && ( <> {( toNumber( systemUsage.used_memory / systemUsage.total_memory ) * 100 ).toFixed(2)} % )}
} /> {/* Vulkan Settings */} {/* {hardwareData.gpus.length > 0 && ( { setVulkanEnabled(checked) setTimeout(() => { window.location.reload() }, 500) // Reload after 500ms to apply changes }} />
} /> )} */} {/* GPU Information */} {!IS_MACOS ? ( {hardwareData.gpus.length > 0 ? ( index)} strategy={verticalListSortingStrategy} > {filteredGPUs.map((gpu, index) => ( ))} ) : ( } /> )} ) : ( <> )}
) }